edu.uci.ics.jung.visualization.BasicVisualizationServer Maven / Gradle / Ivy
/*
\* Copyright (c) 2003, The JUNG Authors
*
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
*/
package edu.uci.ics.jung.visualization;
import static edu.uci.ics.jung.visualization.MultiLayerTransformer.Layer;
import com.google.common.collect.Lists;
import com.google.common.graph.Network;
import edu.uci.ics.jung.layout.algorithms.LayoutAlgorithm;
import edu.uci.ics.jung.layout.model.LayoutModel;
import edu.uci.ics.jung.layout.util.Caching;
import edu.uci.ics.jung.layout.util.LayoutChangeListener;
import edu.uci.ics.jung.layout.util.LayoutEvent;
import edu.uci.ics.jung.layout.util.LayoutEventSupport;
import edu.uci.ics.jung.layout.util.LayoutNetworkEvent;
import edu.uci.ics.jung.visualization.annotations.AnnotationPaintable;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.control.TransformSupport;
import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintFunction;
import edu.uci.ics.jung.visualization.decorators.PickableNodePaintFunction;
import edu.uci.ics.jung.visualization.layout.BoundingRectangleCollector;
import edu.uci.ics.jung.visualization.layout.NetworkElementAccessor;
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.properties.VisualizationViewerUI;
import edu.uci.ics.jung.visualization.renderers.BasicRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.spatial.Spatial;
import edu.uci.ics.jung.visualization.spatial.SpatialRTree;
import edu.uci.ics.jung.visualization.spatial.rtree.QuadraticLeafSplitter;
import edu.uci.ics.jung.visualization.spatial.rtree.QuadraticSplitter;
import edu.uci.ics.jung.visualization.spatial.rtree.RStarLeafSplitter;
import edu.uci.ics.jung.visualization.spatial.rtree.RStarSplitter;
import edu.uci.ics.jung.visualization.spatial.rtree.SplitterContext;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
import java.awt.*;
import java.awt.RenderingHints.Key;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 extends JPanel
implements ChangeListener,
ChangeEventSupport,
VisualizationServer,
LayoutChangeListener {
static Logger log = LoggerFactory.getLogger(BasicVisualizationServer.class);
protected ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this);
/** holds the state of this View */
protected VisualizationModel model;
/** handles the actual drawing of graph elements */
protected Renderer renderer;
/** rendering hints used in drawing. Anti-aliasing is on by default */
protected Map renderingHints = new HashMap();
/** holds the state of which nodes of the graph are currently 'picked' */
protected PickedState pickedNodeState;
/** holds the state of which edges of the graph are currently 'picked' */
protected PickedState 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 preRenderers = new ArrayList<>();
/**
* a collection of user-implementable functions to render over the topology (after the graph is
* rendered)
*/
protected List postRenderers = new ArrayList<>();
protected RenderContext renderContext;
protected TransformSupport transformSupport = new TransformSupport();
protected Spatial nodeSpatial;
protected Spatial edgeSpatial;
/**
* @param network the network to render
* @param layoutAlgorithm the algorithm to apply
* @param preferredSize the size of the graph area
*/
public BasicVisualizationServer(
Network network, LayoutAlgorithm layoutAlgorithm, Dimension preferredSize) {
this(new BaseVisualizationModel(network, layoutAlgorithm, preferredSize), preferredSize);
}
/**
* Create an instance with the specified model and view dimension.
*
* @param model the model to use
* @param preferredSize initial preferred layoutSize of the view
*/
public BasicVisualizationServer(VisualizationModel model, Dimension preferredSize) {
this.model = model;
renderContext = new PluggableRenderContext<>(model.getNetwork());
renderer = new BasicRenderer<>();
createSpatialStuctures(model, renderContext);
model.addChangeListener(this);
model.addLayoutChangeListener(this);
setDoubleBuffered(false);
this.addComponentListener(new VisualizationListener(this));
setPickSupport(new ShapePickSupport<>(this));
setPickedNodeState(new MultiPickedState<>());
setPickedEdgeState(new MultiPickedState<>());
renderContext.setEdgeDrawPaintFunction(
new PickableEdgePaintFunction<>(getPickedEdgeState(), Color.black, Color.cyan));
renderContext.setNodeFillPaintFunction(
new PickableNodePaintFunction<>(getPickedNodeState(), Color.red, Color.yellow));
setPreferredSize(preferredSize);
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
renderContext.getMultiLayerTransformer().addChangeListener(this);
try {
VisualizationViewerUI.getInstance(this).parse();
} catch (IOException e) {
log.debug("Unable to read property files. Using defaults.");
}
}
private void createSpatialStuctures(VisualizationModel model, RenderContext renderContext) {
setNodeSpatial(
new SpatialRTree.Nodes(
model,
new BoundingRectangleCollector.Nodes<>(renderContext, model),
SplitterContext.of(new RStarLeafSplitter(), new RStarSplitter())));
setEdgeSpatial(
new SpatialRTree.Edges<>(
model,
new BoundingRectangleCollector.Edges(renderContext, model),
SplitterContext.of(new QuadraticLeafSplitter<>(), new QuadraticSplitter<>())));
}
public Spatial getNodeSpatial() {
return nodeSpatial;
}
public void setNodeSpatial(Spatial spatial) {
if (this.nodeSpatial != null) {
disconnectListeners(this.nodeSpatial);
}
this.nodeSpatial = spatial;
boolean layoutModelRelaxing = model.getLayoutModel().isRelaxing();
nodeSpatial.setActive(!layoutModelRelaxing);
if (!layoutModelRelaxing) {
nodeSpatial.recalculate();
}
connectListeners(spatial);
}
public Spatial getEdgeSpatial() {
return edgeSpatial;
}
public void setEdgeSpatial(Spatial spatial) {
if (this.edgeSpatial != null) {
disconnectListeners(this.edgeSpatial);
}
this.edgeSpatial = spatial;
boolean layoutModelRelaxing = model.getLayoutModel().isRelaxing();
edgeSpatial.setActive(!layoutModelRelaxing);
if (!layoutModelRelaxing) {
edgeSpatial.recalculate();
}
connectListeners(edgeSpatial);
}
/**
* hook up events so that when the VisualizationModel gets an event from the LayoutModel and fires
* it, the Spatial will get the same event and know to update or recalculate its space
*
* @param spatial
*/
private void connectListeners(Spatial> spatial) {
if (model instanceof LayoutEventSupport && spatial instanceof LayoutChangeListener) {
if (spatial instanceof LayoutChangeListener) {
model.addLayoutChangeListener((LayoutChangeListener) spatial);
}
}
// this one toggles active/inactive as the opposite of the LayoutModel's active/inactive state
model.getLayoutModel().getLayoutStateChangeSupport().addLayoutStateChangeListener(spatial);
}
/**
* disconnect listeners that will no longer be used
*
* @param spatial
*/
private void disconnectListeners(Spatial> spatial) {
if (model instanceof LayoutEventSupport) {
if (spatial instanceof LayoutChangeListener) {
model.removeLayoutChangeListener((LayoutChangeListener) spatial);
}
}
if (model.getLayoutModel() instanceof LayoutEventSupport) {
((LayoutEventSupport) model.getLayoutModel())
.removeLayoutChangeListener((LayoutChangeListener) spatial);
}
if (model.getLayoutModel() instanceof LayoutModel.ChangeSupport) {
if (spatial instanceof LayoutModel.ChangeListener) {
((LayoutModel.ChangeSupport) model.getLayoutModel())
.removeChangeListener((LayoutModel.ChangeListener) spatial);
}
}
model.getLayoutModel().getLayoutStateChangeSupport().removeLayoutStateChangeListener(spatial);
}
@Override
public void setDoubleBuffered(boolean doubleBuffered) {
this.doubleBuffered = doubleBuffered;
}
@Override
public boolean isDoubleBuffered() {
return doubleBuffered;
}
/**
* Always sanity-check getLayoutSize 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 layoutSize.
*
* @param d the expected Dimension of the offscreen buffer
*/
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();
}
}
}
public VisualizationModel getModel() {
return model;
}
public void setModel(VisualizationModel model) {
this.model = model;
}
public void stateChanged(ChangeEvent e) {
repaint();
fireStateChanged();
}
public void setRenderer(Renderer r) {
this.renderer = r;
repaint();
}
public Renderer getRenderer() {
return renderer;
}
public void scaleToLayout(ScalingControl scaler) {
Dimension vd = getPreferredSize();
if (this.isShowing()) {
vd = getSize();
}
Dimension ld = model.getLayoutSize();
if (vd.equals(ld) == false) {
scaler.scale(this, (float) (vd.getWidth() / ld.getWidth()), new Point2D.Double());
}
}
public Map getRenderingHints() {
return renderingHints;
}
public void setRenderingHints(Map 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);
}
}
public Shape viewOnLayout() {
Dimension d = this.getSize();
MultiLayerTransformer vt = renderContext.getMultiLayerTransformer();
Shape s = new Rectangle2D.Double(0, 0, d.width, d.height);
return vt.inverseTransform(s);
}
protected void renderGraph(Graphics2D g2d) {
if (renderContext.getGraphicsContext() == null) {
renderContext.setGraphicsContext(new GraphicsDecorator(g2d));
} else {
renderContext.getGraphicsContext().setDelegate(g2d);
}
renderContext.setScreenDevice(this);
g2d.setRenderingHints(renderingHints);
// the layoutSize 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());
g2d.setTransform(newXform);
AnnotationPaintable lowerAnnotationPaintable = null;
if (log.isTraceEnabled()) {
// when logging is set to trace, the grid will be drawn on the graph visualization
addSpatialAnnotations(this.nodeSpatial, Color.blue);
addSpatialAnnotations(this.edgeSpatial, Color.green);
} else {
removeSpatialAnnotations();
}
// 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 (lowerAnnotationPaintable != null) {
this.removePreRenderPaintable(lowerAnnotationPaintable);
}
if (model instanceof Caching) {
((Caching) model).clear();
}
renderer.render(renderContext, model, nodeSpatial, edgeSpatial);
// 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);
}
@Override
public void layoutChanged(LayoutEvent evt) {
repaint();
}
@Override
public void layoutChanged(LayoutNetworkEvent evt) {
repaint();
}
/**
* VisualizationListener reacts to changes in the layoutSize of the VisualizationViewer. When the
* layoutSize changes, it ensures that the offscreen image is sized properly. If the layout is
* locked to this view layoutSize, then the layout is also resized to be the same as the view
* layoutSize.
*/
protected class VisualizationListener extends ComponentAdapter {
protected BasicVisualizationServer vv;
public VisualizationListener(BasicVisualizationServer 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<>();
}
preRenderers.add(paintable);
}
public void prependPreRenderPaintable(Paintable paintable) {
if (preRenderers == null) {
preRenderers = new ArrayList<>();
}
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<>();
}
postRenderers.add(paintable);
}
public void prependPostRenderPaintable(Paintable paintable) {
if (postRenderers == null) {
postRenderers = new ArrayList<>();
}
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 getPickedNodeState() {
return pickedNodeState;
}
public PickedState getPickedEdgeState() {
return pickedEdgeState;
}
public void setPickedNodeState(PickedState pickedNodeState) {
if (pickEventListener != null && this.pickedNodeState != null) {
this.pickedNodeState.removeItemListener(pickEventListener);
}
this.pickedNodeState = pickedNodeState;
this.renderContext.setPickedNodeState(pickedNodeState);
if (pickEventListener == null) {
pickEventListener = e -> repaint();
}
pickedNodeState.addItemListener(pickEventListener);
}
public void setPickedEdgeState(PickedState pickedEdgeState) {
if (pickEventListener != null && this.pickedEdgeState != null) {
this.pickedEdgeState.removeItemListener(pickEventListener);
}
this.pickedEdgeState = pickedEdgeState;
this.renderContext.setPickedEdgeState(pickedEdgeState);
if (pickEventListener == null) {
pickEventListener = e -> repaint();
}
pickedEdgeState.addItemListener(pickEventListener);
}
public NetworkElementAccessor getPickSupport() {
return renderContext.getPickSupport();
}
public void setPickSupport(NetworkElementAccessor pickSupport) {
renderContext.setPickSupport(pickSupport);
}
public Point2D getCenter() {
Dimension d = getSize();
return new Point2D.Double(d.width / 2, d.height / 2);
}
public RenderContext getRenderContext() {
return renderContext;
}
public void setRenderContext(RenderContext renderContext) {
this.renderContext = renderContext;
}
private void addSpatialAnnotations(Spatial spatial, Color color) {
if (spatial != null) {
addPreRenderPaintable(new SpatialPaintable(spatial, color));
}
}
private void removeSpatialAnnotations() {
for (Iterator iterator = preRenderers.iterator(); iterator.hasNext(); ) {
Paintable paintable = iterator.next();
if (paintable instanceof BasicVisualizationServer.SpatialPaintable) {
iterator.remove();
}
}
}
public TransformSupport getTransformSupport() {
return transformSupport;
}
public void setTransformSupport(TransformSupport transformSupport) {
this.transformSupport = transformSupport;
}
class SpatialPaintable implements VisualizationServer.Paintable {
Spatial quadTree;
Color color;
public SpatialPaintable(Spatial quadTree, Color color) {
this.quadTree = quadTree;
this.color = color;
}
public boolean useTransform() {
return false;
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Color oldColor = g2d.getColor();
// gather all the grid shapes
List grid = Lists.newArrayList();
grid = quadTree.getGrid();
g2d.setColor(color);
for (Shape r : grid) {
Shape shape = transformSupport.transform(BasicVisualizationServer.this, r);
g2d.draw(shape);
}
g2d.setColor(Color.red);
for (Shape pickShape : quadTree.getPickShapes()) {
if (pickShape != null) {
Shape shape = transformSupport.transform(BasicVisualizationServer.this, pickShape);
g2d.draw(shape);
}
}
g2d.setColor(oldColor);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy