View Javadoc

1   /*
2    * TouchGraph LLC. Apache-Style Software License
3    *
4    *
5    * Copyright (c) 2001-2002 Alexander Shapiro. All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer. 
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution,
20   *    if any, must include the following acknowledgment:  
21   *       "This product includes software developed by 
22   *        TouchGraph LLC (http://www.touchgraph.com/)."
23   *    Alternately, this acknowledgment may appear in the software itself,
24   *    if and wherever such third-party acknowledgments normally appear.
25   *
26   * 4. The names "TouchGraph" or "TouchGraph LLC" must not be used to endorse 
27   *    or promote products derived from this software without prior written 
28   *    permission.  For written permission, please contact 
29   *    alex@touchgraph.com
30   *
31   * 5. Products derived from this software may not be called "TouchGraph",
32   *    nor may "TouchGraph" appear in their name, without prior written
33   *    permission of alex@touchgraph.com.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL TOUCHGRAPH OR ITS CONTRIBUTORS BE 
39   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
40   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
41   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
42   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
43   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
44   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
45   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46   * ====================================================================
47   *
48   */
49  
50  package com.touchgraph.graphlayout;
51  
52  import java.awt.*;
53  import java.awt.event.*;
54  import java.util.*;
55  
56  import javax.swing.*;
57  
58  import com.touchgraph.graphlayout.graphelements.*;
59  import com.touchgraph.graphlayout.interaction.*;
60  
61  /*** TGPanel contains code for drawing the graph, and storing which nodes
62    * are selected, and which ones the mouse is over.
63    *
64    * It houses methods to activate TGLayout, which performs dynamic layout.
65    * Whenever the graph is moved, or repainted, TGPanel fires listner
66    * methods on associated objects.
67    *
68    * <p><b>
69    * Parts of this code build upon Sun's Graph Layout example.
70    * http://java.sun.com/applets/jdk/1.1/demo/GraphLayout/Graph.java
71    * </b></p>
72    *
73    * @author   Alexander Shapiro
74    * @author   Murray Altheim  (2001-11-06; 2002-01-14 cleanup)
75    * @version  1.21  $Id: TGPanel.java,v 1.1.1.1 2004/02/06 08:44:05 keesj Exp $
76    */
77  public class TGPanel extends JPanel {
78  
79    // static variables for use within the package
80    
81      public static Color BACK_COLOR = Color.white;
82  
83    // ....
84  
85      private GraphEltSet completeEltSet;
86      private VisibleLocality visibleLocality;
87      private LocalityUtils localityUtils;
88      public TGLayout tgLayout;
89      protected BasicMouseMotionListener basicMML;
90  
91      protected Edge mouseOverE;  //mouseOverE is the edge the mouse is over
92      protected Node mouseOverN;  //mouseOverN is the node the mouse is over
93      protected boolean maintainMouseOver = false; //If true, then don't change mouseOverN or mouseOverE
94  
95      protected Node select;
96  
97      Node dragNode; //Node currently being dragged
98  
99      protected Point mousePos; //Mouse location, updated in the mouseMotionListener
100     Image offscreen;
101     Dimension offscreensize;
102     Graphics offgraphics;
103 
104     private Vector graphListeners;
105     private Vector paintListeners;
106     TGLensSet tgLensSet;  // Converts between a nodes visual position (drawx, drawy),
107                           // and its absolute position (x,y).
108     AdjustOriginLens adjustOriginLens;
109     SwitchSelectUI switchSelectUI;
110 
111   // ............
112 
113    /*** Default constructor.
114      */
115     public TGPanel()
116     {
117         setLayout(null);
118 
119         setGraphEltSet(new GraphEltSet());
120         addMouseListener(new BasicMouseListener());
121         basicMML = new BasicMouseMotionListener();
122         addMouseMotionListener(basicMML);
123 
124         graphListeners=new Vector();
125         paintListeners=new Vector();
126 
127         adjustOriginLens = new AdjustOriginLens();
128         switchSelectUI = new SwitchSelectUI();
129 
130         TGLayout tgLayout = new TGLayout(this);
131         setTGLayout(tgLayout);
132         tgLayout.start();
133         setGraphEltSet(new GraphEltSet());        
134     }
135     
136     public void setLensSet( TGLensSet lensSet ) {
137         tgLensSet = lensSet;
138     }
139 
140     public void setTGLayout( TGLayout tgl ) {
141         tgLayout = tgl;
142     }
143 
144     public void setGraphEltSet( GraphEltSet ges ) {
145         completeEltSet = ges;
146         visibleLocality = new VisibleLocality(completeEltSet);
147         localityUtils = new LocalityUtils(visibleLocality, this);
148     }
149 
150     public AdjustOriginLens getAdjustOriginLens() {
151         return adjustOriginLens;
152     }
153 
154     public SwitchSelectUI getSwitchSelectUI() {
155         return switchSelectUI;
156     }
157 
158   // color and font setters ......................
159 
160     public void setBackColor( Color color ) { BACK_COLOR = color; }
161 
162   // Node manipulation ...........................
163 
164     /*** Returns an Iterator over all nodes in the complete graph. */
165     public Iterator getAllNodes() {
166         return completeEltSet.getNodes();
167     }
168 
169     /*** Return the current visible locality. */
170     public ImmutableGraphEltSet getGES() {
171         return visibleLocality;
172     }
173 
174     /*** Returns the current node count. */
175     public int getNodeCount() {
176         return completeEltSet.nodeCount();
177     }
178 
179     /*** Returns the current node count within the VisibleLocality.
180       * @deprecated        this method has been replaced by the <tt>visibleNodeCount()</tt> method.
181       */
182     public int nodeNum() {
183         return visibleLocality.nodeCount();
184     }
185 
186     /*** Returns the current node count within the VisibleLocality. */
187     public int visibleNodeCount() {
188         return visibleLocality.nodeCount();
189     }
190 
191     /*** Return the Node whose ID matches the String <tt>id</tt>, null if no match is found.
192       *
193       * @param id The ID identifier used as a query.
194       * @return The Node whose ID matches the provided 'id', null if no match is found.
195       */
196     public Node findNode( String id ) {
197         if ( id == null ) return null; // ignore
198         return completeEltSet.findNode(id);
199     }
200     
201     
202     /*** Return a Collection of all Nodes whose label matches the String <tt>label</tt>, 
203       * null if no match is found. */
204     public Collection findNodesByLabel( String label ) {
205         if ( label == null ) return null; // ignore
206         return completeEltSet.findNodesByLabel(label);    
207     }
208 
209     
210    /*** Return the first Nodes whose label contains the String <tt>substring</tt>,      
211      * null if no match is found. 
212      * @param substring The Substring used as a query.
213      */
214     public Node findNodeLabelContaining( String substring ) {
215         if ( substring == null ) return null; //ignore
216         return completeEltSet.findNodeLabelContaining(substring);
217     }
218 
219    /*** Adds a Node, with its ID and label being the current node count plus 1.
220      * @see com.touchgraph.graphlayout.Node
221      */
222     public Node addNode() throws TGException {
223         String id = String.valueOf(getNodeCount()+1);
224         return addNode(id,null);
225     }
226 
227    /*** Adds a Node, provided its label.  The node is assigned a unique ID.
228      * @see com.touchgraph.graphlayout.graphelements.GraphEltSet
229      */
230     public Node addNode( String label ) throws TGException {
231         return addNode(null,label);
232     }
233 
234    /*** Adds a Node, provided its ID and label.
235      * @see com.touchgraph.graphlayout.Node
236      */
237     public Node addNode( String id, String label ) throws TGException {
238         Node node;
239         if (label==null) 
240             node = new Node(id);
241         else
242             node = new Node(id, label);
243 
244         updateDrawPos(node); // The addNode() call should probably take a position, this just sets it at 0,0
245         addNode(node);
246         return node;
247     }
248 
249 
250     /*** Add the Node <tt>node</tt> to the visibleLocality, checking for ID uniqueness. */
251     public void addNode( final Node node ) throws TGException {
252         synchronized (localityUtils) {
253             visibleLocality.addNode(node);
254             resetDamper();
255         }
256     }
257 
258     /*** Remove the Node object matching the ID <code>id</code>, returning true if the
259       * deletion occurred, false if a Node matching the ID does not exist (or if the ID
260       * value was null).
261       *
262       * @param id The ID identifier used as a query.
263       * @return true if the deletion occurred.
264       */
265     public boolean deleteNodeById( String id )
266     {
267         if ( id == null ) return false; // ignore
268         Node node = findNode(id);
269         if ( node == null ) return false;
270         else return deleteNode(node);
271     }
272 
273     public boolean deleteNode( Node node ) {
274         synchronized (localityUtils) {
275             if (visibleLocality.deleteNode(node)) { // delete from visibleLocality, *AND completeEltSet
276                 if ( node == select ) clearSelect();
277                 resetDamper();
278                 return true;
279             }
280             return false;
281         }
282     }
283 
284     public void clearAll() {
285         synchronized (localityUtils) {
286             visibleLocality.clearAll();
287         }
288     }
289 
290     public Node getSelect() {
291         return select;
292     }
293 
294     public Node getMouseOverN() {
295         return mouseOverN;
296     }
297 
298     public synchronized void setMouseOverN( Node node ) {
299         if ( dragNode != null || maintainMouseOver ) return;  // So you don't accidentally switch nodes while dragging
300         if ( mouseOverN != node ) {
301             Node oldMouseOverN = mouseOverN;
302             mouseOverN=node;
303         }
304     }
305 
306   // Edge manipulation ...........................
307 
308     /*** Returns an Iterator over all edges in the complete graph. */
309     public Iterator getAllEdges() {
310         return completeEltSet.getEdges();
311     }
312 
313     public void deleteEdge( Edge edge ) {
314         synchronized (localityUtils) {
315             visibleLocality.deleteEdge(edge);
316             resetDamper();
317         }
318     }
319 
320     public void deleteEdge( Node from, Node to ) {
321         synchronized (localityUtils) {
322             visibleLocality.deleteEdge(from,to);
323         }   
324     }
325 
326     /*** Returns the current edge count in the complete graph. 
327       */
328     public int getEdgeCount() {
329         return completeEltSet.edgeCount();
330     }
331 
332     /*** Return the number of Edges in the Locality. 
333       * @deprecated        this method has been replaced by the <tt>visibleEdgeCount()</tt> method.
334       */
335     public int edgeNum() {
336         return visibleLocality.edgeCount();
337     }
338 
339     /*** Return the number of Edges in the Locality. 
340       */
341     public int visibleEdgeCount() {
342         return visibleLocality.edgeCount();
343     }
344 
345     public Edge findEdge( Node f, Node t ) {
346         return visibleLocality.findEdge(f, t);
347     }
348 
349     public void addEdge(Edge e) {
350         synchronized (localityUtils) {
351             visibleLocality.addEdge(e); resetDamper();
352         }
353     }
354 
355     public Edge addEdge( Node f, Node t, int tens ) {
356         synchronized (localityUtils) {
357             return visibleLocality.addEdge(f, t, tens);
358         }   
359     }
360 
361     public Edge getMouseOverE() {
362         return mouseOverE;
363     }
364 
365     public synchronized void setMouseOverE( Edge edge ) {
366         if ( dragNode != null || maintainMouseOver ) return; // No funny business while dragging
367         if ( mouseOverE != edge ) {
368             Edge oldMouseOverE = mouseOverE;
369             mouseOverE = edge;
370         }
371     }
372 
373 
374   // miscellany ..................................
375 
376     protected class AdjustOriginLens extends TGAbstractLens {
377         protected void applyLens(TGPoint2D p) {
378             p.x=p.x+TGPanel.this.getSize().width/2;
379             p.y=p.y+TGPanel.this.getSize().height/2;
380         }
381 
382         protected void undoLens(TGPoint2D p) {
383             p.x=p.x-TGPanel.this.getSize().width/2;
384             p.y=p.y-TGPanel.this.getSize().height/2;
385         }
386     }
387 
388     public class SwitchSelectUI extends TGAbstractClickUI {
389         public void mouseClicked(MouseEvent e) {
390             if ( mouseOverN != null ) {
391                 if ( mouseOverN != select )
392                     setSelect(mouseOverN);
393                 else
394                     clearSelect();
395             }
396         }
397     }
398 
399     void fireMovedEvent() {
400         Vector listeners;
401 
402         synchronized(this) {
403               listeners = (Vector)graphListeners.clone();
404            }
405 
406         for (int i=0;i<listeners.size();i++){
407               GraphListener gl = (GraphListener) listeners.elementAt(i);
408               gl.graphMoved();
409         }
410       }
411 
412     public void fireResetEvent() {
413         Vector listeners;
414 
415         synchronized(this) {
416               listeners = (Vector)graphListeners.clone();
417         }
418 
419         for (int i=0;i<listeners.size();i++){
420               GraphListener gl = (GraphListener) listeners.elementAt(i);
421               gl.graphReset();
422         }
423     }
424 
425     public synchronized void addGraphListener(GraphListener gl){
426         graphListeners.addElement(gl);
427     }
428 
429 
430     public synchronized void removeGraphListener(GraphListener gl){
431         graphListeners.removeElement(gl);
432     }
433 
434 
435     public synchronized void addPaintListener(TGPaintListener pl){
436         paintListeners.addElement(pl);
437     }
438 
439 
440     public synchronized void removePaintListener(TGPaintListener pl){
441         paintListeners.removeElement(pl);
442     }
443 
444     private void redraw() {
445         resetDamper();
446     }
447 
448     public void setMaintainMouseOver( boolean maintain ) {
449         maintainMouseOver = maintain;
450     }
451 
452     public void clearSelect() {
453         if ( select != null ) {
454             select = null;
455             repaint();
456         }
457     }
458 
459    /*** A convenience method that selects the first node of a graph, so that hiding works.
460      */
461     public void selectFirstNode() {
462         setSelect(getGES().getFirstNode());
463     }
464 
465     public void setSelect( Node node ) {
466           if ( node != null ) {
467               select = node;
468               repaint();
469           } else if ( node == null ) clearSelect();
470     }
471 
472     public void multiSelect( TGPoint2D from, TGPoint2D to ) {
473         final double minX,minY,maxX,maxY;
474 
475         if ( from.x > to.x ) { maxX = from.x; minX = to.x; }
476         else                 { minX = from.x; maxX = to.x; }
477         if ( from.y > to.y ) { maxY = from.y; minY = to.y; }
478         else                 { minY = from.y; maxY = to.y; }
479 
480         final Vector selectedNodes = new Vector();
481 
482         TGForEachNode fen = new TGForEachNode() {
483             public void forEachNode( Node node ) {
484                 double x = node.drawx;
485                 double y = node.drawy;
486                 if ( x > minX && x < maxX && y > minY && y < maxY ) {
487                     selectedNodes.addElement(node);
488                 }
489             }
490         };
491 
492         visibleLocality.forAllNodes(fen);
493 
494         if ( selectedNodes.size() > 0 ) {
495             int r = (int)( Math.random()*selectedNodes.size() );
496             setSelect((Node)selectedNodes.elementAt(r));
497         } else {
498             clearSelect();
499         }
500     }
501 
502 
503     public void updateLocalityFromVisibility() throws TGException {
504         visibleLocality.updateLocalityFromVisibility();
505     }
506 
507     public void setLocale( Node node, int radius, int maxAddEdgeCount, int maxExpandEdgeCount,
508                            boolean unidirectional ) throws TGException {
509         localityUtils.setLocale(node,radius, maxAddEdgeCount, maxExpandEdgeCount, unidirectional);
510     }
511     
512     public void fastFinishAnimation() { //Quickly wraps up the add node animation
513         localityUtils.fastFinishAnimation(); 
514     }
515 
516     public void setLocale( Node node, int radius ) throws TGException {
517         localityUtils.setLocale(node,radius);
518     }
519 
520     public void expandNode( Node node ) {
521         localityUtils.expandNode(node);
522     }
523 
524     public void hideNode( Node hideNode ) {
525         localityUtils.hideNode(hideNode );
526     }
527 
528     public void collapseNode( Node collapseNode ) {
529         localityUtils.collapseNode(collapseNode );
530     }
531 
532     public void hideEdge( Edge hideEdge ) {
533         visibleLocality.removeEdge(hideEdge);
534         if ( mouseOverE == hideEdge ) setMouseOverE(null);
535         resetDamper();
536     }
537 
538     public void setDragNode( Node node ) {
539         dragNode = node;
540         tgLayout.setDragNode(node);
541     }
542 
543     public Node getDragNode() {
544         return dragNode;
545     }
546 
547     void setMousePos( Point p ) {
548         mousePos = p;
549     }
550 
551     public Point getMousePos() {
552         return mousePos;
553     }
554 
555     /*** Start and stop the damper.  Should be placed in the TGPanel too. */
556     public void startDamper() {
557         if (tgLayout!=null) tgLayout.startDamper();
558     }
559     public void kstartDamper() {
560         if (tgLayout!=null) tgLayout.start();
561     }
562 
563     public void stopDamper() {
564         if (tgLayout!=null) tgLayout.stopDamper();
565     }
566 
567 
568     public void kstopDamper() {
569         if (tgLayout!=null) tgLayout.stop();
570     }
571     /*** Makes the graph mobile, and slowly slows it down. */
572     public void resetDamper() {
573         if (tgLayout!=null) tgLayout.resetDamper();
574     }
575 
576     /*** Gently stops the graph from moving */
577     public void stopMotion() {
578         if (tgLayout!=null) tgLayout.stopMotion();
579     }
580 
581     class BasicMouseListener extends MouseAdapter {
582 
583         public void mouseEntered(MouseEvent e) {
584             addMouseMotionListener(basicMML);
585         }
586 
587         public void mouseExited(MouseEvent e) {
588             removeMouseMotionListener(basicMML);
589             mousePos = null;
590             setMouseOverN(null);
591             setMouseOverE(null);
592             repaint();
593         }
594     }
595 
596     class BasicMouseMotionListener implements MouseMotionListener {
597         public void mouseDragged(MouseEvent e) {
598             mousePos = e.getPoint();
599             findMouseOver();
600             try {
601                 Thread.sleep(6); //An attempt to make the cursor flicker less
602             } catch (InterruptedException ex) {
603                 //break;
604             }
605         }
606 
607         public void mouseMoved( MouseEvent e ) {
608             mousePos = e.getPoint();
609             synchronized (this) {
610                 Edge oldMouseOverE = mouseOverE;
611                 Node oldMouseOverN = mouseOverN;
612                 findMouseOver();
613 
614                 if (oldMouseOverE!=mouseOverE || oldMouseOverN!=mouseOverN) {
615                     repaint();
616                 }
617                 // Replace the above lines with the commented portion below to prevent whole graph
618                 // from being repainted simply to highlight a node On mouseOver.
619                 // This causes some annoying flickering though.
620                 /*
621                 if(oldMouseOverE!=mouseOverE) {
622                     if (oldMouseOverE!=null) {
623                         synchronized(oldMouseOverE) {
624                             oldMouseOverE.paint(TGPanel.this.getGraphics(),TGPanel.this);
625                             oldMouseOverE.from.paint(TGPanel.this.getGraphics(),TGPanel.this);
626                             oldMouseOverE.to.paint(TGPanel.this.getGraphics(),TGPanel.this);
627 
628                         }
629                     }
630 
631                     if (mouseOverE!=null) {
632                         synchronized(mouseOverE) {
633                             mouseOverE.paint(TGPanel.this.getGraphics(),TGPanel.this);
634                             mouseOverE.from.paint(TGPanel.this.getGraphics(),TGPanel.this);
635                             mouseOverE.to.paint(TGPanel.this.getGraphics(),TGPanel.this);
636                         }
637                     }
638                 }
639 
640                 if(oldMouseOverN!=mouseOverN) {
641                     if (oldMouseOverN!=null) oldMouseOverN.paint(TGPanel.this.getGraphics(),TGPanel.this);
642                     if (mouseOverN!=null) mouseOverN.paint(TGPanel.this.getGraphics(),TGPanel.this);
643                 }
644                 */
645             }
646         }
647     }
648 
649     protected synchronized void findMouseOver() {
650 
651         if ( mousePos == null ) {
652             setMouseOverN(null);
653             setMouseOverE(null);
654             return;
655         }
656 
657         final int mpx=mousePos.x;
658         final int mpy=mousePos.y;
659 
660         final Node[] monA = new Node[1];
661         final Edge[] moeA = new Edge[1];
662 
663         TGForEachNode fen = new TGForEachNode() {
664 
665             double minoverdist = 100; //Kind of a hack (see second if statement)
666                                         //Nodes can be as wide as 200 (=2*100)
667             public void forEachNode( Node node ) {
668                 double x = node.drawx;
669                 double y = node.drawy;
670 
671                 double dist = Math.sqrt((mpx-x)*(mpx-x)+(mpy-y)*(mpy-y));
672 
673                   if ( ( dist < minoverdist ) && node.containsPoint(mpx,mpy) ) {
674                       minoverdist = dist;
675                       monA[0] = node;
676                   }
677             }
678         };
679         visibleLocality.forAllNodes(fen);
680 
681         TGForEachEdge fee = new TGForEachEdge() {
682 
683             double minDist = 8; // Tangential distance to the edge
684             double minFromDist = 1000; // Distance to the edge's "from" node
685 
686             public void forEachEdge( Edge edge ) {
687                 double x = edge.from.drawx;
688                 double y = edge.from.drawy;
689                 double dist = edge.distFromPoint(mpx,mpy);
690                 if ( dist < minDist ) { // Set the over edge to the edge with the minimun tangential distance
691                     minDist = dist;
692                     minFromDist = Math.sqrt((mpx-x)*(mpx-x)+(mpy-y)*(mpy-y));
693                     moeA[0] = edge;
694                 } else if ( dist == minDist ) { // If tangential distances are identical, chose
695                                                 // the edge whose "from" node is closest.
696                     double fromDist = Math.sqrt((mpx-x)*(mpx-x)+(mpy-y)*(mpy-y));
697                     if ( fromDist < minFromDist ) {
698                         minFromDist = fromDist;
699                         moeA[0] = edge;
700                     }
701                 }
702             }
703         };
704         visibleLocality.forAllEdges(fee);
705 
706         setMouseOverN(monA[0]);
707         if ( monA[0] == null )
708             setMouseOverE(moeA[0]);
709         else
710             setMouseOverE(null);
711     }
712 
713     TGPoint2D topLeftDraw = null;
714     TGPoint2D bottomRightDraw = null;
715 
716     public TGPoint2D getTopLeftDraw() {
717         return new TGPoint2D(topLeftDraw);
718     }
719 
720     public TGPoint2D getBottomRightDraw() {
721         return new TGPoint2D(bottomRightDraw);
722     }
723 
724     public TGPoint2D getCenter() {
725         return tgLensSet.convDrawToReal(getSize().width/2,getSize().height/2);
726     }
727 
728     public TGPoint2D getDrawCenter() {
729         return new TGPoint2D(getSize().width/2,getSize().height/2);
730     }
731 
732     public void updateGraphSize() {
733         if ( topLeftDraw == null ) topLeftDraw=new TGPoint2D(0,0);
734         if ( bottomRightDraw == null ) bottomRightDraw=new TGPoint2D(0,0);
735 
736         TGForEachNode fen = new TGForEachNode() {
737             boolean firstNode=true;
738             public void forEachNode( Node node ) {
739                 if ( firstNode ) { //initialize topRight + bottomLeft
740                     topLeftDraw.setLocation(node.drawx,node.drawy);
741                     bottomRightDraw.setLocation(node.drawx,node.drawy);
742                     firstNode=false;
743                 } else {  //Standard max and min finding
744                     topLeftDraw.setLocation(Math.min(node.drawx,topLeftDraw.x),
745                                             Math.min(node.drawy,topLeftDraw.y));
746                     bottomRightDraw.setLocation(Math.max(node.drawx, bottomRightDraw.x),
747                                                 Math.max(node.drawy, bottomRightDraw.y));
748                 }
749             }
750         };
751 
752         visibleLocality.forAllNodes(fen);
753     }
754 
755     public synchronized void processGraphMove() {
756         updateDrawPositions();
757         updateGraphSize();
758     }
759 
760     public synchronized void repaintAfterMove() { // Called by TGLayout + others to indicate that graph has moved
761         processGraphMove();
762         findMouseOver();
763         fireMovedEvent();
764         repaint();
765     }
766 
767     public void updateDrawPos( Node node ) { //sets the visual position from the real position
768         TGPoint2D p = tgLensSet.convRealToDraw(node.x,node.y);
769         node.drawx = p.x;
770         node.drawy = p.y;
771        }
772 
773     public void updatePosFromDraw( Node node ) { //sets the real position from the visual position
774         TGPoint2D p = tgLensSet.convDrawToReal(node.drawx,node.drawy);
775         node.x = p.x;
776         node.y = p.y;
777     }
778 
779     public void updateDrawPositions() {
780         TGForEachNode fen = new TGForEachNode() {
781             public void forEachNode( Node node ) {
782                 updateDrawPos(node);
783             }
784         };
785         visibleLocality.forAllNodes(fen);
786     }
787 
788     Color myBrighter( Color c ) {
789         int r = c.getRed();
790         int g = c.getGreen();
791         int b = c.getBlue();
792 
793         r=Math.min(r+96, 255);
794         g=Math.min(g+96, 255);
795         b=Math.min(b+96, 255);
796 
797         return new Color(r,g,b);
798     }
799 
800     public synchronized void paint( Graphics g ) {
801         update(g);
802     }
803 
804     public synchronized void update( Graphics g ) {
805         Dimension d = getSize();
806         if ( (offscreen == null) 
807                 || ( d.width != offscreensize.width ) 
808                 || ( d.height != offscreensize.height )) {
809             offscreen = createImage(d.width, d.height);
810             offscreensize = d;
811             offgraphics = offscreen.getGraphics();
812 
813             processGraphMove();            
814             findMouseOver();
815             fireMovedEvent();
816         }
817 
818         offgraphics.setColor(BACK_COLOR);
819         offgraphics.fillRect(0, 0, d.width, d.height);
820 
821         synchronized(this) {
822             paintListeners = (Vector)paintListeners.clone();
823         }
824 
825         for ( int i = 0; i < paintListeners.size(); i++ ) {
826             TGPaintListener pl = (TGPaintListener) paintListeners.elementAt(i);
827             pl.paintFirst(offgraphics);
828         }
829 
830         TGForEachEdge fee = new TGForEachEdge() {
831             public void forEachEdge( Edge edge ) {
832                 edge.paint(offgraphics, TGPanel.this);
833             }
834         };
835 
836         visibleLocality.forAllEdges(fee);
837 
838         for ( int i = 0; i < paintListeners.size() ; i++ ) {
839              TGPaintListener pl = (TGPaintListener) paintListeners.elementAt(i);
840              pl.paintAfterEdges(offgraphics);
841         }
842 
843         TGForEachNode fen = new TGForEachNode() {
844             public void forEachNode( Node node ) {
845                 node.paint(offgraphics,TGPanel.this);
846             }
847         };
848 
849         visibleLocality.forAllNodes(fen);
850 
851         if ( mouseOverE != null ) { //Make the edge the mouse is over appear on top.
852             mouseOverE.paint(offgraphics, this);
853             mouseOverE.from.paint(offgraphics, this);
854             mouseOverE.to.paint(offgraphics, this);
855         }
856 
857         if ( select != null ) { //Make the selected node appear on top.
858             select.paint(offgraphics, this);
859         }
860 
861         if ( mouseOverN != null ) { //Make the node the mouse is over appear on top.
862             mouseOverN.paint(offgraphics, this);
863         }
864 
865         for ( int i = 0; i < paintListeners.size(); i++ ) {
866             TGPaintListener pl = (TGPaintListener)paintListeners.elementAt(i);
867             pl.paintLast(offgraphics);
868         }
869         
870         paintComponents(offgraphics); //Paint any components that have been added to this panel
871         g.drawImage(offscreen, 0, 0, null);
872         
873     }
874 
875 
876     public static void main( String[] args ) {
877 
878         JFrame frame;
879         frame = new JFrame("TGPanel");
880         TGPanel tgPanel = new TGPanel();
881         frame.addWindowListener(new WindowAdapter() {
882             public void windowClosing(WindowEvent e) {System.exit(0);}
883         });
884 
885         TGLensSet tgls = new TGLensSet();
886         tgls.addLens(tgPanel.getAdjustOriginLens());
887         tgPanel.setLensSet(tgls);
888         try {
889             tgPanel.addNode();  //Add a starting node.
890         } catch ( TGException tge ) {
891             System.err.println(tge.getMessage());
892         }
893         tgPanel.setVisible(true);
894         new GLEditUI(tgPanel).activate();
895         frame.getContentPane().add("Center", tgPanel);
896         frame.setSize(500,500);
897         frame.setVisible(true);
898     }
899 
900 } // end com.touchgraph.graphlayout.TGPanel