/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.visualization;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
import edu.uci.ics.jung.visualization.picking.MultiPickedState;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.picking.ShapePickSupport;
import edu.uci.ics.jung.visualization.renderers.BasicRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import edu.uci.ics.jung.visualization.util.Caching;
import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
/**
* A class that maintains many of the details necessary for creating
* visualizations of graphs.
* This is the old VisualizationViewer without tooltips and mouse behaviors. Its purpose is
* to be a base class that can also be used on the server side of a multi-tiered application.
*
* @author Joshua O'Madadhain
* @author Tom Nelson
* @author Danyel Fisher
*/
@SuppressWarnings("serial")
public class BasicVisualizationServer<V, E> extends JPanel
implements ChangeListener, ChangeEventSupport, VisualizationServer<V, E>{
protected ChangeEventSupport changeSupport =
new DefaultChangeEventSupport(this);
/**
* holds the state of this View
*/
protected VisualizationModel<V,E> model;
/**
* handles the actual drawing of graph elements
*/
protected Renderer<V,E> renderer = new BasicRenderer<V,E>();
/**
* rendering hints used in drawing. Anti-aliasing is on
* by default
*/
protected Map<Key, Object> renderingHints = new HashMap<Key, Object>();
/**
* holds the state of which vertices of the graph are
* currently 'picked'
*/
protected PickedState<V> pickedVertexState;
/**
* holds the state of which edges of the graph are
* currently 'picked'
*/
protected PickedState<E> pickedEdgeState;
/**
* a listener used to cause pick events to result in
* repaints, even if they come from another view
*/
protected ItemListener pickEventListener;
/**
* an offscreen image to render the graph
* Used if doubleBuffered is set to true
*/
protected BufferedImage offscreen;
/**
* graphics context for the offscreen image
* Used if doubleBuffered is set to true
*/
protected Graphics2D offscreenG2d;
/**
* user-settable choice to use the offscreen image
* or not. 'false' by default
*/
protected boolean doubleBuffered;
/**
* a collection of user-implementable functions to render under
* the topology (before the graph is rendered)
*/
protected List<Paintable> preRenderers = new ArrayList<Paintable>();
/**
* a collection of user-implementable functions to render over the
* topology (after the graph is rendered)
*/
protected List<Paintable> postRenderers = new ArrayList<Paintable>();
protected RenderContext<V,E> renderContext = new PluggableRenderContext<V,E>();
/**
* Create an instance with passed parameters.
*
* @param layout The Layout to apply, with its associated Graph
* @param renderer The Renderer to draw it with
*/
public BasicVisualizationServer(Layout<V,E> layout) {
this(new DefaultVisualizationModel<V,E>(layout));
}
/**
* Create an instance with passed parameters.
*
* @param layout The Layout to apply, with its associated Graph
* @param renderer The Renderer to draw it with
* @param preferredSize the preferred size of this View
*/
public BasicVisualizationServer(Layout<V,E> layout, Dimension preferredSize) {
this(new DefaultVisualizationModel<V,E>(layout, preferredSize), preferredSize);
}
/**
* Create an instance with passed parameters.
*
* @param model
* @param renderer
*/
public BasicVisualizationServer(VisualizationModel<V,E> model) {
this(model, new Dimension(600,600));
}
/**
* Create an instance with passed parameters.
*
* @param model
* @param renderer
* @param preferredSize initial preferred size of the view
*/
@SuppressWarnings("unchecked")
public BasicVisualizationServer(VisualizationModel<V,E> model,
Dimension preferredSize) {
this.model = model;
// renderContext.setScreenDevice(this);
model.addChangeListener(this);
setDoubleBuffered(false);
this.addComponentListener(new VisualizationListener(this));
setPickSupport(new ShapePickSupport<V,E>(this));
setPickedVertexState(new MultiPickedState<V>());
setPickedEdgeState(new MultiPickedState<E>());
renderContext.setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<E>(getPickedEdgeState(), Color.black, Color.cyan));
renderContext.setVertexFillPaintTransformer(new PickableVertexPaintTransformer<V>(getPickedVertexState(),
Color.red, Color.yellow));
setPreferredSize(preferredSize);
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
renderContext.getMultiLayerTransformer().addChangeListener(this);
}
@Override
public void setDoubleBuffered(boolean doubleBuffered) {
this.doubleBuffered = doubleBuffered;
}
@Override
public boolean isDoubleBuffered() {
return doubleBuffered;
}
/**
* Always sanity-check getSize so that we don't use a
* value that is improbable
* @see java.awt.Component#getSize()
*/
@Override
public Dimension getSize() {
Dimension d = super.getSize();
if(d.width <= 0 || d.height <= 0) {
d = getPreferredSize();
}
return d;
}
/**
* Ensure that, if doubleBuffering is enabled, the offscreen
* image buffer exists and is the correct size.
* @param d
*/
protected void checkOffscreenImage(Dimension d) {
if(doubleBuffered) {
if(offscreen == null || offscreen.getWidth() != d.width || offscreen.getHeight() != d.height) {
offscreen = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
offscreenG2d = offscreen.createGraphics();
}
}
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#getModel()
*/
public VisualizationModel<V,E> getModel() {
return model;
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#setModel(edu.uci.ics.jung.visualization.VisualizationModel)
*/
public void setModel(VisualizationModel<V,E> model) {
this.model = model;
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#stateChanged(javax.swing.event.ChangeEvent)
*/
public void stateChanged(ChangeEvent e) {
repaint();
fireStateChanged();
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#setRenderer(edu.uci.ics.jung.visualization.Renderer)
*/
public void setRenderer(Renderer<V,E> r) {
this.renderer = r;
repaint();
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#getRenderer()
*/
public Renderer<V,E> getRenderer() {
return renderer;
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#setGraphLayout(edu.uci.ics.jung.visualization.layout.Layout)
*/
public void setGraphLayout(Layout<V,E> layout) {
Dimension viewSize = getPreferredSize();
if(this.isShowing()) {
viewSize = getSize();
}
model.setGraphLayout(layout, viewSize);
}
public void scaleToLayout(ScalingControl scaler) {
Dimension vd = getPreferredSize();
if(this.isShowing()) {
vd = getSize();
}
Dimension ld = getGraphLayout().getSize();
if(vd.equals(ld) == false) {
scaler.scale(this, (float)(vd.getWidth()/ld.getWidth()), new Point2D.Double());
}
}
public Layout<V,E> getGraphLayout() {
return model.getGraphLayout();
}
@Override
public void setVisible(boolean aFlag) {
super.setVisible(aFlag);
if(aFlag == true) {
Dimension d = this.getSize();
if(d.width <= 0 || d.height <= 0) {
d = this.getPreferredSize();
}
model.getGraphLayout().setSize(d);
}
}
public Map<Key, Object> getRenderingHints() {
return renderingHints;
}
public void setRenderingHints(Map<Key, Object> renderingHints) {
this.renderingHints = renderingHints;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
if(doubleBuffered) {
checkOffscreenImage(getSize());
renderGraph(offscreenG2d);
g2d.drawImage(offscreen, null, 0, 0);
} else {
renderGraph(g2d);
}
}
protected void renderGraph(Graphics2D g2d) {
if(renderContext.getGraphicsContext() == null) {
renderContext.setGraphicsContext(new GraphicsDecorator(g2d));
} else {
renderContext.getGraphicsContext().setDelegate(g2d);
}
renderContext.setScreenDevice(this);
Layout<V,E> layout = model.getGraphLayout();
g2d.setRenderingHints(renderingHints);
// the size of the VisualizationViewer
Dimension d = getSize();
// clear the offscreen image
g2d.setColor(getBackground());
g2d.fillRect(0,0,d.width,d.height);
AffineTransform oldXform = g2d.getTransform();
AffineTransform newXform = new AffineTransform(oldXform);
newXform.concatenate(
renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform());
// viewTransformer.getTransform());
g2d.setTransform(newXform);
// if there are preRenderers set, paint them
for(Paintable paintable : preRenderers) {
if(paintable.useTransform()) {
paintable.paint(g2d);
} else {
g2d.setTransform(oldXform);
paintable.paint(g2d);
g2d.setTransform(newXform);
}
}
if(layout instanceof Caching) {
((Caching)layout).clear();
}
renderer.render(renderContext, layout);
// if there are postRenderers set, do it
for(Paintable paintable : postRenderers) {
if(paintable.useTransform()) {
paintable.paint(g2d);
} else {
g2d.setTransform(oldXform);
paintable.paint(g2d);
g2d.setTransform(newXform);
}
}
g2d.setTransform(oldXform);
}
/**
* VisualizationListener reacts to changes in the size of the
* VisualizationViewer. When the size changes, it ensures
* that the offscreen image is sized properly.
* If the layout is locked to this view size, then the layout
* is also resized to be the same as the view size.
*
*
*/
protected class VisualizationListener extends ComponentAdapter {
protected BasicVisualizationServer<V,E> vv;
public VisualizationListener(BasicVisualizationServer<V,E> vv) {
this.vv = vv;
}
/**
* create a new offscreen image for the graph
* whenever the window is resied
*/
@Override
public void componentResized(ComponentEvent e) {
Dimension d = vv.getSize();
if(d.width <= 0 || d.height <= 0) return;
checkOffscreenImage(d);
repaint();
}
}
public void addPreRenderPaintable(Paintable paintable) {
if(preRenderers == null) {
preRenderers = new ArrayList<Paintable>();
}
preRenderers.add(paintable);
}
public void prependPreRenderPaintable(Paintable paintable) {
if(preRenderers == null) {
preRenderers = new ArrayList<Paintable>();
}
preRenderers.add(0,paintable);
}
public void removePreRenderPaintable(Paintable paintable) {
if(preRenderers != null) {
preRenderers.remove(paintable);
}
}
public void addPostRenderPaintable(Paintable paintable) {
if(postRenderers == null) {
postRenderers = new ArrayList<Paintable>();
}
postRenderers.add(paintable);
}
public void prependPostRenderPaintable(Paintable paintable) {
if(postRenderers == null) {
postRenderers = new ArrayList<Paintable>();
}
postRenderers.add(0,paintable);
}
public void removePostRenderPaintable(Paintable paintable) {
if(postRenderers != null) {
postRenderers.remove(paintable);
}
}
public void addChangeListener(ChangeListener l) {
changeSupport.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
changeSupport.removeChangeListener(l);
}
public ChangeListener[] getChangeListeners() {
return changeSupport.getChangeListeners();
}
public void fireStateChanged() {
changeSupport.fireStateChanged();
}
public PickedState<V> getPickedVertexState() {
return pickedVertexState;
}
public PickedState<E> getPickedEdgeState() {
return pickedEdgeState;
}
public void setPickedVertexState(PickedState<V> pickedVertexState) {
if(pickEventListener != null && this.pickedVertexState != null) {
this.pickedVertexState.removeItemListener(pickEventListener);
}
this.pickedVertexState = pickedVertexState;
this.renderContext.setPickedVertexState(pickedVertexState);
if(pickEventListener == null) {
pickEventListener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
repaint();
}
};
}
pickedVertexState.addItemListener(pickEventListener);
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#setPickedEdgeState(edu.uci.ics.jung.visualization.picking.PickedState)
*/
public void setPickedEdgeState(PickedState<E> pickedEdgeState) {
if(pickEventListener != null && this.pickedEdgeState != null) {
this.pickedEdgeState.removeItemListener(pickEventListener);
}
this.pickedEdgeState = pickedEdgeState;
this.renderContext.setPickedEdgeState(pickedEdgeState);
if(pickEventListener == null) {
pickEventListener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
repaint();
}
};
}
pickedEdgeState.addItemListener(pickEventListener);
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#getPickSupport()
*/
public GraphElementAccessor<V,E> getPickSupport() {
return renderContext.getPickSupport();
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#setPickSupport(edu.uci.ics.jung.visualization.GraphElementAccessor)
*/
public void setPickSupport(GraphElementAccessor<V,E> pickSupport) {
renderContext.setPickSupport(pickSupport);
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#getCenter()
*/
public Point2D getCenter() {
Dimension d = getSize();
return new Point2D.Float(d.width/2, d.height/2);
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#getRenderContext()
*/
public RenderContext<V,E> getRenderContext() {
return renderContext;
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.VisualizationServer#setRenderContext(edu.uci.ics.jung.visualization.RenderContext)
*/
public void setRenderContext(RenderContext<V,E> renderContext) {
this.renderContext = renderContext;
}
}