/* * TouchGraph LLC. Apache-Style Software License * * * Copyright (c) 2001-2002 Alexander Shapiro. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by * TouchGraph LLC (http://www.touchgraph.com/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "TouchGraph" or "TouchGraph LLC" must not be used to endorse * or promote products derived from this software without prior written * permission. For written permission, please contact * alex@touchgraph.com * * 5. Products derived from this software may not be called "TouchGraph", * nor may "TouchGraph" appear in their name, without prior written * permission of alex@touchgraph.com. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL TOUCHGRAPH OR ITS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * */ package com.touchgraph.graphlayout; import com.touchgraph.graphlayout.graphelements.*; import java.util.*; /** LocalityUtils: Utilities for switching locality. Animation effects * require a reference to TGPanel. * * @author Alexander Shapiro * @version 1.21 $Id: LocalityUtils.java,v 1.1.1.1 2007/01/07 07:58:19 andrew Exp $ */ public class LocalityUtils { TGPanel tgPanel; Locality locality; public static final int INFINITE_LOCALITY_RADIUS = Integer.MAX_VALUE; ShiftLocaleThread shiftLocaleThread; boolean fastFinishShift=false; // If finish fast is true, quickly wrap up animation public LocalityUtils(Locality loc, TGPanel tgp) { locality = loc; tgPanel = tgp; } public void fastFinishAnimation() { fastFinishShift = true; } /** Mark for deletion nodes not contained within distHash. */ private synchronized boolean markDistantNodes(final Collection subgraph) { final boolean[] someNodeWasMarked = new boolean[1]; someNodeWasMarked[0] = false; Boolean x; TGForEachNode fen = new TGForEachNode() { public void forEachNode(Node n) { if(!subgraph.contains(n)) { n.markedForRemoval=true; someNodeWasMarked[0] = true; } } }; locality.forAllNodes(fen); return someNodeWasMarked[0]; } private synchronized void removeMarkedNodes() { final Vector nodesToRemove = new Vector(); TGForEachNode fen = new TGForEachNode() { public void forEachNode(Node n) { if(n.markedForRemoval) { nodesToRemove.addElement(n); n.markedForRemoval=false; } } }; synchronized(locality) { locality.forAllNodes(fen); locality.removeNodes(nodesToRemove); } } /** Add to locale nodes within radius distance of a focal node. */ private synchronized void addNearNodes(Hashtable distHash, int radius) throws TGException { for ( int r=0; r<radius+1; r++ ) { Enumeration localNodes = distHash.keys(); while (localNodes.hasMoreElements()) { Node n = (Node)localNodes.nextElement(); if(!locality.contains(n) && ((Integer)distHash.get(n)).intValue()<=r) { n.justMadeLocal = true; locality.addNodeWithEdges(n); if (!fastFinishShift) { try { Thread.currentThread().sleep(50); } catch (InterruptedException ex) {} } } } } } private synchronized void unmarkNewAdditions() { TGForEachNode fen = new TGForEachNode() { public void forEachNode(Node n) { n.justMadeLocal=false; } }; locality.forAllNodes(fen); } /** The thread that gets instantiated for doing the locality shift animation. */ class ShiftLocaleThread extends Thread { Hashtable distHash; Node focusNode; int radius; int maxAddEdgeCount; int maxExpandEdgeCount; boolean unidirectional; ShiftLocaleThread(Node n, int r, int maec, int meec, boolean unid) { focusNode = n; radius = r; maxAddEdgeCount = maec; maxExpandEdgeCount = meec; unidirectional = unid; start(); } public void run() { synchronized (LocalityUtils.this) { //Make sure node hasn't been deleted if (!locality.getCompleteEltSet().contains(focusNode)) return; tgPanel.stopDamper(); distHash = GESUtils.calculateDistances( locality.getCompleteEltSet(),focusNode,radius,maxAddEdgeCount,maxExpandEdgeCount,unidirectional); try { if (markDistantNodes(distHash.keySet())) { for (int i=0;i<5&&!fastFinishShift;i++) { Thread.currentThread().sleep(100); } } removeMarkedNodes(); for (int i=0;i<1&&!fastFinishShift;i++) { Thread.currentThread().sleep(100); } addNearNodes(distHash,radius); for (int i=0;i<4&&!fastFinishShift;i++) { Thread.currentThread().sleep(100); } unmarkNewAdditions(); } catch ( TGException tge ) { System.err.println("TGException: " + tge.getMessage()); } catch (InterruptedException ex) {} tgPanel.resetDamper(); } } } public void setLocale(Node n, final int radius, final int maxAddEdgeCount, final int maxExpandEdgeCount, final boolean unidirectional) throws TGException { if (n==null || radius<0) return; if(shiftLocaleThread!=null && shiftLocaleThread.isAlive()) { fastFinishShift=true; //This should cause last locale shift to finish quickly while(shiftLocaleThread.isAlive()) try { Thread.currentThread().sleep(100); } catch (InterruptedException ex) {} } if (radius == INFINITE_LOCALITY_RADIUS || n==null) { addAllGraphElts(); tgPanel.resetDamper(); return; } fastFinishShift=false; shiftLocaleThread=new ShiftLocaleThread(n, radius, maxAddEdgeCount, maxExpandEdgeCount, unidirectional); } public void setLocale(Node n, final int radius) throws TGException { setLocale(n,radius,1000,1000, false); } public synchronized void addAllGraphElts() throws TGException { locality.addAll(); } /** Add to locale nodes that are one edge away from a given node. * This method does not utilize "fastFinishShift" so it's likely that * synchronization errors will occur. */ public void expandNode(final Node n) { new Thread() { public void run() { synchronized (LocalityUtils.this) { if (!locality.getCompleteEltSet().contains(n)) return; tgPanel.stopDamper(); for(int i=0;i<n.edgeCount();i++) { Node newNode = n.edgeAt(i).getOtherEndpt(n); if (!locality.contains(newNode)) { newNode.justMadeLocal = true; try { locality.addNodeWithEdges(newNode); Thread.currentThread().sleep(50); } catch ( TGException tge ) { System.err.println("TGException: " + tge.getMessage()); } catch ( InterruptedException ex ) {} } else if (!locality.contains(n.edgeAt(i))) { locality.addEdge(n.edgeAt(i)); } } try { Thread.currentThread().sleep(200); } catch (InterruptedException ex) {} unmarkNewAdditions(); tgPanel.resetDamper(); } } }.start(); } /** Hides a node, and all the nodes attached to it. */ public synchronized void hideNode( final Node hideNode ) { if (hideNode==null) return; new Thread() { public void run() { synchronized(LocalityUtils.this) { if (!locality.getCompleteEltSet().contains(hideNode)) return; locality.removeNode(hideNode); //Necessary so that node is ignored in distances calculation. if (hideNode==tgPanel.getSelect()) { tgPanel.clearSelect(); } Collection subgraph = GESUtils.getLargestConnectedSubgraph(locality); markDistantNodes(subgraph); tgPanel.repaint(); try { Thread.currentThread().sleep(200); } catch (InterruptedException ex) {} removeMarkedNodes(); tgPanel.resetDamper(); } } }.start(); } /** Opposite of expand node, works like hide node except that the selected node is not hidden.*/ public synchronized void collapseNode( final Node collapseNode ) { if (collapseNode==null) return; new Thread() { public void run() { synchronized(LocalityUtils.this) { if (!locality.getCompleteEltSet().contains(collapseNode)) return; locality.removeNode(collapseNode); //Necessary so that node is ignored in distances calculation. Collection subgraph = GESUtils.getLargestConnectedSubgraph(locality); markDistantNodes(subgraph); try { locality.addNodeWithEdges(collapseNode); // Add the collapsed node back in. } catch (TGException tge) { tge.printStackTrace(); } tgPanel.repaint(); try { Thread.currentThread().sleep(200); } catch (InterruptedException ex) {} removeMarkedNodes(); tgPanel.resetDamper(); } } }.start(); } } // end com.touchgraph.graphlayout.LocalityUtils