1 32 33 package websphinx.workbench; 34 35 import graph.*; 37 import gd.*; 38 39 import rcm.awt.*; 40 41 import java.util.*; 42 import java.awt.*; 43 import java.awt.image.ImageObserver ; 44 45 public class GraphLayout extends Canvas implements Runnable , ImageObserver { 46 47 Graph graph; 49 double restLength = 50; double springConstant = 100; double nodeCharge = 10000; 53 GDAlgorithm algorithm; 55 boolean running = false; boolean automaticLayout = true; double threshold = 100; boolean quiescent = true; boolean dirty = true; 62 int interval = 100; int iterations = 3; 65 Color nodeColor = Color.pink; Color edgeColor = Color.black; Color tipColor = Color.yellow; 70 74 Object tipObject = null; MultiLineString tip = null; int tipX, tipY, tipWidth, tipHeight; 78 GraphLayoutControlPanel controlPanel; 79 80 83 public GraphLayout () { 84 graph = new Graph (); 85 resetAlgorithm (); 86 start (); 87 } 88 89 92 public synchronized void clear () { 93 graph = new Graph (); 94 changedGraph (); 95 } 96 97 100 public synchronized Graph getGraph () { 101 return graph; 102 } 103 104 107 public synchronized void setGraph (Graph graph) { 108 this.graph = graph; 109 tipObject = null; 112 tip = null; 113 changedGraph (); 114 } 115 116 119 public synchronized GDAlgorithm getAlgorithm () { 120 return algorithm; 121 } 122 123 126 public synchronized void setAlgorithm (GDAlgorithm algorithm) { 127 this.algorithm = algorithm; 128 changedGraph (); 129 } 130 131 synchronized void resetAlgorithm () { 132 algorithm = new AllPairsAlgorithm (springConstant, nodeCharge); 133 changedGraph (); 134 } 135 136 139 public synchronized double getRestLength () { 140 return restLength; 141 } 142 143 146 public synchronized void setRestLength (double restLength) { 147 this.restLength = restLength; 148 changedGraph (); 149 } 150 151 154 public synchronized double getSpringConstant () { 155 return springConstant; 156 } 157 158 161 public synchronized void setSpringConstant (double springConstant) { 162 this.springConstant = springConstant; 163 resetAlgorithm (); 164 } 165 166 169 public synchronized double getNodeCharge () { 170 return nodeCharge; 171 } 172 173 176 public synchronized void setNodeCharge (double nodeCharge) { 177 this.nodeCharge = nodeCharge; 178 resetAlgorithm (); 179 } 180 181 184 public synchronized int getInterval () { 185 return interval; 186 } 187 188 191 public synchronized void setInterval (int interval) { 192 this.interval = interval; 193 } 194 195 198 public synchronized int getIterations () { 199 return iterations; 200 } 201 202 205 public synchronized void setIterations (int iterations) { 206 this.iterations = iterations; 207 } 208 209 212 public synchronized boolean getAutomaticLayout () { 213 return automaticLayout; 214 } 215 216 219 public synchronized void setAutomaticLayout (boolean f) { 220 automaticLayout = f; 221 quiescent = !automaticLayout; 222 if (controlPanel != null) 223 controlPanel.automatic.setState (automaticLayout); 224 } 225 226 229 public synchronized boolean getQuiescent () { 230 return quiescent; 231 } 232 233 236 public synchronized boolean getRunning () { 237 return running; 238 } 239 240 243 public synchronized double getThreshold () { 244 return threshold; 245 } 246 247 250 public synchronized void setThreshold (double threshold) { 251 this.threshold = threshold; 252 changedGraph (); 253 } 254 255 258 public synchronized Color getNodeColor () { 259 return nodeColor; 260 } 261 262 265 public synchronized void setNodeColor (Color nodeColor) { 266 this.nodeColor = nodeColor; 267 } 268 269 272 public synchronized Color getEdgeColor () { 273 return edgeColor; 274 } 275 276 279 public synchronized void setEdgeColor (Color edgeColor) { 280 this.edgeColor = edgeColor; 281 } 282 283 286 public synchronized Color getTipColor () { 287 return tipColor; 288 } 289 290 293 public synchronized void setTipColor (Color tipColor) { 294 this.tipColor = tipColor; 295 } 296 297 300 public synchronized RenderedNode getSelectedNode () { 301 return tipObject instanceof RenderedNode 302 ? (RenderedNode)tipObject 303 : null; 304 } 305 306 309 public synchronized RenderedEdge getSelectedEdge () { 310 return tipObject instanceof RenderedEdge 311 ? (RenderedEdge)tipObject 312 : null; 313 } 314 315 318 public synchronized void addNode (RenderedNode node) { 319 graph.addNode (node); 320 graph.placeNode (node, node.x, node.y); 321 changedGraph (); 322 } 323 324 327 public synchronized void addEdge (RenderedEdge edge) { 328 if (edge.restLength == 0) 329 edge.restLength = restLength; 330 graph.addEdge (edge); 331 changedGraph (); 332 } 333 334 337 public synchronized void removeNode (RenderedNode node) { 338 graph.removeNode (node); 339 changedGraph (); 340 } 341 342 345 public synchronized void removeEdge (RenderedEdge edge) { 346 graph.removeEdge (edge); 347 changedGraph (); 348 } 349 350 353 public synchronized boolean imageUpdate(Image img, 354 int infoflags, 355 int x, 356 int y, 357 int width, 358 int height) { 359 if ((infoflags & (ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0) { 360 for (int i=0; i<graph.sizeNodes; ++i) { 361 RenderedNode n = (RenderedNode)graph.nodes[i]; 362 if (n.icon == img) { 363 n.width = width; 364 n.height = height; 365 changedGraph (); 366 } 367 } 368 } 369 return super.imageUpdate (img, infoflags, x, y, width, height); 370 } 371 372 376 377 Thread iterator; 378 379 382 public synchronized void start () { 383 if (!running) { 384 running = true; 385 iterator = new Thread (this, "GraphListener"); 386 iterator.setDaemon (true); 387 iterator.setPriority (Thread.MIN_PRIORITY); 388 iterator.start (); 389 } 390 } 391 392 395 public synchronized void stop () { 396 if (running) { 397 running = false; 398 notify (); 399 iterator = null; 400 } 401 } 402 403 407 final static int MULTIPLIER = 2; 408 public synchronized void run () { 409 quiescent = false; 410 while (running) { 411 long start = System.currentTimeMillis (); 412 413 if (automaticLayout && !quiescent) { 414 for (int i=0; i < iterations; ++i) { 415 double improvement = algorithm.improveGraph (graph); 416 dirty = true; 417 if (improvement <= threshold * graph.sizeNodes) { 418 quiescent = true; 419 break; 420 } 421 } 422 } 423 424 if (dirty) 425 super.repaint (); 426 427 431 432 try { 433 wait (interval); 434 } catch (InterruptedException e) { 435 } 436 } 437 quiescent = true; 438 } 439 440 443 public synchronized void changedGraph () { 444 if (automaticLayout) 445 quiescent = false; 446 repaint (); 447 } 448 449 452 public synchronized void repaint () { 453 if (!running) 454 super.repaint (); 455 else 456 dirty = true; 457 } 458 459 462 public void showControlPanel () { 463 if (controlPanel == null) 464 controlPanel = new GraphLayoutControlPanel (this); 465 controlPanel.show (); 466 } 467 468 protected void finalize () throws Throwable { 469 super.finalize (); 470 if (controlPanel != null) { 471 controlPanel.dispose(); 472 controlPanel = null; 473 } 474 } 475 476 479 double originX = 0.0, originY = 0.0; 480 double scaleX = 1.0, scaleY = 1.0; 481 482 private void scaleGraph () { 483 Dimension d = size (); 484 double halfScreenWidth = d.width/2.0; 485 double halfScreenHeight = d.height/2.0; 486 487 double sX = 1.0, sY = 1.0; 488 for (int i=0; i < graph.sizeNodes; ++i) { 489 RenderedNode n = (RenderedNode)graph.nodes[i]; 490 sX = Math.min(sX, (halfScreenWidth - n.width/2.0)/(Math.abs(n.x)+1)); 491 sY = Math.min(sY, (halfScreenHeight - n.height/2.0)/(Math.abs(n.y)+1)); 492 } 493 494 double oX = halfScreenWidth; 495 double oY = halfScreenHeight; 496 497 for (int i=0; i < graph.sizeNodes; ++i) { 498 RenderedNode n = (RenderedNode)graph.nodes[i]; 499 n.screenX = (int)(n.x*sX + oX); 500 n.screenY = (int)(n.y*sY + oY); 501 } 502 503 originX = oX; 505 originY = oY; 506 scaleX = sX; 507 scaleY = sY; 508 } 509 510 public synchronized void placeNodeOnScreen (RenderedNode n, int x, int y) { 511 graph.placeNode (n, (x - originX)/scaleX, (y - originY)/scaleY); 512 n.screenX = x; 513 n.screenY = y; 514 } 515 516 public synchronized void placeNodeOnGraph (RenderedNode n, double x, double y) { 517 graph.placeNode (n, x, y); 518 n.screenX = (int)(x*scaleX + originX); 519 n.screenY = (int)(y*scaleY + originY); 520 } 521 522 526 527 Image offscreen; Dimension offSize; Graphics offg; FontMetrics fm; 532 public void update (Graphics g) { 533 paint (g); 536 } 539 540 void createOffscreenArea (Dimension d) { 541 offSize = new Dimension (d.width > 0 ? d.width : 1, 542 d.height > 0 ? d.height : 1); 543 offscreen = createImage (offSize.width, offSize.height); 544 offg = offscreen.getGraphics (); 545 offg.setFont (getFont ()); 546 fm = offg.getFontMetrics (); 547 } 548 549 public synchronized void paint (Graphics g) { 550 Dimension d = size (); 551 552 if (offscreen == null 553 || d.width != offSize.width 554 || d.height != offSize.height) 555 createOffscreenArea (d); 556 557 offg.setColor (getBackground ()); 558 offg.fillRect (0, 0, d.width, d.height); 559 560 scaleGraph (); 561 562 for (int i=0; i<graph.sizeEdges; ++i) { 564 RenderedEdge e = (RenderedEdge)graph.edges[i]; 565 if (e == null) 566 continue; 567 RenderedNode from = (RenderedNode)e.from; 568 RenderedNode to = (RenderedNode)e.to; 569 if (from == null || to == null) 570 continue; 571 572 Color c = e.color; 573 if (c == null) 574 c = edgeColor; 575 576 offg.setColor (c); 577 drawArrowToBox (offg, (int)from.screenX, (int)from.screenY, 578 (int)to.screenX, (int)to.screenY, 579 (int)(to.width/2), (int)(to.height/2), 580 6, 3, e.thick); 581 } 582 583 for (int i=0; i < graph.sizeNodes; ++i) { 585 RenderedNode n = (RenderedNode)graph.nodes[i]; 586 if (n == null) 587 continue; 588 589 int width = (int)n.width; 590 int height = (int)n.height; 591 int x = (int)n.screenX - width/2; 592 int y = (int)n.screenY - height/2; 593 Color c = n.color; 594 595 if (n.icon == null) { 596 if (c == null) 597 c = nodeColor; 598 599 offg.setColor (c); 600 offg.fillRect (x, y, width, height); 601 offg.setColor (getForeground ()); 602 offg.drawRect (x, y, width-1, height-1); 603 offg.drawString (n.name, x+5, y+2 + fm.getAscent ()); 604 } 605 else { 606 if (c == null) 608 offg.drawImage (n.icon, x, y, this); 609 else 610 offg.drawImage (n.icon, x, y, c, this); 611 } 612 } 613 614 if (tip != null) { 616 offg.setColor (tipColor); 617 offg.fillRect (tipX, tipY, tipWidth, tipHeight); 618 offg.setColor (Color.black); 619 offg.drawRect (tipX, tipY, tipWidth-1, tipHeight-1); 620 tip.draw (offg, tipX + 5, tipY + 2, Label.LEFT); 621 } 622 623 offg.setColor (quiescent ? getForeground () : Color.red); 625 offg.drawRect (0, 0, d.width-1, d.height-1); 626 627 g.drawImage (offscreen, 0, 0, null); 629 630 dirty = false; 631 } 632 633 void drawArrowToBox (Graphics g, int x1, int y1, int x2, int y2, 634 int wHalfBox, int hHalfBox, 635 int head_length, int head_width, boolean thick) { 636 if (thick) { 637 drawArrowToBox (g, x1, y1, x2, y2, 638 wHalfBox, hHalfBox, head_length, head_width, false); 639 drawArrowToBox (g, x1-1, y1, x2-1, y2, 640 wHalfBox, hHalfBox, head_length, head_width, false); 641 drawArrowToBox (g, x1, y1-1, x2, y2-1, 642 wHalfBox, hHalfBox, head_length, head_width, false); 643 drawArrowToBox (g, x1-1, y1-1, x2-1, y2-1, 644 wHalfBox, hHalfBox, head_length, head_width, false); 645 } 646 else { 647 double dx = x2 - x1; 648 double dy = y2 - y1; 649 double d = Math.sqrt (dx * dx + dy * dy); 650 if (d < 1.0) { 651 d = 1.0; 652 dx = 1; 653 } 654 dx /= d; 655 dy /= d; 656 657 double lx = head_length * dx; 658 double ly = head_length * dy; 659 double wx = head_width * dx; 660 double wy = head_width * dy; 661 662 double cp1 = dx*hHalfBox - dy*wHalfBox; 663 double cp2 = dx*hHalfBox + dy*wHalfBox; 664 665 if (cp1 < 0) { 666 if (cp2 < 0) { 667 x2 += wHalfBox; 669 y2 += wHalfBox*dy/dx; 670 } 671 else { 672 y2 -= hHalfBox; 674 x2 -= hHalfBox*dx/dy; 675 } 676 } 677 else { 678 if (cp2 > 0) { 679 x2 -= wHalfBox; 681 y2 -= wHalfBox*dy/dx; 682 } 683 else { 684 y2 += hHalfBox; 686 x2 += hHalfBox*dx/dy; 687 } 688 } 689 690 g.drawLine (x1, y1, x2, y2); 691 g.drawLine (x2, y2, (int)(x2-lx+wy+0.5), (int)(y2-ly-wx+.5)); 692 g.drawLine (x2, y2, (int)(x2-lx-wy+0.5), (int)(y2-ly+wx+.5)); 693 } 694 } 695 696 public synchronized FontMetrics getFontMetrics () { 697 if (fm == null) { 698 Dimension d = size (); 699 createOffscreenArea (d); 700 } 701 return fm; 702 } 703 704 public synchronized void setFont (Font f) { 706 super.setFont (f); 707 if (offg != null) { 708 offg.setFont (f); 709 fm = offg.getFontMetrics (); 710 } 711 } 712 713 718 719 723 724 RenderedNode dragNode = null; int dragOffsetX, dragOffsetY; 726 729 void point (int x, int y) { 730 Object over = pick (x, y); 731 if (over == null) { 732 if (tipObject != null || tip != null) { 733 tipObject = null; 734 tip = null; 735 super.repaint (); 736 } 737 } 738 else if (over != tipObject) { 739 String [] tipLines = ((Tipped)over).getTip (); 740 741 if (tipLines == null) { 742 tipObject = null; 743 tip = null; 744 super.repaint (); 745 } 746 else { 747 tipObject = over; 748 tip = new MultiLineString (tipLines); 749 tipWidth = tip.getWidth (fm) + 10; 750 tipHeight = tip.getHeight (fm) + 4; 751 tipX = Math.max (x - tipWidth/2, 0); 752 tipY = Math.min (y + 25, 753 offSize.height - tipHeight); 754 super.repaint (); 755 } 756 } 757 } 758 759 void leave () { 760 if (tipObject != null || tip != null) { 761 tip = null; 762 tipObject = null; 763 super.repaint (); 764 } 765 } 766 767 void click (int x, int y, boolean rightClick) { 768 requestFocus(); 769 770 Object over = pick (x, y); 771 if (over != null) { 772 if (over instanceof RenderedNode) { 773 RenderedNode n = (RenderedNode)over; 774 777 if (!n.fixed) { 779 dragNode = n; 780 dragNode.fixed = true; 781 dragOffsetX = (int)dragNode.screenX - x; 782 dragOffsetY = (int)dragNode.screenY - y; 783 } 784 } 785 } 791 else if (rightClick) { 792 showControlPanel (); 794 } 795 } 796 797 void drag (int x, int y) { 798 if (dragNode != null) { 799 placeNodeOnScreen (dragNode, 800 x + dragOffsetX, 801 y + dragOffsetY); 802 changedGraph (); 803 } 804 } 805 806 void drop (int x, int y) { 807 if (dragNode != null) { 808 placeNodeOnScreen (dragNode, 809 x + dragOffsetX, 810 y + dragOffsetY); 811 changedGraph (); 812 dragNode.fixed = false; 813 dragNode = null; 814 } 815 } 816 817 public boolean handleEvent (Event event) { 818 switch (event.id) { 819 case Event.MOUSE_DOWN: 820 click (event.x, event.y, event.metaDown()); 821 return true; 822 case Event.MOUSE_UP: 823 drop (event.x, event.y); 824 return true; 825 case Event.MOUSE_MOVE: 826 point (event.x, event.y); 827 return true; 828 case Event.MOUSE_EXIT: 829 leave (); 830 return true; 831 case Event.MOUSE_DRAG: 832 if (dragNode != null) { 833 drag (event.x, event.y); 834 return true; 835 } 836 else super.handleEvent (event); 837 default: 838 return super.handleEvent (event); 839 } 840 } 841 842 848 public Object pick (int x, int y) { 849 for (int i=graph.sizeNodes-1; i >= 0; --i) { 851 RenderedNode n = (RenderedNode)graph.nodes[i]; 852 if (Math.abs (n.screenX - x) < n.width/2 && Math.abs (n.screenY - y) < n.height/2) 853 return n; 854 } 855 856 for (int i=graph.sizeEdges-1; i>=0; --i) { 857 RenderedEdge e = (RenderedEdge)graph.edges[i]; 858 RenderedNode to = (RenderedNode)e.to; 859 RenderedNode from = (RenderedNode)e.from; 860 if (inLineSegment (x, y, 861 (int)to.screenX, (int)to.screenY, 862 (int)from.screenX, (int)from.screenY, 4)) 863 return e; 864 } 865 866 return null; 867 } 868 869 boolean inLineSegment (int x, int y, int x1, int y1, int x2, int y2, 870 int threshold) { 871 int left, right, top, bottom; 872 if (x1 < x2) { 873 left = x1; right = x2; 874 } 875 else { 876 left = x2; right = x1; 877 } 878 if (y1 < y2) { 879 top = y1; bottom = y2; 880 } 881 else { 882 top = y2; bottom = y1; 883 } 884 885 if (x < left-threshold || x > right+threshold || 887 y < top-threshold || y > bottom+threshold) { 888 return false; 889 } 890 891 int a = y1 - y2; 894 int b = x2 - x1; 895 int c = x1*y2 - x2*y1; 896 int d = a*x + b*y + c; 897 898 return (d*d <= threshold * threshold * (a*a + b*b)); 899 } 900 901 936 } 937 938 class GraphLayoutControlPanel extends ClosableFrame { 939 GraphLayout gl; 940 941 Checkbox automatic; 942 943 Scrollbar threshold; 944 Scrollbar restLength; 945 Scrollbar springConstant; 946 Scrollbar nodeCharge; 947 948 TextField thresholdText; 949 TextField restLengthText; 950 TextField springConstantText; 951 TextField nodeChargeText; 952 953 public GraphLayoutControlPanel (GraphLayout graphLayout) { 954 super ("Graph Layout Control Panel", true); 955 gl = graphLayout; 956 957 setLayout (new GridBagLayout ()); 958 Constrain.add (this, automatic = new Checkbox ("Automatic layout"), 959 Constrain.labelLike (0, 0, 2)); 960 automatic.setState (true); 961 Constrain.add (this, new Label ("Threshold:", Label.LEFT), Constrain.labelLike (0,1)); 962 Constrain.add (this, thresholdText = new TextField (String.valueOf (gl.getThreshold())), 963 Constrain.fieldLike (1,1)); 964 Constrain.add (this, threshold = new Scrollbar (Scrollbar.HORIZONTAL, 965 (int)gl.getThreshold(), 50, 0, 1000), 966 Constrain.fieldLike (0,2,2)); 967 Constrain.add (this, new Label ("Rest length:", Label.LEFT), Constrain.labelLike (0,3)); 968 Constrain.add (this, restLengthText = new TextField (String.valueOf (gl.getRestLength())), 969 Constrain.fieldLike (1,3)); 970 Constrain.add (this, restLength = new Scrollbar (Scrollbar.HORIZONTAL, 971 (int)gl.getRestLength(), 50, 0, 1000), Constrain.fieldLike (0,4,2)); 972 Constrain.add (this, new Label ("Spring constant:", Label.LEFT), Constrain.labelLike (0,5)); 973 Constrain.add (this, springConstantText = new TextField (String.valueOf (gl.getSpringConstant())), 974 Constrain.fieldLike (1,5)); 975 Constrain.add (this, springConstant = new Scrollbar (Scrollbar.HORIZONTAL, 976 (int)gl.getSpringConstant(), 50, 0, 1000), Constrain.fieldLike (0,6,2)); 977 Constrain.add (this, new Label ("Node charge:", Label.LEFT), Constrain.labelLike (0,7)); 978 Constrain.add (this, nodeChargeText = new TextField (String.valueOf (Math.sqrt(gl.getNodeCharge()))), 979 Constrain.fieldLike (1,7)); 980 Constrain.add (this, nodeCharge = new Scrollbar (Scrollbar.HORIZONTAL, 981 (int)(Math.sqrt(gl.getNodeCharge())), 50, 0, 1000), Constrain.fieldLike (0,8,2)); 982 pack (); 983 } 984 985 public boolean handleEvent (Event event) { 986 if (event.target == automatic) 988 gl.setAutomaticLayout (automatic.getState ()); 989 else if (event.target == threshold) 990 gl.setThreshold (((Integer )event.arg).intValue()); 991 else if (event.target == restLength) 992 gl.setRestLength (((Integer )event.arg).intValue()); 993 else if (event.target == springConstant) 994 gl.setSpringConstant (((Integer )event.arg).intValue()); 995 else if (event.target == restLength) { 996 int v = ((Integer )event.arg).intValue(); 997 gl.setNodeCharge (v*v); 998 } 999 else 1000 return super.handleEvent (event); 1001 return true; 1002 } 1003} 1004 | Popular Tags |