package edu.brown.gui.common; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Shape; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.Point2D; import java.lang.reflect.Method; import java.util.concurrent.CountDownLatch; import javax.swing.JFrame; import org.apache.commons.collections15.Transformer; import edu.brown.catalog.conflicts.ConflictGraph; import edu.brown.designer.PartitionTree; import edu.brown.graphs.AbstractDirectedGraph; import edu.brown.markov.MarkovGraph; import edu.brown.utils.CollectionUtil; import edu.brown.utils.EventObservable; import edu.brown.utils.EventObserver; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.algorithms.layout.DAGLayout; import edu.uci.ics.jung.algorithms.layout.FRLayout; import edu.uci.ics.jung.algorithms.layout.KKLayout; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.TreeLayout; import edu.uci.ics.jung.graph.DelegateForest; import edu.uci.ics.jung.graph.Forest; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.control.ViewScalingControl; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.decorators.ToStringLabeller; import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position; import edu.uci.ics.jung.visualization.transform.MutableTransformer; public class GraphVisualizationPanel<V, E> extends VisualizationViewer<V, E> { private static final long serialVersionUID = 1L; public final EventObservable<V> EVENT_SELECT_VERTEX = new EventObservable<V>(); public final EventObservable<E> EVENT_SELECT_EDGE = new EventObservable<E>(); protected final Graph<V, E> graph; DefaultModalGraphMouse<V, E> visualizer_mouse; MouseListener<E> edge_listener; MouseListener<V> vertex_listener; final ViewScalingControl viewScalingControl = new ViewScalingControl(); public static <V, E> EventObserver<V> makeVertexObserver(final Graph<V, E> graph) { return new EventObserver<V>() { @Override public void update(EventObservable<V> o, V v) { System.out.println(v); } }; } /** * VertexFontTransformer * Copied from http://jung.sourceforge.net/site/jung-samples/ * @param <V> */ public final static class VertexFontTransformer<V> implements Transformer<V, Font> { private boolean bold; private final Font f = new Font("Helvetica", Font.PLAIN, 12); private final Font b = new Font("Helvetica", Font.BOLD, 12); public VertexFontTransformer(boolean bold) { this.bold = bold; } public VertexFontTransformer() { this(false); } public void setBold(boolean bold) { this.bold = bold; } public Font transform(V v) { return (bold ? b : f); } } /** * EdgeFontTransformer * Copied from http://jung.sourceforge.net/site/jung-samples/ * @param <E> */ public final static class EdgeFontTransformer<E> implements Transformer<E, Font> { private boolean bold; private final Font f = new Font("Helvetica", Font.PLAIN, 12); private final Font b = new Font("Helvetica", Font.BOLD, 12); public EdgeFontTransformer(boolean bold) { this.bold = bold; } public EdgeFontTransformer() { this(false); } public void setBold(boolean bold) { this.bold = bold; } public Font transform(E e) { return (bold ? b : f); } } public static <V, E> void show(final Graph<V, E> graph) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); Thread t = new Thread() { @SuppressWarnings("unchecked") public void run() { try { GraphVisualizationPanel.createFrame(graph).setVisible(true); } finally { latch.countDown(); } } }; t.setDaemon(true); t.start(); latch.await(); } /** * Convenience method for creating a JFrame that displays the graph * @param <V> * @param <E> * @param graph * @param observers * @return */ public static <V, E> JFrame createFrame(Graph<V, E> graph, EventObserver<V>...observers) { GraphVisualizationPanel<V, E> panel = factory(graph); for (EventObserver<V> eo : observers) panel.EVENT_SELECT_VERTEX.addObserver(eo); // Check for a graph name Class<?> clazz = graph.getClass(); String title = clazz.getCanonicalName(); try { Method handle = clazz.getMethod("getName"); if (handle != null) { title = handle.invoke(graph).toString(); } } catch (Exception ex) { // Ignore anything that gets thrown at us... } JFrame ret = new JFrame(title); ret.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ret.setLayout(new BorderLayout()); ret.setContentPane(panel); ret.setSize(650, 650); return (ret); } public static <V, E> GraphVisualizationPanel<V, E> factory(Graph<V, E> graph, EventObserver<V> v_observer, EventObserver<E> e_observer) { GraphVisualizationPanel<V, E> ret = GraphVisualizationPanel.factory(graph); if (v_observer != null) ret.EVENT_SELECT_VERTEX.addObserver(v_observer); if (e_observer != null) ret.EVENT_SELECT_EDGE.addObserver(e_observer); return (ret); } public static <V, E> GraphVisualizationPanel<V, E> factory(Graph<V, E> graph) { Layout<V, E> layout = null; if (graph instanceof DelegateForest) { layout = new TreeLayout<V, E>((Forest<V, E>) graph); } else if (graph instanceof MarkovGraph){ layout = new FRLayout<V,E>(graph); } else if (graph instanceof ConflictGraph){ layout = new KKLayout<V, E>(graph); } else if (graph instanceof AbstractDirectedGraph) { layout = new DAGLayout<V, E>(graph); } else { layout = new CircleLayout<V, E>(graph); } return (new GraphVisualizationPanel<V, E>(layout, graph)); } private GraphVisualizationPanel(Layout<V, E> layout, Graph<V, E> graph) { super(layout); this.graph = graph; this.init(); } public Graph<V, E> getGraph() { return this.graph; } /** * Map one of our graphs to * @param agraph */ private void init() { this.setBackground(Color.white); this.visualizer_mouse = new DefaultModalGraphMouse<V, E>(); this.visualizer_mouse.setMode(ModalGraphMouse.Mode.TRANSFORMING); // Auto-resize this.addComponentListener(new ComponentAdapter() { private Dimension last_size = null; @Override public void componentResized(ComponentEvent evt) { Dimension new_size = evt.getComponent().getSize(); if (this.last_size == null || !new_size.equals(this.last_size)) { Layout<V, E> layout = GraphVisualizationPanel.this.getGraphLayout(); try { layout.setSize(new_size); GraphVisualizationPanel.this.setGraphLayout(layout); this.last_size = new_size; } catch (UnsupportedOperationException ex) { // Ignore... } } } }); this.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<E>()); this.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<V>()); this.getRenderer().getVertexLabelRenderer().setPosition(Position.S); if (this.graph instanceof PartitionTree) { this.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line<V, E>()); } if (this.graph instanceof AbstractDirectedGraph) { Dimension d = this.getGraphLayout().getSize(); Point2D center = new Point2D.Double(d.width/2, d.height/2); this.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).rotate(-Math.PI, center); } this.setGraphMouse(this.visualizer_mouse); this.edge_listener = new MouseListener<E>(this.getPickedEdgeState()); this.vertex_listener = new MouseListener<V>(this.getPickedVertexState()); this.addGraphMouseListener(this.vertex_listener); this.getPickedVertexState().addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { V vertex = CollectionUtil.first(GraphVisualizationPanel.this.getPickedVertexState().getPicked()); if (vertex != null) GraphVisualizationPanel.this.EVENT_SELECT_VERTEX.notifyObservers(vertex); } }); this.getPickedEdgeState().addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { E edge = CollectionUtil.first(GraphVisualizationPanel.this.getPickedEdgeState().getPicked()); if (edge != null) GraphVisualizationPanel.this.EVENT_SELECT_EDGE.notifyObservers(edge); } }); //this.visualizer.setBorder(BorderFactory.createLineBorder(Color.blue)); this.repaint(); // Zoom in a little bit this.zoom(0.85); } @Override public void setVisible(boolean arg0) { // Hide UnsupportedOperationException try { super.setVisible(arg0); } catch (UnsupportedOperationException ex) { // Ignore... } } /** * * @param scale */ public void zoom(Double scale) { this.viewScalingControl.scale(this, scale.floatValue(), this.getCenter()); } /** * Center the visualization panel on the given vertex * @param vertex */ public void centerVisualization(V vertex) { this.centerVisualization(vertex, false); } public void centerVisualization(V vertex, final boolean immediate) { if (vertex != null) { Layout<V,E> layout = this.getGraphLayout(); Point2D q = layout.transform(vertex); Point2D lvc = this.getRenderContext().getMultiLayerTransformer().inverseTransform(this.getCenter()); final int steps = (immediate ? 1 : 10); final double dx = (lvc.getX() - q.getX()) / steps; final double dy = (lvc.getY() - q.getY()) / steps; new Thread() { public void run() { MutableTransformer transformer = GraphVisualizationPanel.this.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); for (int i = 0; i < steps; i++) { transformer.translate(dx, dy); try { Thread.sleep(100); } catch (InterruptedException ex) { } } } }.start(); } } /** * Return the position of the given vertex on the canvas * @param vertex * @return */ public Point2D getPosition(V vertex) { Point2D pos = null; if (vertex != null) { Layout<V,E> layout = this.getGraphLayout(); pos = layout.transform(vertex); // MutableTransformer transformer = GraphVisualizationPanel.this.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); // pos = transformer.inverseTransform(pos); } return (pos); } public Point2D transform(Point2D p) { MutableTransformer transformer = GraphVisualizationPanel.this.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); return (transformer.transform(p)); } public Shape transform(Shape s) { MutableTransformer transformer = GraphVisualizationPanel.this.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT); return (transformer.transform(s)); } public void selectVertex(V vertex) { this.vertex_listener.graphClicked(vertex, null); this.EVENT_SELECT_VERTEX.notifyObservers(vertex); } public void selectEdge(E edge) { this.edge_listener.graphClicked(edge, null); this.EVENT_SELECT_EDGE.notifyObservers(edge); } }