1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
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;
92 protected Node mouseOverN;
93 protected boolean maintainMouseOver = false;
94
95 protected Node select;
96
97 Node dragNode;
98
99 protected Point mousePos;
100 Image offscreen;
101 Dimension offscreensize;
102 Graphics offgraphics;
103
104 private Vector graphListeners;
105 private Vector paintListeners;
106 TGLensSet tgLensSet;
107
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
159
160 public void setBackColor( Color color ) { BACK_COLOR = color; }
161
162
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;
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;
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;
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);
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;
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)) {
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;
300 if ( mouseOverN != node ) {
301 Node oldMouseOverN = mouseOverN;
302 mouseOverN=node;
303 }
304 }
305
306
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;
367 if ( mouseOverE != edge ) {
368 Edge oldMouseOverE = mouseOverE;
369 mouseOverE = edge;
370 }
371 }
372
373
374
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() {
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);
602 } catch (InterruptedException ex) {
603
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
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
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;
666
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;
684 double minFromDist = 1000;
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 ) {
691 minDist = dist;
692 minFromDist = Math.sqrt((mpx-x)*(mpx-x)+(mpy-y)*(mpy-y));
693 moeA[0] = edge;
694 } else if ( dist == minDist ) {
695
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 ) {
740 topLeftDraw.setLocation(node.drawx,node.drawy);
741 bottomRightDraw.setLocation(node.drawx,node.drawy);
742 firstNode=false;
743 } else {
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() {
761 processGraphMove();
762 findMouseOver();
763 fireMovedEvent();
764 repaint();
765 }
766
767 public void updateDrawPos( Node node ) {
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 ) {
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 ) {
852 mouseOverE.paint(offgraphics, this);
853 mouseOverE.from.paint(offgraphics, this);
854 mouseOverE.to.paint(offgraphics, this);
855 }
856
857 if ( select != null ) {
858 select.paint(offgraphics, this);
859 }
860
861 if ( mouseOverN != null ) {
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);
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();
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 }