/* * Created on Jul 27, 2005 * * TODO To change the template for this generated file go to * Window - Preferences - Java - Code Style - Code Templates */ /** * @author Dave Wang * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ package org.mindswap.swoop.utils.graph.hierarchy; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.mindswap.swoop.SwoopModel; import edu.uci.ics.jung.exceptions.FatalException; import edu.uci.ics.jung.graph.Edge; import edu.uci.ics.jung.graph.Vertex; import edu.uci.ics.jung.graph.impl.DirectedSparseGraph; import edu.uci.ics.jung.graph.impl.DirectedSparseVertex; import edu.uci.ics.jung.utils.PickEventListener; import edu.uci.ics.jung.visualization.GraphMouseListener; import edu.uci.ics.jung.visualization.Layout; import edu.uci.ics.jung.visualization.MultiPickedState; import edu.uci.ics.jung.visualization.PickSupport; import edu.uci.ics.jung.visualization.PickedState; import edu.uci.ics.jung.visualization.Renderer; import edu.uci.ics.jung.visualization.StatusCallback; import edu.uci.ics.jung.visualization.VisualizationViewer; /** * A class that maintains many of the details necessary for creating * visualizations of graphs. * * @author Joshua O'Madadhain * @author Tom Nelson * @author Danyel Fisher */ public class OntologyVisualizationViewer extends VisualizationViewer { //protected StatusCallback statusCallback; Thread relaxer; boolean suspended; boolean manualSuspend; private SwoopModel myModel = null; private OntologyWithClassHierarchyGraph myGraph = null; protected Set previousSelectedNodes = null; // selected nodes (by // mouse/selection list) protected Set previousHighlightedNodes = null; // highlighted nodes (by // search box) protected SwoopOntologyVertex previousSelectedVertex = null; // selected // vertex by // mouse protected ClassTreeNode listBrowsedNode = null; // viewed/browsed in the class list protected ClassTreeNode rightSelectedNode = null; // right clicked node protected ClassTreeNode currentSelectedNode = null; // the node that's selected by user //protected Renderer renderer; //protected Layout layout; //protected ToolTipListener toolTipListener; /** * holds the values for zoom/pan of the display */ //protected AffineTransform transform; /** * the inverse of transform. Used to map window points to graph points. * Lazily created in transform method and reset to null any time transform * is changed. */ //protected AffineTransform inverse; //protected Map renderingHints = new HashMap(); /** * pluggable support for picking graph elements by finding them based on * their coordinates. Typically used in mouse events. */ //protected PickSupport pickSupport; /** * pluggable support for handling the picked/not-picked state of graph * elements. */ //protected PickedState pickedState; /** * an offscreen image to render the graph */ //protected BufferedImage offscreen; /** * graphics context for the offscreen image */ //protected Graphics2D offscreenG2d; /** * 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 long relaxerThreadSleepTime = 20L; /** * The <code>changeListener</code>. */ //protected ChangeListener changeListener; /** * Only one <code>ChangeEvent</code> is needed instance since the event's * only state is the source property. The source of events generated is * always "this". */ //protected transient ChangeEvent changeEvent; //public Object pauseObject = new String("PAUSE OBJECT"); // keeps track of where the current view port is centered at protected double currentX = 0; protected double currentY = 0; // debugging values private boolean DEBUG = false; private double DEBUGX = 0; private double DEBUGY = 0; private double DEBUGW = 0; private double DEBUGH = 0; private double DX = 0; private double DY = 0; /** * The VisualizationViewer constructor creates a JPanel based a given Layout * and Renderer. While GraphDraw places reasonable defaults on these, this * gives more precise control. * * @param layout * The Layout to apply, with its associated Graph * @param renderer * The Renderer to draw it with */ public OntologyVisualizationViewer(SwoopModel model, OntologyWithClassHierarchyGraph graph, Layout layout, Renderer r) { super(layout, r); myModel = model; myGraph = graph; // adding postrenderer (overlaygraph) //postRenderers.add(myGraph.getOverlayGraph()); // code copied from the constructor. setDoubleBuffered( true ); this.transform = new AffineTransform(); this.addComponentListener(new VisualizationListener(this)); this.renderer = r; pickedState = new MultiPickedState(); if (layout instanceof PickEventListener) pickedState.addListener((PickEventListener) layout); r.setPickedKey(pickedState); this.layout = layout; //setPreferredSize(new Dimension(640, 600)); //setSize(640, 600); Dimension d = getPreferredSize(); Dimension ld = layout.getCurrentSize(); // if the layout has NOT been intialized yet, initialize it // now to be the same size as the VisualizationViewer window if (ld == null) { System.out.println("Ont VV constructor: layout not init" ); layout.initialize(d); } ld = layout.getCurrentSize(); if (DEBUG) { System.out.println( " vv preferred size: w=" + d.width + " h=" + d.height); System.out.println( " layout current: w=" + ld.width + " lh=" + d.height); System.out.println( " after init" ); System.out.println( " layout current: w=" + ld.width + " lh=" + d.height); } // set my scale to show the entire layout setScale((float) d.width / ld.width, (float) d.height / ld.height, new Point2D.Float()); this.suspended = true; this.manualSuspend = false; renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); init(); initMouseClicker(); } private Rectangle getViewRectangle() { DirectedSparseGraph graph = myGraph.getVisualGraph(); Set vertices = graph.getVertices(); double xmin = Integer.MAX_VALUE; double xmax = Integer.MIN_VALUE; double ymin = Integer.MAX_VALUE; double ymax = Integer.MIN_VALUE; for (Iterator it = vertices.iterator(); it.hasNext();) { Vertex v = (Vertex) it.next(); //System.out.println("v x,y= " + layout.getX(v) + " " + layout.getY(v)); OntologyGraphNode ontNode = ((OntologyGraphNode) v.getUserDatum(OntologyWithClassHierarchyGraph.DATA)); int r = ontNode.getRadius(); //System.out.println( "radius = " + r); double x = layout.getX(v); double y = layout.getY(v); if ((x - r) < xmin) xmin = x - r; if ((x + r) > xmax) xmax = x + r; if ((y - r) < ymin) ymin = y - r; if ((y + r) > ymax) ymax = y + r; } Rectangle rect = new Rectangle( (int)xmin, (int)ymin, (int)(xmax - xmin), (int)(ymax - ymin)); return rect; } /* * Called by OntologyWithClassHierarchyGraph at init time to zoom in / center at the * graph so every vertex on the graph is in the view port. * */ public void autoPanZoom() { Rectangle rect = getViewRectangle(); int xfocus = (int) ( (rect.width/2 + rect.x) ); int yfocus = (int) ( (rect.height/2 + rect.y) ); // getting the size of the screen space int screenWidth = this.getWidth(); int screenHeight = this.getHeight(); double hw = screenWidth / 2; double hh = screenHeight / 2; Point2D point = transform( new Point2D.Double( screenWidth/2, screenHeight/2) ); Point2D focus = this.transformGraph2Screen( new Point2D.Double( xfocus, yfocus)); if (DEBUG) { System.out.println( " screenWidth = " + screenWidth + " screenHeight = "+ screenHeight ); System.out.println( " xmin = " + rect.x + " ymin = " + rect.y + " xmax " + ( rect.width + rect.x ) + " ymax " + (rect.height + rect.y )); System.out.println( " xfocus = " + xfocus + " yfocus = " + yfocus ); System.out.println( " center of screen: x=" + (screenWidth/2) + " y=" + (screenHeight/2) ); System.out.println( " graphspace of centerscreen: " + point.getX() + " " + point.getY() ); System.out.println( " screensapce: xfocus = " + focus.getX() + " yfocus " + focus.getY() ); System.out.println( " scaling = " + this.getScaleX()); } DX = focus.getX(); DY = focus.getY(); DEBUGX = rect.x; DEBUGY = rect.y; DEBUGW = rect.width ; DEBUGH = rect.height; // compute for the necessary translation offsets to center double translateOffsetX = (point.getX() - xfocus); double translateOffsetY = (point.getY() - yfocus); //double translateOffsetX = 0; //double translateOffsetY = 0; // compute for the appropriate scaling factor for zoom in double min = Math.min(screenHeight, screenWidth); double graphMaxLength = Math.max(( rect.width), (rect.height)); double scaleX = min / (graphMaxLength) / this.getScaleX(); if (scaleX > 1) scaleX = 1; double scaleY = scaleX; // apply translation and zooming this.translate(translateOffsetX, translateOffsetY); this.scale(scaleX, scaleY); // set initial position of where the viewer is viewing this.currentX = xfocus; this.currentY = yfocus; } public void autoPanZoomTest() { Rectangle rect = getViewRectangle(); int xfocus = (int) ( (rect.width/2 + rect.x) ); int yfocus = (int) ( (rect.height/2 + rect.y) ); int screenWidth = this.getWidth(); int screenHeight = this.getHeight(); double hw = screenWidth / 2; double hh = screenHeight / 2; double translateOffsetX = (currentX - xfocus); double translateOffsetY = (currentY - yfocus); double min = Math.min(screenHeight, screenWidth); double graphMaxLength = Math.max(( rect.width ), ( rect.height )); double scaleX = min / (graphMaxLength) / this.getScaleX(); if (scaleX > 1) scaleX = 1; double scaleY = scaleX; this.translate(translateOffsetX, translateOffsetY); this.scale(scaleX, scaleY); } /** * Returns the time between iterations of the Relaxer thread. The Relaxer * thread sleeps for a moment before calling the Layout to update again. * This tells how long the current delay is. The default, 20 milliseconds, * essentially causes the system to run the next iteration with virtually no * pause. * * @return Returns the relaxerThreadSleepTime. */ public long getRelaxerThreadSleepTime() { return relaxerThreadSleepTime; } /** * Sets the relaxerThreadSleepTime. * * @see #getRelaxerThreadSleepTime() * @param relaxerThreadSleepTime * The relaxerThreadSleepTime to set. */ public void setRelaxerThreadSleepTime(long relaxerThreadSleepTime) { this.relaxerThreadSleepTime = relaxerThreadSleepTime; } /** * Creates a default mouseClicker behavior: a default * * @link{GraphMouseImpl} * @deprecated replaced by setGraphMouse() */ protected void initMouseClicker() { // GraphMouseImpl will give original behavior setGraphMouse(new GraphMouseImpl(this)); } /** * a setter for the GraphMouse. This will remove any previous GraphMouse * (including the one that is added in the initMouseClicker method. * * @param graphMouse * new value */ public void setGraphMouse(GraphMouse graphMouse) { MouseListener[] ml = getMouseListeners(); for (int i = 0; i < ml.length; i++) { if (ml[i] instanceof GraphMouse) { removeMouseListener(ml[i]); } } MouseMotionListener[] mml = getMouseMotionListeners(); for (int i = 0; i < mml.length; i++) { if (mml[i] instanceof GraphMouse) { removeMouseMotionListener(mml[i]); } } MouseWheelListener[] mwl = getMouseWheelListeners(); for (int i = 0; i < mwl.length; i++) { if (mwl[i] instanceof GraphMouse) { removeMouseWheelListener(mwl[i]); } } addMouseListener(graphMouse); addMouseMotionListener(graphMouse); addMouseWheelListener(graphMouse); } /** * Sets the showing Renderer to be the input Renderer. Also tells the * Renderer to refer to this visualizationviewer as a PickedKey. (Because * Renderers maintain a small amount of state, such as the PickedKey, it is * important to create a separate instance for each VV instance.) * * @param v */ public void setRenderer(Renderer r) { this.renderer = r; r.setPickedKey(pickedState); repaint(); } /** * Returns the renderer used by this instance. * * @return */ public Renderer getRenderer() { return renderer; } /** * Removes the current graph layout, and adds a new one. */ public void setGraphLayout(Layout layout) { if (this.layout instanceof PickEventListener) pickedState.removeListener((PickEventListener) this.layout); suspend(); Dimension d = getPreferredSize(); Dimension ld = layout.getCurrentSize(); // if the layout has NOT been initialized yet, initialize it // now to the size of the VisualizationViewer window if (ld == null) { layout.initialize(d); } ld = layout.getCurrentSize(); // set scale to show the entire graph layout setScale((float) d.width / ld.width, (float) d.height / ld.height, new Point2D.Float()); this.layout = layout; layout.restart(); prerelax(); unsuspend(); if (layout instanceof PickEventListener) pickedState.addListener((PickEventListener) layout); this.pickSupport.setLayout(layout); } public void setGraph(OntologyWithClassHierarchyGraph graph) { myGraph = graph; } public OntologyWithClassHierarchyGraph getGraph() { return myGraph; } /** * Returns the current graph layout. */ public Layout getGraphLayout() { return layout; } /** * This is the interface for adding a mouse listener. The GEL will be called * back with mouse clicks on vertices. * * @param gel */ public void addGraphMouseListener(GraphMouseListener gel) { addMouseListener(new SwoopMouseListenerTranslator(gel, this)); } /** * starts a visRunner thread without prerelaxing */ public synchronized void restartThreadOnly() { if (visRunnerIsRunning) { throw new FatalException("Can't init while a visrunner is running"); } relaxer = new VisRunner(); relaxer.setPriority(Thread.MIN_PRIORITY); relaxer.start(); } /** * Pre-relaxes and starts a visRunner thread */ public synchronized void init() { if (visRunnerIsRunning) { throw new FatalException("Can't init while a visrunner is running"); } prerelax(); relaxer = new VisRunner(); relaxer.start(); } /** * Restarts layout, then calls init(); */ public synchronized void restart() { if (visRunnerIsRunning) { throw new FatalException( "Can't restart while a visrunner is running"); } layout.restart(); init(); repaint(); } /** * * @see javax.swing.JComponent#setVisible(boolean) */ public void setVisible(boolean aFlag) { super.setVisible(aFlag); layout.resize(this.getSize()); } /** * Runs the visualization forward a few hundred iterations (for half a * second) */ public void prerelax() { suspend(); int i = 0; if (layout.isIncremental()) { // then increment layout for half a second long timeNow = System.currentTimeMillis(); while (System.currentTimeMillis() - timeNow < 500 && !layout.incrementsAreDone()) { i++; layout.advancePositions(); } } unsuspend(); } /** * If the visualization runner is not yet running, kick it off. */ protected synchronized void start() { suspended = false; synchronized (pauseObject) { pauseObject.notifyAll(); } } public synchronized void suspend() { manualSuspend = true; } public synchronized void unsuspend() { manualSuspend = false; synchronized (pauseObject) { pauseObject.notifyAll(); } } /** * @deprecated Use <code>getPickedState.isPicked(e)</code>. */ public boolean isPicked(Vertex v) { return pickedState.isPicked(v); } /** * @deprecated Use <code>getPickedState.isPicked(e)</code>. */ public boolean isPicked(Edge e) { return pickedState.isPicked(e); } /** * @deprecated Use <code>getPickedState.pick(picked, b)</code>. */ protected void pick(Vertex picked, boolean b) { pickedState.pick(picked, b); } long[] relaxTimes = new long[5]; long[] paintTimes = new long[5]; int relaxIndex = 0; int paintIndex = 0; double paintfps, relaxfps; boolean stop = false; boolean visRunnerIsRunning = false; protected class VisRunner extends Thread { public VisRunner() { super("Relaxer Thread"); } public void run() { visRunnerIsRunning = true; try { while (!layout.incrementsAreDone() && !stop) { synchronized (pauseObject) { while ((suspended || manualSuspend) && !stop) { try { pauseObject.wait(); } catch (InterruptedException e) { } } } long start = System.currentTimeMillis(); layout.advancePositions(); long delta = System.currentTimeMillis() - start; if (stop) return; String status = layout.getStatus(); if (statusCallback != null && status != null) { statusCallback.callBack(status); } if (stop) return; relaxTimes[relaxIndex++] = delta; relaxIndex = relaxIndex % relaxTimes.length; relaxfps = average(relaxTimes); if (stop) return; repaint(); if (stop) return; try { sleep(relaxerThreadSleepTime); } catch (InterruptedException ie) { } } } finally { visRunnerIsRunning = false; } } } /** * Returns a flag that says whether the visRunner thread is running. If it * is not, then you may need to restart the thread (with * * @return */ public boolean isVisRunnerRunning() { return visRunnerIsRunning; } /** * setter for the scale fires a PropertyChangeEvent with the * AffineTransforms representing the previous and new values for scale and * offset * * @param scalex * @param scaley */ public void scale(double scalex, double scaley) { scale(scalex, scaley, null); } public void scale(double scalex, double scaley, Point2D from) { if (from == null) { Dimension d = getSize(); from = new Point2D.Float(d.width / 2.f, d.height / 2.f); } AffineTransform xf = AffineTransform.getTranslateInstance(from.getX(), from.getY()); xf.scale(scalex, scaley); xf.translate(-from.getX(), -from.getY()); inverse = null; transform.preConcatenate(xf); fireStateChanged(); repaint(); } public void setScale(double scalex, double scaley) { setScale(scalex, scaley, null); } /** * setter for the scale fires a PropertyChangeEvent with the * AffineTransforms representing the previous and new values for scale and * offset * * @param scalex * @param scaley */ public void setScale(double scalex, double scaley, Point2D from) { if (from == null) { Dimension d = getSize(); from = new Point2D.Float(d.width / 2.f, d.height / 2.f); } inverse = null; transform.setToIdentity(); transform.translate(from.getX(), from.getY()); transform.scale(scalex, scaley); transform.translate(-from.getX(), -from.getY()); fireStateChanged(); repaint(); } /** * getter for scalex * * @return scalex */ public double getScaleX() { return transform.getScaleX(); } /** * getter for scaley * * @return */ public double getScaleY() { return transform.getScaleY(); } /** * getter for offsetx * * @return */ public double getOffsetX() { return getTranslateX(); } public double getTranslateX() { return transform.getTranslateX(); } /** * getter for offsety * * @return */ public double getOffsetY() { return getTranslateY(); } public double getTranslateY() { return transform.getTranslateY(); } /** * set the offset values that will be used in the translation component of * the graph rendering transform. Changes the transform to the identity * transform, then sets the translation conponents to the passed values * Fires a PropertyChangeEvent with the AffineTransforms representing the * previous and new values for the transform * * @param offsetx * @param offsety */ public void setOffset(double offsetx, double offsety) { setTranslate(offsetx, offsety); } public void setTranslate(double tx, double ty) { float scalex = (float) transform.getScaleX(); float scaley = (float) transform.getScaleY(); inverse = null; transform.setTransform(scalex, 0, 0, scaley, tx, ty); fireStateChanged(); repaint(); } public void translate(double offsetx, double offsety) { inverse = null; transform.translate(offsetx, offsety); fireStateChanged(); repaint(); currentX = currentX - offsetx; currentY = currentY - offsety; } /** * Transform the mouse point with the inverse transform of the * VisualizationViewer. This maps from screen coordinates to graph * coordinates. * * @param p * the point to transform (typically, a mouse point) * @return a transformed Point2D */ public Point2D transform(Point2D p) { if (inverse == null) { try { inverse = transform.createInverse(); } catch (NoninvertibleTransformException e) { throw new IllegalArgumentException(e.toString()); } } return inverse.transform(p, null); } public Point2D transformGraph2Screen(Point2D p) { return transform.transform(p, null); } /** * @return Returns the renderingHints. */ public Map getRenderingHints() { return renderingHints; } /** * @param renderingHints * The renderingHints to set. */ public void setRenderingHints(Map renderingHints) { this.renderingHints = renderingHints; } protected synchronized void paintComponent(Graphics g) { start(); super.paintComponent(g); //Graphics2D g2d = (Graphics2D) g; //long tic = System.currentTimeMillis(); renderGraph(); // tw7: removed the following line for optimization // (not really sure what it does anyway) //g2d.drawImage(offscreen, null, 0, 0); //long toc = System.currentTimeMillis(); //System.out.println( "Time taken to render: " + (toc - tic) + " milisecond" ); } protected void renderGraph() { if (offscreenG2d == null) return; offscreenG2d.setRenderingHints(renderingHints); long start = System.currentTimeMillis(); // the size of the VisualizationViewer Dimension d = getSize(); // composites to control layering of the rendering AlphaComposite srcOver = AlphaComposite .getInstance(AlphaComposite.SRC_OVER); AlphaComposite clear = AlphaComposite.getInstance(AlphaComposite.CLEAR); // clear the offscreen image offscreenG2d.setComposite(clear); offscreenG2d.fillRect(0, 0, d.width, d.height); AffineTransform oldXform = offscreenG2d.getTransform(); offscreenG2d.setTransform(transform); offscreenG2d.setComposite(srcOver); // if there are preRenderers set, paint them for (Iterator iterator = preRenderers.iterator(); iterator.hasNext();) { Paintable paintable = (Paintable) iterator.next(); if (paintable.useTransform()) { paintable.paint(offscreenG2d); } else { offscreenG2d.setTransform(oldXform); paintable.paint(offscreenG2d); offscreenG2d.setTransform(transform); } } // paint all the edges for (Iterator iter = layout.getVisibleEdges().iterator(); iter .hasNext();) { Edge e = (Edge) iter.next(); Vertex v1 = (Vertex) e.getEndpoints().getFirst(); Vertex v2 = (Vertex) e.getEndpoints().getSecond(); renderer.paintEdge(offscreenG2d, e, (int) layout.getX(v1), (int) layout.getY(v1), (int) layout.getX(v2), (int) layout .getY(v2)); } // paint all the vertices for (Iterator iter = layout.getVisibleVertices().iterator(); iter .hasNext();) { Vertex v = (Vertex) iter.next(); renderer.paintVertex(offscreenG2d, v, (int) layout.getX(v), (int) layout.getY(v)); } // paint overlay edges and such //OverlayGraph overlayGraph = myGraph.getOverlayGraph(); //overlayGraph.paint( offscreenG2d ); long delta = System.currentTimeMillis() - start; paintTimes[paintIndex++] = delta; paintIndex = paintIndex % paintTimes.length; paintfps = average(paintTimes); // if there are postRenderers set, do it for (Iterator iterator = postRenderers.iterator(); iterator.hasNext();) { Paintable paintable = (Paintable) iterator.next(); if (paintable.useTransform()) { paintable.paint(offscreenG2d); } else { offscreenG2d.setTransform(oldXform); paintable.paint(offscreenG2d); offscreenG2d.setTransform(transform); } } offscreenG2d.setTransform(oldXform); // visual debugging if (DEBUG) { offscreenG2d.setColor( Color.BLACK ); Point2D boxTL = transformGraph2Screen( new Point2D.Double(DEBUGX, DEBUGY) ); Point2D boxBR = transformGraph2Screen( new Point2D.Double(DEBUGX + DEBUGW, DEBUGY + DEBUGH) ); offscreenG2d.drawRect( (int)boxTL.getX(), (int)boxTL.getY(), (int)(boxBR.getX() - boxTL.getX()), (int)(boxBR.getY() - boxTL.getY()) ); offscreenG2d.drawLine( (int)((boxTL.getX()+boxBR.getX())/2), (int)boxTL.getY(), (int)((boxTL.getX()+boxBR.getX())/2), (int)boxBR.getY() ); offscreenG2d.drawLine( (int)boxTL.getX(), (int)((boxTL.getY()+boxBR.getY())/2), (int)boxBR.getX(), (int)((boxTL.getY()+boxBR.getY())/2) ); offscreenG2d.fillOval( (this.getWidth()/2)-5, (this.getHeight()/2)-5, 10, 10 ); offscreenG2d.setColor( Color.RED ); Point2D focus = transformGraph2Screen( new Point2D.Double(DX, DY) ); offscreenG2d.drawRect( (int)(focus.getX()-5), (int)(focus.getY()-5), 10, 10 ); } } /** * Returns the double average of a number of long values. * * @param paintTimes * an array of longs * @return the average of the doubles */ protected double average(long[] paintTimes) { double l = 0; for (int i = 0; i < paintTimes.length; i++) { l += paintTimes[i]; } return l / paintTimes.length; } protected class VisualizationListener extends ComponentAdapter { protected OntologyVisualizationViewer vv; public VisualizationListener(OntologyVisualizationViewer vv) { this.vv = vv; } /** * create a new offscreen image for the graph whenever the window is * resied */ public void componentResized(ComponentEvent e) { Dimension d = vv.getSize(); offscreen = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB); offscreenG2d = offscreen.createGraphics(); } } /** * @param scb */ public void setTextCallback(StatusCallback scb) { this.statusCallback = scb; } /** * */ public synchronized void stop() { manualSuspend = false; suspended = false; stop = true; synchronized (pauseObject) { pauseObject.notifyAll(); } } public void setToolTipListener(ToolTipListener listener) { this.toolTipListener = listener; setToolTipText("VisViewer"); // something to make tool tips happen at // all } public String getToolTipText(MouseEvent event) { if (toolTipListener != null) { return toolTipListener.getToolTipText(event); } else { return getToolTipText(); } } public void resetSelectedNode() { if (previousSelectedNodes != null) { for (Iterator it = previousSelectedNodes.iterator(); it.hasNext();) { ClassTreeNode previousSelectedNode = (ClassTreeNode) it.next(); previousSelectedNode.setIsSelected(false); } } } // used when user traverses through the Class list history public void setSelectedNodeWithoutAddingHistory(ClassTreeNode node) { resetSelectedNode(); // reset selectred nodes resetHighlightedNode(); // reset highlighted nodes resetListBrowsedNode(); // reset listbrowsed node currentSelectedNode = node; // set selected node previousSelectedNodes = new HashSet(); // clean up old vector previousSelectedNodes = node.getOntologyNode().findNodesBy( node.getURI() ); // set new node to be previous // set new nodes to be selected for (Iterator it = previousSelectedNodes.iterator(); it.hasNext();) { ClassTreeNode previousSelectedNode = (ClassTreeNode) it.next(); previousSelectedNode.setIsSelected(true); } } public void setSelectedNode(ClassTreeNode node) { if ( node == currentSelectedNode ) return; // don't the new node is the same as the current one resetSelectedNode(); // reset selectred nodes resetHighlightedNode(); // reset highlighted nodes resetListBrowsedNode(); // reset listbrowsed node previousSelectedNodes = new HashSet(); // clean up old vector currentSelectedNode = node; // set selected node // set new node to be previous DirectedSparseGraph graph = myGraph.getVisualGraph(); Set vertexSet = graph.getVertices(); if (node.getParent() == null) // selected node is a OWL:Thing { OntologyGraphNode graphNode = node.getOntologyNode(); previousSelectedNodes.addAll(graphNode.findNodesBy(node.getURI())); } else { for (Iterator it = vertexSet.iterator(); it.hasNext();) { DirectedSparseVertex vertex = (DirectedSparseVertex) it.next(); OntologyGraphNode graphNode = (OntologyGraphNode) vertex.getUserDatum(OntologyWithClassHierarchyGraph.DATA); previousSelectedNodes.addAll(graphNode.findNodesBy(node.getURI())); } } // set new nodes to be selected for (Iterator it = previousSelectedNodes.iterator(); it.hasNext();) { ClassTreeNode previousSelectedNode = (ClassTreeNode) it.next(); previousSelectedNode.setIsSelected(true); } myGraph.populateClassList(node); // reload classList tree } public void resetHighlightedNode() { if (previousHighlightedNodes != null) { for (Iterator it = previousHighlightedNodes.iterator(); it .hasNext();) { ClassTreeNode previousHighlighteddNode = (ClassTreeNode) it .next(); previousHighlighteddNode.setIsHighlighted(false); } } } public void resetListBrowsedNode() { if (listBrowsedNode != null) listBrowsedNode.setIsListBrowsed(false); } public void setHighlightedNodeWithoutAddingHistory(Set nodes) { resetSelectedNode(); // reset selectred nodes resetHighlightedNode(); // reset highlighted nodes resetListBrowsedNode(); // reset listbrowsed node previousHighlightedNodes = nodes; // clean up old vector for (Iterator it = previousHighlightedNodes.iterator(); it.hasNext();) { ClassTreeNode previousHighlightedNode = (ClassTreeNode) it.next(); previousHighlightedNode.setIsHighlighted(true); } } public void setHighlightedNode(Set nodes, String subText) { resetSelectedNode(); // reset selectred nodes resetHighlightedNode(); // reset highlighted nodes resetListBrowsedNode(); // reset listbrowsed node previousHighlightedNodes = nodes; for (Iterator it = previousHighlightedNodes.iterator(); it.hasNext();) { ClassTreeNode previousHighlightedNode = (ClassTreeNode) it.next(); previousHighlightedNode.setIsHighlighted(true); } myGraph.populateClassListWithSearchTerm(nodes, subText); // reload // classList // tree } public SwoopOntologyVertex getSelectedVertex() { return previousSelectedVertex; } public ClassTreeNode getListBrowsedNode() { return listBrowsedNode; } public void setListBrowsedNode(ClassTreeNode node) { if (listBrowsedNode != null) listBrowsedNode.setIsListBrowsed(false); listBrowsedNode = node; node.setIsListBrowsed(true); } public ClassTreeNode getRightSelectedNode() { return rightSelectedNode; } public ClassTreeNode getCurrentSelectedNode() { return currentSelectedNode; } public boolean isNodeOnScreen( ClassTreeNode targetNode ) { int screenWidth = this.getWidth(); int screenHeight = this.getHeight(); Point2D.Double point = targetNode.getGlobalCenter(); int ulx = (int)(point.x - (targetNode.getRadius() * this.getScaleX()) ); int uly = (int)(point.y - (targetNode.getRadius()* this.getScaleX()) ); int brx = (int)(point.x + (targetNode.getRadius()* this.getScaleX()) ); int bry = (int)(point.y + (targetNode.getRadius()* this.getScaleX()) ); //System.out.println("Parent Center: " + point.x + " " + point.y ); //System.out.println( "(" + ulx + "," + uly + ") , (" + brx + "," + bry + ")" ); if ( (ulx > 0) && (uly > 0) && (brx < screenWidth) && (bry < screenHeight) ) return true; return false; } public void smartPanZoomToNode( ClassTreeNode targetNode, ClassTreeNode topNode ) { int screenWidth = this.getWidth(); int screenHeight = this.getHeight(); Point2D.Double point = targetNode.getGlobalCenter(); if (targetNode == topNode) point = (Point2D.Double) this.transformGraph2Screen(targetNode.getLocalCenter()); if ( ( targetNode.getSubtreeDepth() > 6 ) || (targetNode.getNumChildren() > 75) ) { panZoomToFitNode( targetNode, topNode ); return; } double r = targetNode.getRadius(); double screenR = r * this.getScaleX(); double screenD = 2 * screenR; if ( ((screenD) > screenWidth) && ((screenD) > screenHeight) ) { panZoomToFitNode( targetNode, topNode ); } else if ( screenD < 75 ) { panZoomToFitNode( targetNode, topNode ); } } public void panZoomToFitNode( ClassTreeNode targetNode, ClassTreeNode topNode ) { //System.out.println("targetNode: " + targetNode.getURI() ); //System.out.println("topNode: " + topNode.getURI() ); int screenWidth = this.getWidth(); int screenHeight = this.getHeight(); Point2D.Double point = targetNode.getGlobalCenter(); if (targetNode == topNode) point = (Point2D.Double) this.transformGraph2Screen(targetNode.getLocalCenter()); double r = targetNode.getRadius(); if (targetNode == topNode) r = topNode.getOntologyNode().getRadius(); double min = Math.min(screenHeight, screenWidth); double scaleX = min / (2 * r) / this.getScaleX(); double scaleY = scaleX; double hw = screenWidth / 2; double hh = screenHeight / 2; double translateOffsetX = (hw - point.x)/ this.getScaleX(); double translateOffsetY = (hh - point.y)/ this.getScaleX(); this.translate(translateOffsetX, translateOffsetY); this.scale(scaleX, scaleY); //System.out.println(" translate x, y = " + translateOffsetX + " " + translateOffsetY); //System.out.println(" scalex = " + scaleX ); } /** * an interface for the preRender and postRender */ public interface Paintable { public void paint(Graphics g); public boolean useTransform(); } /** * a convenience type to represent a class that processes all types of mouse * events for the graph */ public interface GraphMouse extends MouseListener, MouseMotionListener, MouseWheelListener { } /** * this is the original GraphMouse class, renamed to use GraphMouse as the * interface name, and updated to correctly apply the vv transform to the * point point * */ protected final class GraphMouseImpl extends MouseAdapter implements GraphMouse { protected Vertex picked; protected OntologyVisualizationViewer myViewer = null; public GraphMouseImpl(OntologyVisualizationViewer v) { myViewer = v; } public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) { if (DEBUG) { System.out.println("mousePressed (screenspace): " + e.getX() + " " + e.getY() ); Point2D gp = transform( new Point2D.Double( e.getX(), e.getY() )); System.out.println("mousePressed (graph space): " + gp.getX() + " " + gp.getY() ); } // find the circle that's picked, if any Point2D p = transform(e.getPoint()); Vertex v = pickSupport.getVertex(p.getX(), p.getY()); picked = v; previousSelectedVertex = (SwoopOntologyVertex) v; OntologyWithClassHierarchyRenderer rend = (OntologyWithClassHierarchyRenderer) renderer; // refit the vertices into the viewport when double-clicked on whitespace if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 2)) { if (!rend.getIsDrawContent() || picked == null) { autoPanZoomTest(); return; } } if (rend.getIsDrawContent()) { // if no vertex selected, we exit if (v == null) { previousSelectedVertex = null; return; } // left click... if (SwingUtilities.isLeftMouseButton(e)) { int shiftmask = MouseEvent.SHIFT_DOWN_MASK; if ((e.getModifiersEx() & shiftmask) == shiftmask) handleRightClick(e, rend, p, v); else { ClassTreeNode topNode = ((OntologyGraphNode) v.getUserDatum(OntologyWithClassHierarchyGraph.DATA)).getTreeNode(); ClassTreeNode selectedNode = topNode.getSelectedChild(p); if (selectedNode == null) // null selectedNode means user clicked on owl:Thing selectedNode = topNode; if (e.getClickCount() == 2) // zoom into the node panZoomToFitNode( selectedNode, topNode ); myViewer.setSelectedNode( selectedNode ); } pick(picked, true); } // right click... else if (SwingUtilities.isRightMouseButton(e)) { handleRightClick(e, rend, p, v); } } repaint(); } private void handleRightClick(MouseEvent e, OntologyWithClassHierarchyRenderer rend, Point2D p, Vertex v) { ClassTreeNode selectedNode = null; if (rend.getIsDrawContent()) { ClassTreeNode topNode = ((OntologyGraphNode) v .getUserDatum(OntologyWithClassHierarchyGraph.DATA)) .getTreeNode(); selectedNode = topNode.getSelectedChild(p); } if (selectedNode == null) myGraph.showOntologyPopupMenu(e, v); else myGraph.showClassPopupMenu(e, selectedNode); rightSelectedNode = selectedNode; } public void mouseReleased(MouseEvent e) { if (picked == null) return; pick(picked, false); picked = null; repaint(); } public void mouseDragged(MouseEvent e) { if (picked == null) return; Point2D p = transform(e.getPoint()); layout.forceMove(picked, (int) p.getX(), (int) p.getY()); repaint(); // drawSpot( e.getX(), e.getY() ); } public void mouseMoved(MouseEvent e) { return; } public void mouseWheelMoved(MouseWheelEvent e) { return; } } /** * @param paintable * The paintable to add. */ public void addPreRenderPaintable(Paintable paintable) { if (preRenderers == null) { preRenderers = new ArrayList(); } preRenderers.add(paintable); } /** * @param paintable * The paintable to remove. */ public void removePreRenderPaintable(Paintable paintable) { if (preRenderers != null) { preRenderers.remove(paintable); } } /** * @param paintable * The paintable to add. */ public void addPostRenderPaintable(Paintable paintable) { if (postRenderers == null) { postRenderers = new ArrayList(); } postRenderers.add(paintable); } /** * @param paintable * The paintable to remove. */ public void removePostRenderPaintable(Paintable paintable) { if (postRenderers != null) { postRenderers.remove(paintable); } } /** * Adds a <code>ChangeListener</code>. * * @param l * the listener to be added */ public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } /** * Removes a ChangeListener. * * @param l * the listener to be removed */ public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } /** * Returns an array of all the <code>ChangeListener</code> s added with * addChangeListener(). * * @return all of the <code>ChangeListener</code> s added or an empty * array if no listeners have been added */ public ChangeListener[] getChangeListeners() { return (ChangeListener[]) (listenerList .getListeners(ChangeListener.class)); } /** * Notifies all listeners that have registered interest for notification on * this event type. The event instance is lazily created. * * @see EventListenerList */ protected void fireStateChanged() { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { // Lazily create the event: if (changeEvent == null) changeEvent = new ChangeEvent(this); ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); } } } /** * @return Returns the pickedState. */ public PickedState getPickedState() { return pickedState; } /** * @param pickedState * The pickedState to set. */ public void setPickedState(PickedState pickedState) { this.pickedState = pickedState; if (layout instanceof PickEventListener) pickedState.addListener((PickEventListener) layout); } /** * @return Returns the pickSupport. */ public PickSupport getPickSupport() { return pickSupport; } /** * @param pickSupport * The pickSupport to set. */ public void setPickSupport(PickSupport pickSupport) { this.pickSupport = pickSupport; } }