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 	    renderContext = new PluggableRenderContext<V,E>(layout.getGraph());
140 	}
141 	
142     /**
143      * Create an instance with the specified Layout and view dimension.
144      * 
145      * @param layout		The Layout to apply, with its associated Graph
146      * @param preferredSize the preferred size of this View
147      */
148 	public BasicVisualizationServer(Layout<V,E> layout, Dimension preferredSize) {
149 	    this(new DefaultVisualizationModel<V,E>(layout, preferredSize), preferredSize);
150 	    renderContext = new PluggableRenderContext<V,E>(layout.getGraph());
151 	}
152 	
153 	/**
154 	 * Create an instance with the specified model and a default dimension (600x600).
155 	 * 
156 	 * @param model the model to use
157 	 */
158 	public BasicVisualizationServer(VisualizationModel<V,E> model) {
159 	    this(model, new Dimension(600,600));
160 	}
161 	
162 	/**
163 	 * Create an instance with the specified model and view dimension.
164 	 * 
165 	 * @param model the model to use
166 	 * @param preferredSize initial preferred size of the view
167 	 */
168     public BasicVisualizationServer(VisualizationModel<V,E> model,
169 	        Dimension preferredSize) {
170 	    this.model = model;
171 	    renderContext = new PluggableRenderContext<V,E>(model.getGraphLayout().getGraph());
172 	    model.addChangeListener(this);
173 	    setDoubleBuffered(false);
174 		this.addComponentListener(new VisualizationListener(this));
175 
176 		setPickSupport(new ShapePickSupport<V,E>(this));
177 		setPickedVertexState(new MultiPickedState<V>());
178 		setPickedEdgeState(new MultiPickedState<E>());
179         
180         renderContext.setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<E>(getPickedEdgeState(), Color.black, Color.cyan));
181         renderContext.setVertexFillPaintTransformer(new PickableVertexPaintTransformer<V>(getPickedVertexState(), 
182                 Color.red, Color.yellow));
183 		
184 		setPreferredSize(preferredSize);
185 		renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
186 
187         renderContext.getMultiLayerTransformer().addChangeListener(this);
188 	}
189 	
190 	@Override
191     public void setDoubleBuffered(boolean doubleBuffered) {
192 	    this.doubleBuffered = doubleBuffered;
193 	}
194 	
195 	@Override
196     public boolean isDoubleBuffered() {
197 	    return doubleBuffered;
198 	}
199 	
200 	/**
201 	 * Always sanity-check getSize so that we don't use a
202 	 * value that is improbable
203 	 * @see java.awt.Component#getSize()
204 	 */
205 	@Override
206 	public Dimension getSize() {
207 		Dimension d = super.getSize();
208 		if(d.width <= 0 || d.height <= 0) {
209 			d = getPreferredSize();
210 		}
211 		return d;
212 	}
213 
214 	/**
215 	 * Ensure that, if doubleBuffering is enabled, the offscreen
216 	 * image buffer exists and is the correct size.
217 	 * @param d the expected Dimension of the offscreen buffer
218 	 */
219 	protected void checkOffscreenImage(Dimension d) {
220 	    if(doubleBuffered) {
221 	        if(offscreen == null || offscreen.getWidth() != d.width || offscreen.getHeight() != d.height) {
222 	            offscreen = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
223 	            offscreenG2d = offscreen.createGraphics();
224 	        }
225 	    }
226 	}
227 	
228     public VisualizationModel<V,E> getModel() {
229         return model;
230     }
231 
232     public void setModel(VisualizationModel<V,E> model) {
233         this.model = model;
234     }
235 
236 	public void stateChanged(ChangeEvent e) {
237 	    repaint();
238 	    fireStateChanged();
239 	}
240 
241 	public void setRenderer(Renderer<V,E> r) {
242 	    this.renderer = r;
243 	    repaint();
244 	}
245 	
246 	public Renderer<V,E> getRenderer() {
247 	    return renderer;
248 	}
249 
250     public void setGraphLayout(Layout<V,E> layout) {
251     	Dimension viewSize = getPreferredSize();
252     	if(this.isShowing()) {
253     		viewSize = getSize();
254     	}
255 	    model.setGraphLayout(layout, viewSize);
256     }
257     
258     public void scaleToLayout(ScalingControl scaler) {
259     	Dimension vd = getPreferredSize();
260     	if(this.isShowing()) {
261     		vd = getSize();
262     	}
263 		Dimension ld = getGraphLayout().getSize();
264 		if(vd.equals(ld) == false) {
265 			scaler.scale(this, (float)(vd.getWidth()/ld.getWidth()), new Point2D.Double());
266 		}
267     }
268 	
269 	public Layout<V,E> getGraphLayout() {
270 	        return model.getGraphLayout();
271 	}
272 	
273 	@Override
274     public void setVisible(boolean aFlag) {
275 		super.setVisible(aFlag);
276 		if(aFlag == true) {
277 			Dimension d = this.getSize();
278 			if(d.width <= 0 || d.height <= 0) {
279 				d = this.getPreferredSize();
280 			}
281 			model.getGraphLayout().setSize(d);
282 		}
283 	}
284 
285     public Map<Key, Object> getRenderingHints() {
286         return renderingHints;
287     }
288     
289     public void setRenderingHints(Map<Key, Object> renderingHints) {
290         this.renderingHints = renderingHints;
291     }
292     
293 	@Override
294     protected void paintComponent(Graphics g) {
295         super.paintComponent(g);
296 
297 		Graphics2D g2d = (Graphics2D)g;
298 		if(doubleBuffered) {
299 		    checkOffscreenImage(getSize());
300 			renderGraph(offscreenG2d);
301 		    g2d.drawImage(offscreen, null, 0, 0);
302 		} else {
303 		    renderGraph(g2d);
304 		}
305 	}
306 	
307 	protected void renderGraph(Graphics2D g2d) {
308 	    if(renderContext.getGraphicsContext() == null) {
309 	        renderContext.setGraphicsContext(new GraphicsDecorator(g2d));
310         } else {
311         	renderContext.getGraphicsContext().setDelegate(g2d);
312         }
313         renderContext.setScreenDevice(this);
314 	    Layout<V,E> layout = model.getGraphLayout();
315 
316 		g2d.setRenderingHints(renderingHints);
317 		
318 		// the size of the VisualizationViewer
319 		Dimension d = getSize();
320 		
321 		// clear the offscreen image
322 		g2d.setColor(getBackground());
323 		g2d.fillRect(0,0,d.width,d.height);
324 
325 		AffineTransform oldXform = g2d.getTransform();
326         AffineTransform newXform = new AffineTransform(oldXform);
327         newXform.concatenate(
328         		renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform());
329 		
330         g2d.setTransform(newXform);
331 
332 		// if there are  preRenderers set, paint them
333 		for(Paintable paintable : preRenderers) {
334 
335 		    if(paintable.useTransform()) {
336 		        paintable.paint(g2d);
337 		    } else {
338 		        g2d.setTransform(oldXform);
339 		        paintable.paint(g2d);
340                 g2d.setTransform(newXform);
341 		    }
342 		}
343 		
344         if(layout instanceof Caching) {
345         	((Caching)layout).clear();
346         }
347         
348         renderer.render(renderContext, layout);
349 		
350 		// if there are postRenderers set, do it
351 		for(Paintable paintable : postRenderers) {
352 
353 		    if(paintable.useTransform()) {
354 		        paintable.paint(g2d);
355 		    } else {
356 		        g2d.setTransform(oldXform);
357 		        paintable.paint(g2d);
358                 g2d.setTransform(newXform);
359 		    }
360 		}
361 		g2d.setTransform(oldXform);
362 	}
363 
364 	/**
365 	 * VisualizationListener reacts to changes in the size of the
366 	 * VisualizationViewer. When the size changes, it ensures
367 	 * that the offscreen image is sized properly. 
368 	 * If the layout is locked to this view size, then the layout
369 	 * is also resized to be the same as the view size.
370 	 *
371 	 *
372 	 */
373 	protected class VisualizationListener extends ComponentAdapter {
374 		protected BasicVisualizationServer<V,E> vv;
375 		public VisualizationListener(BasicVisualizationServer<V,E> vv) {
376 			this.vv = vv;
377 		}
378 
379 		/**
380 		 * create a new offscreen image for the graph
381 		 * whenever the window is resied
382 		 */
383 		@Override
384         public void componentResized(ComponentEvent e) {
385 		    Dimension d = vv.getSize();
386 		    if(d.width <= 0 || d.height <= 0) return;
387 		    checkOffscreenImage(d);
388 		    repaint();
389 		}
390 	}
391 
392     public void addPreRenderPaintable(Paintable paintable) {
393         if(preRenderers == null) {
394             preRenderers = new ArrayList<Paintable>();
395         }
396         preRenderers.add(paintable);
397     }
398     
399     public void prependPreRenderPaintable(Paintable paintable) {
400         if(preRenderers == null) {
401             preRenderers = new ArrayList<Paintable>();
402         }
403         preRenderers.add(0,paintable);
404     }
405     
406     public void removePreRenderPaintable(Paintable paintable) {
407         if(preRenderers != null) {
408             preRenderers.remove(paintable);
409         }
410     }
411     
412     public void addPostRenderPaintable(Paintable paintable) {
413         if(postRenderers == null) {
414             postRenderers = new ArrayList<Paintable>();
415         }
416         postRenderers.add(paintable);
417     }
418     
419     public void prependPostRenderPaintable(Paintable paintable) {
420         if(postRenderers == null) {
421             postRenderers = new ArrayList<Paintable>();
422         }
423         postRenderers.add(0,paintable);
424     }
425     
426     public void removePostRenderPaintable(Paintable paintable) {
427         if(postRenderers != null) {
428             postRenderers.remove(paintable);
429         }
430     }
431 
432     public void addChangeListener(ChangeListener l) {
433         changeSupport.addChangeListener(l);
434     }
435     
436     public void removeChangeListener(ChangeListener l) {
437         changeSupport.removeChangeListener(l);
438     }
439     
440     public ChangeListener[] getChangeListeners() {
441         return changeSupport.getChangeListeners();
442     }
443 
444     public void fireStateChanged() {
445         changeSupport.fireStateChanged();
446     }   
447     
448     public PickedState<V> getPickedVertexState() {
449         return pickedVertexState;
450     }
451 
452     public PickedState<E> getPickedEdgeState() {
453         return pickedEdgeState;
454     }
455     
456     public void setPickedVertexState(PickedState<V> pickedVertexState) {
457         if(pickEventListener != null && this.pickedVertexState != null) {
458             this.pickedVertexState.removeItemListener(pickEventListener);
459         }
460         this.pickedVertexState = pickedVertexState;
461         this.renderContext.setPickedVertexState(pickedVertexState);
462         if(pickEventListener == null) {
463             pickEventListener = new ItemListener() {
464 
465                 public void itemStateChanged(ItemEvent e) {
466                     repaint();
467                 }
468             };
469         }
470         pickedVertexState.addItemListener(pickEventListener);
471     }
472     
473     public void setPickedEdgeState(PickedState<E> pickedEdgeState) {
474         if(pickEventListener != null && this.pickedEdgeState != null) {
475             this.pickedEdgeState.removeItemListener(pickEventListener);
476         }
477         this.pickedEdgeState = pickedEdgeState;
478         this.renderContext.setPickedEdgeState(pickedEdgeState);
479         if(pickEventListener == null) {
480             pickEventListener = new ItemListener() {
481 
482                 public void itemStateChanged(ItemEvent e) {
483                     repaint();
484                 }
485             };
486         }
487         pickedEdgeState.addItemListener(pickEventListener);
488     }
489     
490     public GraphElementAccessor<V,E> getPickSupport() {
491         return renderContext.getPickSupport();
492     }
493 
494     public void setPickSupport(GraphElementAccessor<V,E> pickSupport) {
495         renderContext.setPickSupport(pickSupport);
496     }
497     
498     public Point2D getCenter() {
499         Dimension d = getSize();
500         return new Point2D.Float(d.width/2, d.height/2);
501     }
502 
503     public RenderContext<V,E> getRenderContext() {
504         return renderContext;
505     }
506 
507     public void setRenderContext(RenderContext<V,E> renderContext) {
508         this.renderContext = renderContext;
509     }
510 }