Corrected MDP Embed
authorveltr
Wed, 14 Nov 2012 17:26:06 +0100
changeset 43 4baa7530912c
parent 42 e05da377a27e
child 44 0931f368cb9a
Corrected MDP Embed
src/hp/settings.py
src/hp/static/hp/css/player.css
src/hp/static/hp/tmgraph/constants.pde
src/hp/static/hp/tmgraph/event.pde
src/hp/static/hp/tmgraph/initialdata.pde
src/hp/static/hp/tmgraph/javascript.pde
src/hp/static/hp/tmgraph/menu.pde
src/hp/static/hp/tmgraph/model.pde
src/hp/static/hp/tmgraph/physics.pde
src/hp/static/hp/tmgraph/preload.pde
src/hp/static/hp/tmgraph/tmgraph.pde
src/hp/templates/hp/partial/embed_player.html
src/hp/templates/hp/video_player.html
--- a/src/hp/settings.py	Wed Nov 14 16:35:12 2012 +0100
+++ b/src/hp/settings.py	Wed Nov 14 17:26:06 2012 +0100
@@ -163,8 +163,8 @@
     }
 }
 
-LDT_DOMAIN = 'http://localhost'
-LDT_BASE_URL = LDT_DOMAIN + '/~ymh/hp_ldt/'
+LDT_DOMAIN = 'http://capsicum'
+LDT_BASE_URL = LDT_DOMAIN + '/pf/'
 LDT_URL = LDT_BASE_URL + "ldtplatform/"
 LDT_API_URL = LDT_URL + 'api/ldt/1.0/'
 LDT_STATIC_URL = LDT_BASE_URL + "static/site/"
--- a/src/hp/static/hp/css/player.css	Wed Nov 14 16:35:12 2012 +0100
+++ b/src/hp/static/hp/css/player.css	Wed Nov 14 17:26:06 2012 +0100
@@ -11,39 +11,3 @@
 .right-column h2 {
     text-align: right;
 }
-
-.related-video {
-    width: 235px; float: left; margin-bottom: 10px;
-}
-
-.related-video:nth-child(even) {
-    margin-left: 10px;
-}
-
-.related-video a {
-    display: block; clear: both; text-decoration: none;
-}
-
-.related-video img {
-    max-width: 120px; max-height: 90px; float: left;
-}
-
-.related-video h3, p {
-    margin: 0 0 5px 125px;
-}
-
-.related-video h3 {
-    font-size: 14px; font-weight: 600; color: #330099;
-}
-
-.related-video h3:hover {
-    text-decoration: underline;
-}
-
-.related-video p {
-    font-size: 12px;
-}
-
-.video-duration {
-    color: #c00000;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/constants.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,266 @@
+//////////    CONSTANTS    //////////
+static final int   LABEL_LENGTH = 24;
+static final float GAP   = 8;
+static final float sigma = 0.01;
+static final float FRAME           = 24;
+static final float TICK            = 1;//0.5f;
+static final float SPRING_LENGTH   = 100;
+static final float SPRING_STRENGTH = 0.4; // 0.2;
+static final float SPRING_DAMPING  = 0.4; // 0.2;
+static final float SPACER_STRENGTH = 2000;
+static final float DRAGFACTOR      = 0.2; //0.2
+static final float REDUCE          = 1;
+static final float MAX_DISTANCE    = 600;
+static final float MIN_DISTANCE    = 24;//6;
+static final float MAX_VELOCITY    = 48;//20; //8;
+
+// mouse event
+static final int   MOVED      =  1, 
+                   DRAGGED    =  2, 
+                   CLICKED    =  3,  
+                   RELEASED   =  4; 
+// touch event
+static final int   MOVE        =  6,  
+                   START       =  7,  
+                   END         =  8;                    
+// motion
+static final int   HALT        = 11, 
+                   MOVING      = 12, 
+                   DRAGGING    = 13, 
+                   DRAG        = 14, 
+                   UP          = 15;
+// action
+static final int   NOOP        = 20, 
+                   CLICK       = 21,  
+                   RELEASE     = 22,  
+                   DOUBLECLICK = 23, 
+                   HOLDING     = 24, 
+                   HOLDED      = 25; 
+// node menu
+static final int   EXPND       = 31, 
+                   ROOT        = 32, 
+                   FIX         = 33, 
+                   FREE        = 34, 
+                   HIDE        = 35, 
+                   INFO        = 36, 
+                   NEWREL      = 37,                    
+                   NEWREL2     = 30, 
+                   SQUEEZE     = 38, 
+                   TOPIC       = 39; 
+// application menu
+static final int   FITALL      = 41, 
+                   ALLFIX      = 42, 
+                   ALLFREE     = 43, 
+                   GROUPSHAPE  = 44, 
+                   ALLSAVE     = 45, 
+                   ALLRETRIEVE = 46, 
+                   NEWTOPIC    = 47,
+                   MODE        = 48,
+                   PEDIA       = 49;
+// edge menu
+static final int   HIDEEDGE    = 51, 
+                   EDITEDGE    = 52, 
+                   DELETEEDGE  = 53;             
+
+static final int AliceBlue         = #F0F8FF;
+static final int AntiqueWhite      = #FAEBD7;
+static final int Aqua              = #00FFFF;
+static final int Aquamarine        = #7FFFD4;
+static final int Azure             = #F0FFFF;
+static final int Beige             = #F5F5DC;
+static final int Bisque            = #FFE4C4;
+static final int Black             = #000000;
+static final int BlanchedAlmond    = #FFEBCD;
+static final int Blue              = #0000FF;
+static final int BlueViolet        = #8A2BE2;
+static final int Brown             = #A52A2A;
+static final int BurlyWood         = #DEB887;
+static final int CadetBlue         = #5F9EA0;
+static final int Chartreuse        = #7FFF00;
+static final int Chocolate         = #D2691E;
+static final int Coral             = #FF7F50;
+static final int CornflowerBlue    = #6495ED;
+static final int Cornsilk          = #FFF8DC;
+static final int Crimson           = #DC143C;
+static final int Cyan              = #00FFFF;
+static final int DarkBlue          = #00008B;
+static final int DarkCyan          = #008B8B;
+static final int DarkGoldenRod     = #B8860B;
+static final int DarkGray          = #A9A9A9;
+static final int DarkGrey          = #A9A9A9;
+static final int DarkGreen         = #006400;
+static final int DarkKhaki         = #BDB76B;
+static final int DarkMagenta       = #8B008B;
+static final int DarkOliveGreen    = #556B2F;
+static final int Darkorange        = #FF8C00;
+static final int DarkOrchid        = #9932CC;
+static final int DarkRed           = #8B0000;
+static final int DarkSalmon        = #E9967A;
+static final int DarkSeaGreen      = #8FBC8F;
+static final int DarkSlateBlue     = #483D8B;
+static final int DarkSlateGray     = #2F4F4F;
+static final int DarkSlateGrey     = #2F4F4F;
+static final int DarkTurquoise     = #00CED1;
+static final int DarkViolet        = #9400D3;
+static final int DeepPink          = #FF1493;
+static final int DeepSkyBlue       = #00BFFF;
+static final int DimGray           = #696969;
+static final int DimGrey           = #696969;
+static final int DodgerBlue        = #1E90FF;
+static final int FireBrick         = #B22222;
+static final int FloralWhite       = #FFFAF0;
+static final int ForestGreen       = #228B22;
+static final int Fuchsia           = #FF00FF;
+static final int Gainsboro         = #DCDCDC;
+static final int GhostWhite        = #F8F8FF;
+static final int Gold              = #FFD700;
+static final int GoldenRod         = #DAA520;
+static final int Gray              = #808080;
+static final int Grey              = #808080;
+static final int Green             = #008000;
+static final int GreenYellow       = #ADFF2F;
+static final int HoneyDew          = #F0FFF0;
+static final int HotPink           = #FF69B4;
+static final int IndianRed         = #CD5C5C;
+static final int Indigo            = #4B0082;
+static final int Ivory             = #FFFFF0;
+static final int Khaki             = #F0E68C;
+static final int Lavender          = #E6E6FA;
+static final int LavenderBlush     = #FFF0F5;
+static final int LawnGreen         = #7CFC00;
+static final int LemonChiffon      = #FFFACD;
+static final int LightBlue         = #ADD8E6;
+static final int LightCoral        = #F08080;
+static final int LightCyan         = #E0FFFF;
+static final int LightGoldenRodYellow = #FAFAD2;
+static final int LightGray         = #D3D3D3;
+static final int LightGrey         = #D3D3D3;
+static final int LightGreen        = #90EE90;
+static final int LightPink         = #FFB6C1;
+static final int LightSalmon       = #FFA07A;
+static final int LightSeaGreen     = #20B2AA;
+static final int LightSkyBlue      = #87CEFA;
+static final int LightSlateGray    = #778899;
+static final int LightSlateGrey    = #778899;
+static final int LightSteelBlue    = #B0C4DE;
+static final int LightYellow       = #FFFFE0;
+static final int Lime              = #00FF00;
+static final int LimeGreen         = #32CD32;
+static final int Linen             = #FAF0E6;
+static final int Magenta           = #FF00FF;
+static final int Maroon            = #800000;
+static final int MediumAquaMarine  = #66CDAA;
+static final int MediumBlue        = #0000CD;
+static final int MediumOrchid      = #BA55D3;
+static final int MediumPurple      = #9370D8;
+static final int MediumSeaGreen    = #3CB371;
+static final int MediumSlateBlue   = #7B68EE;
+static final int MediumSpringGreen = #00FA9A;
+static final int MediumTurquoise   = #48D1CC;
+static final int MediumVioletRed   = #C71585;
+static final int MidnightBlue      = #191970;
+static final int MintCream         = #F5FFFA;
+static final int MistyRose         = #FFE4E1;
+static final int Moccasin          = #FFE4B5;
+static final int NavajoWhite       = #FFDEAD;
+static final int Navy              = #000080;
+static final int OldLace           = #FDF5E6;
+static final int Olive             = #808000;
+static final int OliveDrab         = #6B8E23;
+static final int Orange            = #FFA500;
+static final int OrangeRed         = #FF4500;
+static final int Orchid            = #DA70D6;
+static final int PaleGoldenRod     = #EEE8AA;
+static final int PaleGreen         = #98FB98;
+static final int PaleTurquoise     = #AFEEEE;
+static final int PaleVioletRed     = #D87093;
+static final int PapayaWhip        = #FFEFD5;
+static final int PeachPuff         = #FFDAB9;
+static final int Peru              = #CD853F;
+static final int Pink              = #FFC0CB;
+static final int Plum              = #DDA0DD;
+static final int PowderBlue        = #B0E0E6;
+static final int Purple            = #800080;
+static final int Red               = #FF0000;
+static final int RosyBrown         = #BC8F8F;
+static final int RoyalBlue         = #4169E1;
+static final int SaddleBrown       = #8B4513;
+static final int Salmon            = #FA8072;
+static final int SandyBrown        = #F4A460;
+static final int SeaGreen          = #2E8B57;
+static final int SeaShell          = #FFF5EE;
+static final int Sienna            = #A0522D;
+static final int Silver            = #C0C0C0;
+static final int SkyBlue           = #87CEEB;
+static final int SlateBlue         = #6A5ACD;
+static final int SlateGray         = #708090;
+static final int SlateGrey         = #708090;
+static final int Snow              = #FFFAFA;
+static final int SpringGreen       = #00FF7F;
+static final int SteelBlue         = #4682B4;
+static final int Tan               = #D2B48C;
+static final int Teal              = #008080;
+static final int Thistle           = #D8BFD8;
+static final int Tomato            = #FF6347;
+static final int Turquoise         = #40E0D0;
+static final int Violet            = #EE82EE;
+static final int Wheat             = #F5DEB3;
+static final int White             = #FFFFFF;
+static final int WhiteSmoke        = #F5F5F5;
+static final int Yellow            = #FFFF00;
+static final int YellowGreen       = #9ACD32;
+
+static final color nodeColor     = LightYellow;
+static final color selectColor   = Gold;
+static final color selectedColor = SpringGreen;
+static final color rootColor     = Darkorange;
+static final color fixedColor    = HotPink;
+static final color edgeColor     = 0xFF80A0A0;
+
+////////// TO STRING ///////////////////////////////////////////////
+String toStr(int i) {
+  switch(i) {
+// Event
+    case MOVED:       return "MOVED";
+    case DRAGGED:     return "DRAGGED";
+    case CLICKED:     return "CLICKED";
+    case RELEASED:    return "RELEASED";
+    case START:       return "START";
+    case MOVE:        return "MOVE";
+    case END:         return "END";
+// Motion
+    case HALT:        return "HALT";
+    case MOVING:      return "MOVING";
+    case DRAGGING:    return "DRAGGING";
+    case DRAG:        return "DRAG";
+// Action
+    case NOOP:        return "NOOP";
+    case CLICK:       return "CLICK";
+    case RELEASE:     return "RELEASE";
+    case DOUBLECLICK: return "DOUBLECLICK";
+    case HOLDING:     return "HOLDING";
+    case HOLDED:      return "HOLDED";
+    default:          return "";
+// menu
+    case EXPND:       return "EXPAND";
+    case ROOT:        return "ROOT";
+    case FIX:         return "FIX";
+    case FREE:        return "FREE";
+    case HIDE:        return "HIDE";
+    case INFO:        return "INFO";
+    case NEWREL:      return "NEWREL";    
+    case NEWREL2:     return "NEWREL2";
+    case SQUEEZE:     return "SQUEEZE";
+    case TOPIC:       return "TOPIC";
+// apmenu
+    case FITALL:      return "FITALL";
+    case ALLFIX:      return "ALLFIX";
+    case ALLFREE:     return "ALLFREE";
+    case GROUPSHAPE:  return "GROUPSHAPE";
+    case ALLSAVE:     return "ALLSAVE";
+    case ALLRETRIEVE: return "ALLRETRIEVE";
+    case NEWTOPIC:    return "NEWTOPIC";
+  }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/event.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,437 @@
+boolean isMouse=true;
+int mouse_event=0, 
+    mouse_motion=HALT, 
+    mouse_action=NOOP;
+int touch_event=0, 
+    touch_motion=UP, 
+    touch_action=NOOP;
+int touch_count;
+boolean is_down;
+boolean touchedH,  touchedC,  touchedD;
+float   startH,    startC,    startD;
+float elapsedTimeHold, elapsedTimeClick, elapsedTimeDoubleClick;
+float holdX,    holdY;
+
+////////// EVENT ACTION /////////////////////////////
+void doMove(float x, float y) {
+  event = "Move";
+  if (menu_active || apmenu_active || edgmenu_active) return;
+  adjustCoordinates(Math.round(x), Math.round(y));
+  pressStartX = pointX;
+  pressStartY = pointY;
+  doSelectEdge();
+  doSelectNode();
+}
+
+void doClick(float x, float y) {
+  event = "Click";
+  adjustCoordinates(Math.round(x), Math.round(y));
+  pressStartX = pointX;
+  pressStartY = pointY;
+  doSelectEdge();
+  doSelectNode();
+  if (mouseButton == RIGHT) {
+    if (selection != null && selected == null && !menu_active) 
+      doBeginMenu();
+    else if (sel_edge != null && !edgmenu_active)
+      doBeginEdgMenu();
+    else if (selection == null && !apmenu_active)
+      doBeginApMenu();
+  }
+  else if (from_node == null && selection != null && selected == null) {
+    if (debug) println("doExpandNode");
+    doExpandNode(selection);
+  }
+  else if (from_node != null) {
+    if (debug) println("new_relation("+from_node.name+", "+selection.name+")");
+    String id      = from_node.id;
+    String name    = from_node.name;
+    String grp     = from_node.grp;
+    String proj    = from_node.proj;
+    if (grp==null) grp="";
+    String to_id, to_name, to_grp, to_proj;
+    if (selection != null) {
+      to_node        = selection; 
+      to_id   = to_node.id;
+      to_name = to_node.name;
+      to_grp  = to_node.grp;
+      to_proj = to_node.proj;
+      if (to_grp==null) to_grp="";
+      to_node = null;
+    }
+    else {
+      to_id   = "";
+      to_name = "";
+      to_grp  = "";
+      to_proj = "";      
+    }
+    from_node = null;
+    if (debug) println("javascript.new_relation2("+id+", "+name+", "+grp+", "+proj+", "+to_id+", "+to_name+", "+to_grp+", "+to_proj+")");
+    javascript.new_relation(id, name, grp, proj, to_id, to_name, to_grp, to_proj);
+  }
+  else if (menu_active && selected != null) {
+    if (debug) println("doMenu " + nls(menu,ln) );
+    doMenu();
+  }
+  else if (edgmenu_active && sel_edge != null) {
+    doEdgMenu();
+  }
+  else if (apmenu_active) {
+    doApMenu();
+  }
+  dragging = false;
+}
+
+void doDoubleClick(float x, float y) {
+  event = "DoubleClick";
+  if (selection != null && selected == null) {
+    rootNode(selection);
+  }
+}
+
+void doHold(float x, float y) {
+  event = "Hold";
+  if (selection != null && selected == null) {
+    doBeginMenu();
+  } 
+  else if (sel_edge != null) {
+    doBeginEdgMenu();
+  }
+  else if (selection == null) {
+    doBeginApMenu();
+  }
+}
+
+void doDrag(float x, float y) {
+  event = "Drag";
+  adjustCoordinates(Math.round(x), Math.round(y));
+  if (menu_active == false && apmenu_active == false) {
+    if (selection != null) {
+      doDragNode();
+      dragging = false;
+    } 
+    else {
+      dragging = true;
+    }
+  }
+}
+
+void doReleased(float x, float y) {
+  event = "Released";
+  adjustCoordinates(Math.round(x), Math.round(y));
+  if (dragging) {
+    doUpdateNodePosition();
+  }  
+  dragging = false;
+  menu = 0;
+  selection = null;
+  previous_selection = null;
+  //javascript.end_select();
+}
+
+////////// MOUSE EVENT DETECTION ///////////////////////////////////
+void mouseMoved() {
+  //if (debug) print("\tmouseMoved");
+  is_down = false;
+  if (!isMouse) return;
+  mouse_event  = MOVED;
+  mouse_motion = MOVING;
+  doEvent(mouseX, mouseY);
+}
+
+void mouseDragged() {
+  //if (debug) print("\tmouseDragged");
+  is_down = true;
+  if (!isMouse) return;
+  if (mouse_action==HOLDED) { //Handling wrong dragging status after holding
+    //if (debug) print("\tMoved");
+    is_down = false;
+    mouse_event  = MOVED;
+    mouse_motion = MOVING;
+  } else {
+    //if (debug) print("\tDragged");
+    mouse_event  = DRAGGED;
+    mouse_motion = DRAGGING;
+  }
+  doEvent(mouseX, mouseY);
+}
+void mouseClicked() {
+  //if (debug) print("\nmouseClicked");
+  is_down = true;
+  if (!isMouse) return;
+  mouse_event = CLICKED;
+  doEvent(mouseX, mouseY);
+}
+void mouseReleased() {
+  //if (debug) print("\nmouseReleased");
+  is_down = false;
+  if (!isMouse) return;
+  mouse_event = RELEASED;
+  doEvent(mouseX, mouseY);
+}
+
+////////// MOUSE EVENT HANDLING ////////////////////////////
+void doEvent(float x, float y) {
+  //if (debug) print( "\ndoEvent event=" + toStr(mouse_event) + "\taction=" + toStr(mouse_action) + "\tmotion=" + toStr(mouse_motion) );
+  switch (mouse_event) {
+    case CLICKED:
+      //if (debug) print("\nCLICKED");
+      checkClickTimer(x, y);
+      checkDoubleClickTimer(x, y);
+      break;
+    case RELEASED:
+      //if (debug) print("\nRELEASED");
+      checkClickTimer(x, y);
+      if (mouse_motion == DRAGGING) {      // TO DETECT RELEASE AFTER DRAGGINNG
+        mouse_action = RELEASE;
+        doReleased(x, y);
+        stopClickTimer(); 
+        stopDoubleClickTimer(); 
+        stopHoldTimer();
+      } else if (mouse_motion == MOVING) { // TO CATCH CLICK DUE TO NO CLICKED AFTER HOLDING
+        mouse_action = CLICK;
+        doClick(x, y);
+        stopClickTimer(); 
+        stopDoubleClickTimer(); 
+        stopHoldTimer();
+      }
+      break;
+    case MOVED:
+      //if (debug) print("\nMOVED");
+      mouse_motion = MOVING;
+      doMove(x, y);
+      stopClickTimer(); 
+      stopDoubleClickTimer(); 
+      stopHoldTimer();
+      break;
+    case DRAGGED:
+      //if (debug) print("\nDRAGGED");
+      mouse_motion = DRAGGING;
+      doDrag(x, y);
+      stopClickTimer(); 
+      stopDoubleClickTimer(); 
+      stopHoldTimer();
+      break;
+    default:
+  }
+}
+////////// TOUCH EVENT DETECTION ///////////////////////////////////
+// Following functions are used for iPad(iOS)
+
+void touchMove(TouchEvent touchEvent) { //Comment out while debugging. These routines are for iPad.
+  //if (debug) print("\ttouchMove");
+  isMouse=false;
+  is_down = true;
+  touch_event = MOVE;
+  // draw circles at where fingers touch
+  touch_count = touchEvent.touches.length;
+  for (int i = 0; i < touch_count; i++) {
+    int x = touchEvent.touches[i].offsetX;
+    int y = touchEvent.touches[i].offsetY;
+    fill(128,128,128);
+    ellipse(x, y, 10, 10);
+  }
+  if (touch_count > 1) {
+    currtXa = touchEvent.touches[0].offsetX;
+    currtYa = touchEvent.touches[0].offsetY;
+    currtXb = touchEvent.touches[1].offsetX;
+    currtYb = touchEvent.touches[1].offsetY;
+    currt_dist = Math.sqrt((currtXa - currtXb)*(currtXa - currtXb) + (currtYa - currtYb)*(currtYa - currtYb));
+    scl = currt_dist / start_dist;
+    zoom(scl);
+    javascript.setscale(scl);
+  }
+  if (touch_count==1) {
+    touchX = touchEvent.touches[0].offsetX;
+    touchY = touchEvent.touches[0].offsetY;
+    fill(128,64,64);
+    ellipse(touchX, touchY, 10, 10);
+    doTouch(touchX, touchY);
+  }
+}
+
+void touchEnd(TouchEvent touchEvent) {
+  //if (debug) print("\ttouchEnd");
+  touch_event = END;
+  isMouse=false;
+  is_down = false;
+  if (touch_count==1) {
+    doTouch(touchX, touchY);
+  }
+}
+
+void touchStart(TouchEvent touchEvent) {
+  //if (debug) print("\ttouchStart");
+  isMouse=false;
+  is_down = true;
+  // draw circles at where fingers touch
+  touch_count = touchEvent.touches.length;
+  for (int i = 0; i < touch_count; i++) {
+    int x = touchEvent.touches[i].offsetX;
+    int y = touchEvent.touches[i].offsetY;
+    fill(0,128,64);
+    ellipse(x, y, 10, 10);
+  }
+  if (touch_count > 1) {
+    startXa = touchEvent.touches[0].offsetX;
+    startYa = touchEvent.touches[0].offsetY;
+    startXb = touchEvent.touches[1].offsetX;
+    startYb = touchEvent.touches[1].offsetY;
+    start_dist = Math.sqrt((startXa - startXb)*(startXa - startXb) + (startYa - startYb)*(startYa - startYb));
+  }
+  touchX = touchEvent.touches[0].offsetX;
+  touchY = touchEvent.touches[0].offsetY;
+  fill(64,128,255);
+  ellipse(touchX, touchY, 10, 10);
+  touch_event = START;
+  doTouch(touchX, touchY);
+}
+
+////////// TOUCH EVENT HANDLING ////////////////////////////////
+void doTouch(float x, float y) {
+  //if (debug) print("\ndoTouch event=" + toStr(touch_event));
+  switch (touch_event) {
+    case START:
+      dragmode = !dragmode;
+      checkClickTimer(x, y);
+      checkDoubleClickTimer(x, y);
+      break;
+    case END:
+      mouse_motion = (mouse_motion==DRAGGING)? DRAG:UP;
+      checkClickTimer(x, y);
+      if (mouse_motion == DRAG) {      // TO DETECT RELEASE AFTER DRAGGINNG
+        mouse_action = RELEASE;
+        doReleased(x, y);
+      } 
+      stopClickTimer(); 
+      stopDoubleClickTimer(); 
+      stopHoldTimer();
+      break;
+    case MOVE:
+      if (dragmode && selection==null) {
+        mouse_motion = MOVING;
+        doMove(x, y);
+      }
+      else {
+        mouse_motion = DRAGGING;
+        doDrag(x, y);
+      }
+      stopClickTimer(); 
+      stopDoubleClickTimer(); 
+      stopHoldTimer();
+      break;
+    default:
+  }
+}
+
+////////// TIMER ///////////////////////////////////////////////
+void checkClickTimer(float x, float y) {
+  float current = millis()/1000F;
+  elapsedTimeClick = current - startC; // Get elapsed time in seconds 
+  //if (debug) print("\n"+current+"\tCheck\tClick\ttouched="+touchedC+"\tstart="+startC+"\telapsed="+elapsedTimeClick);
+  if ( !touchedC ) {  // IF Not touched
+    startClickTimer();
+  }
+  else if ( touchedC ) {
+    if ( elapsedTimeClick < 1 ) {
+      //if (debug) print("\nCLICK");
+      mouse_motion = HALT;
+      mouse_action = CLICK;
+      if (isMouse) doClick(mouseX, mouseY);
+      else        doClick(touchX, touchY);
+      stopClickTimer();
+    }
+  }
+}
+void startClickTimer() {
+  float current = millis()/1000F;
+  touchedC = true;  
+  startC = millis()/1000F; // Get current time
+  elapsedTimeClick = 0;
+  //if (debug) print("\n"+current+"\tStart\tClick");
+}
+
+void stopClickTimer() {
+  touchedC  = false;
+  elapsedTimeClick = 0;
+}
+
+void checkDoubleClickTimer(float x, float y) {  // This routine works only under PC Firefox. Other environment doesn't work.
+  float current = millis()/1000F;
+  elapsedTimeDoubleClick = current - startD; // Get elapsed time in seconds 
+  //if (debug) print("\n"+current+"\tCheck\tDoubleClick\ttouched="+touchedD+"\tstart="+startD+"\telapsed="+elapsedTimeDoubleClick);
+  if ( !touchedD ) {  // IF Not touched
+    startDoubleClickTimer();
+  }
+  else if ( touchedD ) {
+    if ( elapsedTimeDoubleClick < 1 ) {
+      //if (debug) print("\nDOUBLE CLICK");
+      mouse_motion = HALT;
+      mouse_action = DOUBLECLICK;
+      if (isMouse) doDoubleClick(mouseX, mouseY);
+      else        doDoubleClick(touchX, touchY);
+      stopDoubleClickTimer();
+    }
+  }
+}
+
+void startDoubleClickTimer() {
+  float current = millis()/1000F;
+  touchedD = true;
+  startD = millis()/1000F; // Get current time
+  elapsedTimeDoubleClick = 0;  
+  //if (debug) print("\n"+current+"\tStart\tDoubleClick");
+}
+
+void stopDoubleClickTimer() {  // ditto.
+  touchedD  = false;
+  elapsedTimeDoubleClick = 0;
+}
+
+void checkHoldTimer() {
+  float current = millis()/1000F;
+  elapsedTimeHold = current - startH; // Get elapsed time in seconds 
+  /*if (Math.round(elapsedTimeHold*10)%10==0)
+    if (debug) print("\n"+current+"\tCheck\tHold\ttouched="+touchedH+"\tstart="+startH+"\telapsed="+elapsedTimeHold);
+  */
+  if ((isMouse && mousePressed && !(mouse_motion==MOVING && mouse_action==HOLDED)) 
+  || (!isMouse  && is_down)) {
+    if ( !touchedH ) {  // IF Not touched
+      startHoldTimer();
+      holdX = (isMouse)? mouseX:touchX;
+      holdY = (isMouse)? mouseY:touchY;
+    }
+    else if ( touchedH ) {
+      float currentX = (isMouse)? mouseX:touchX;
+      float currentY = (isMouse)? mouseY:touchY;
+      float dist2 = (currentX - holdX)*(currentX - holdX) + (currentY - holdY)*(currentY - holdY);
+      if ( dist2 < 16 && elapsedTimeHold > 0.5 ) {
+        //if (debug) print("\nHOLD");
+        mouse_motion = HALT;
+        mouse_action = HOLDING;
+        if (isMouse) doHold(mouseX, mouseY);
+        else        doHold(touchX, touchY);
+        mouse_action = HOLDED;
+        stopHoldTimer();
+      }
+    }
+  }
+  else {
+    stopHoldTimer();
+  }
+}
+
+void startHoldTimer() {
+  touchedH = true;
+  float current = millis()/1000F;
+  startH = current; // Get current time 
+  elapsedTimeHold = 0;
+  //if (debug) print("\n"+current+"\tStart\tHold");
+}
+
+void stopHoldTimer() {
+  touchedH = false;
+  elapsedTimeHold = 0;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/initialdata.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,10 @@
+////////  LOAD DATA  ///////////////////////////////////////////////////////////
+void loadData() {
+
+}
+
+void setNodeShapes() {
+   
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/javascript.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,27 @@
+////////  JAVASCRIPT  //////////////////////////////////////////////////////////
+interface JavaScript {
+  void adjacentnodes(String id, String pj, String adj,  String bd);
+  void selectnode(String id, String pj); 
+  void selectedge(String s);
+  void topicnode(String s);
+  void setscale(float s);
+  void group_shapes();
+  void allbackup(String s);
+  void allretrieve(String s);
+  void new_topic();  
+  void pedia();
+  void set_mode(String lang, boolean d, boolean p, boolean u, String b, boolean ed);
+  void countassoc(String id, String pj);
+  void new_relation(String id, String name, String grp, String proj, String to_id, String to_name, String to_grp, String to_proj);  
+  void new_select(String id, String proj);
+  void startexpand();
+  void endexpand();
+  void username(String uid);
+}
+
+void bindJavascript(JavaScript js) {
+  javascript = js;
+}
+
+JavaScript javascript;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/menu.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,646 @@
+////////  USER INTERFACE  /////////////////////////////////////////////////
+void adjustCoordinates(int x, int y) {
+  scrnX  = Math.round( x - WORLD_SIZE_W/2 );
+  scrnY  = Math.round( y - WORLD_SIZE_H/2 );
+  pointX = Math.round( scrnX/factor );
+  pointY = Math.round( scrnY/factor );
+}
+
+void doSelectNode() {
+  //func = "select node";
+  Iterator it = nodes.entrySet().iterator(); 
+  selection = null;
+  while (it.hasNext ()) {
+    Map.Entry e = (Map.Entry)it.next();
+    String k  = (String) e.getKey();
+    Node n = (Node) e.getValue();  
+    //if (debug) print(n.name + "\t");
+    if ( (! "MARK".equals(n.shape)) && pointerOverRect(n.x, n.y, n.w, n.h) ||
+		"MARK".equals(n.shape) && pointerOverRect(n.x + n.w/factor/2, n.y, n.w, n.h) ) {
+      //func = "select node";
+      sel_edge = null;
+      selection = n;
+      if ( selection == null || 
+          previous_selection == null ||  
+          ! previous_selection.id.equals(selection.id) ) {
+        if (nodetrace) println("NEW SELECTION "+ selection.name);
+        previous_selection = selection;
+        if (javascript != null) javascript.new_select(selection.id, selection.proj);
+      }
+      //sel_edge = null;
+      selectNode(selection);
+      //selected  = null;
+    }    
+  }
+}
+
+void doSelectEdge() {
+  //func = "select edge";
+  if (selection == null) {
+    Iterator it = edges.entrySet().iterator(); 
+    sel_edge = null;
+    while (it.hasNext ()) {
+      Map.Entry ie = (Map.Entry)it.next();
+      String k  = (String) ie.getKey();
+      Edge e = (Edge) ie.getValue();
+      if (pointerOverLine(e.from, e.to) && e.dup==0) {
+        //func = "select edge";
+        //if (debug) println("select edge line "+e.asc_id);
+        sel_edge = e;
+      }
+      else if (pointerOverArc( e )) {
+        //func = "select edge";
+        //if (debug) println("select edge arc  "+e.asc_id);
+        sel_edge = e;      
+      }
+    }
+  }
+}
+
+void move_vizgroup(int dx, int dy) {
+  if (vizgroup!=null){
+    for (int i = 0; i < vizgroup.size(); i++) {
+      Node n = (Node) vizgroup.get(i);
+      int originalX = n.x;
+      int originalY = n.y;
+      n.position(originalX + dx, originalY + dy);
+    }  
+  }
+}
+
+void doUpdateNodePosition() {
+  //func =  "update node position";
+  Iterator it = nodes.entrySet().iterator(); 
+  while (it.hasNext ()) {
+    Map.Entry e = (Map.Entry)it.next();
+    String k  = (String) e.getKey();
+    Node n = (Node) e.getValue(); 
+    n.x += pointX - pressStartX;
+    n.y += pointY - pressStartY;
+    n.position(n.x, n.y);
+  }
+}
+
+boolean pointerOverRect( float x, float y, float w, float h ) {
+  int pX, pY;
+  if (!isMouse) {
+    pX = touchX; 
+    pY = touchY;
+  }
+  else {
+    pX = mouseX; 
+    pY = mouseY;
+  }
+  adjustCoordinates(pX, pY);
+  boolean isOver = false;
+  if ( pointX >= x - w/factor/2 && pointX <= x + w/factor/2 && 
+    pointY >= y - h/factor/2 && pointY <= y + h/factor/2 ) {
+    isOver = true;
+  } 
+  return isOver;
+}
+
+boolean pointerOverLine( Node from, Node to ) {
+  int pX, pY;
+  if (!isMouse) {
+    pX = touchX; 
+    pY = touchY;
+  }
+  else {
+    pX = mouseX; 
+    pY = mouseY;
+  }
+  adjustCoordinates( pX, pY );
+  boolean isOver  = false;
+  Vector2D pFrom  = new Vector2D(from.x, from.y);
+  Vector2D pTo    = new Vector2D(to.x, to.y);
+  Vector2D pointer = new Vector2D(pointX, pointY);
+  float distance = pointer.distance(pFrom, pTo);
+  if (distance < 2) {
+    isOver = true;
+  } 
+  return isOver;
+}
+
+boolean pointerOverArc( Edge e ) {
+  //if (e.dup==0) return false;
+  boolean isOver  = false;
+  int ptX, ptY;
+  float sigma = 0.1;
+  if (!isMouse) {
+    ptX = touchX; 
+    ptY = touchY;
+  }
+  else {
+    ptX = mouseX; 
+    ptY = mouseY;
+  }
+  adjustCoordinates( ptX, ptY );
+  // based on screen coordination. center(0,0) rightbottom(WoORLD_SIZE_W/2, WORLD_SIZE_H/2)
+  if (e.from==null || e.to==null || e.centr==null) return false;
+  fromX = factor * (e.from.x);
+  fromY = factor * (e.from.y);
+  toX   = factor * (e.to.x);
+  toY   = factor * (e.to.y);
+  cX    = factor * (e.centr.x);
+  cY    = factor * (e.centr.y);
+  PVector P    = new PVector(scrnX, scrnY);
+  PVector Cntr = new PVector(cX, cY);
+  if (P != null)  phai   = e.theta(Cntr, P);
+  PVector P1   = new PVector(fromX, fromY);
+  if (P1 != null) theta1 = e.theta(Cntr, P1);
+  PVector P2   = new PVector(toX, toY);
+  if (P2 != null) theta2 = e.theta(Cntr, P2);
+  PVector T = PVector.sub(P, Cntr);
+  float D   = dist(P1.x, P1.y, Cntr.x, Cntr.y);
+  T.normalize(); 
+  T.mult(D);
+  T.add(Cntr);
+  T.sub(P);
+  float w = 0;
+  if (sqrt(sq(T.x) + sq(T.y)) < 3) {
+    if (edgetrace) println("initial theta1="+round(degrees(theta1))+" phai="+round(degrees(phai))+" theta2="+round(degrees(theta2)));
+    if (-TWO_PI<theta1 && theta1<0) theta1 += TWO_PI;
+    if (-TWO_PI<theta2 && theta2<0) theta2 += TWO_PI;
+    if (-TWO_PI<phai && phai<0) phai += TWO_PI;
+    float df = theta2 - theta1;
+    if (-TWO_PI<df && df<0) df += TWO_PI;
+    if (df > PI) {
+      w      = theta2;
+      theta2 = theta1;
+      theta1 = w;
+    }
+    if((( -TWO_PI < phai-theta1 && phai-theta1 < -PI) ||
+        ( 0 < phai-theta1 && phai-theta1 < PI)) &&
+       (( -TWO_PI < theta2-phai && theta2-phai < -PI) ||
+        ( 0 < theta2-phai && theta2-phai < PI)))
+      isOver = true;
+  }
+  return isOver;
+}
+
+void doBeginMenu() {
+  //func =  "begin menu";
+  menu_active = true;
+  menuX = scrnX;
+  menuY = scrnY;
+  selected = selection;
+  selection = null;
+  previous_selection = null;
+  //javascript.end_select();
+}
+
+void doMenu() {
+  if (debug) println( "menu=" + nls(menu,ln) );
+  switch (menu) {
+    case ROOT:
+      rootNode(selected); 
+      break;
+    case HIDE:
+      if (!selected.root) hideNode(selected);
+      break;
+    case INFO:
+      infoNode(selected);
+      break;
+    case NEWREL:
+      newRelation(selected);
+      break;
+    case SQUEEZE:
+      squeezeNode(selected);
+  }
+  menu_active = false;
+  selected  = null;
+  dragging = false;
+}
+
+void doBeginEdgMenu() {
+  //func =  "begin edge menu";
+  edgmenu_active = true;
+  menuX = scrnX;
+  menuY = scrnY;
+  selected_edge = sel_edge;
+}
+
+void doEdgMenu() {
+  if (edgetrace) println( "edge menu=" + nls(edgmenu,ln) );
+  switch (edgmenu) {
+    case HIDEEDGE:
+      hideEdge(sel_edge);
+      break;
+    case EDITEDGE:
+      infoEdge(sel_edge); 
+  }
+  edgmenu_active = false;
+  sel_edge = null;
+  dragging = false;
+}
+
+void doBeginApMenu() {
+  //func =  "begin ap menu";
+  apmenu_active = true;
+  menuX = scrnX;
+  menuY = scrnY;
+}
+
+void doApMenu() {
+  //func =  "execute ap menu";
+  switch (apmenu) {
+    case FITALL:
+      fitAll();
+      break;
+    case ALLFIX:
+      allFix();
+      break;
+    case ALLFREE:
+      allFree();
+      break;
+    case GROUPSHAPE:
+      groupShape();
+      break;
+    case ALLSAVE:
+      allSave();
+      break;
+    case ALLRETRIEVE:
+      allRetrieve();
+      break;
+    case NEWTOPIC:
+      newTopic();
+      break;
+    case PEDIA:
+      pedia();
+      break;
+    case MODE:
+      setMode();
+  }
+  apmenu_active = false;
+}
+
+void setMode() {
+  if (debug) println("setMode");
+  if (javascript!=null) {
+    String  lang = ln;
+    boolean d = debug,
+            p = personal,
+            u = showusername,
+            ed = editmode;
+    String  b = both;
+    javascript.set_mode( lang, d, p, u, b, ed );
+  } 
+}
+
+void saveMode(String lang, String d, String p, String u, String b, String ed){
+  if (debug) println("\t"+lang+" "+d+" "+p+" "+u+" "+b+" "+ed);
+  if (lang!=null) ln = lang;
+  if ("debug".equals(d)) {
+    debug=true; 
+    nodetrace=true;
+    edgetrace=true;
+  }
+  else {
+    debug=false;
+    nodetrace=false;  
+    edgetrace=false;
+  }  
+  if ("personal".equals(p)) 
+    personal=true; else personal=false;  
+  if ("showusername".equals(u)) 
+    showusername=true; else showusername=false;  
+  if ("edit".equals(ed)) {
+    editmode=true; 
+  }
+  else {
+    editmode=false;
+  }
+  both = b;
+  if (debug) println("\tdebug="+debug+" personal="+personal+" showusername="+showusername+" both="+both+" editmode="+editmode);
+}
+
+void doExpandNode(Node node) {
+//  funcTrace("Expandng...");
+  boolean isfree=true;
+  if(node.fixed) isfree=false;
+  node.fix();
+  expandNode(node, both); // "" means one direction "both" means bidirection expansion
+  selection = null;
+  previous_selection = null;
+}
+
+void doFixNode(Node node) {
+  if (debug) println("doFixNode " + node.name);
+  //func =  "fix node";
+  node.fix();
+  selection = null;
+  previous_selection = null;
+}
+
+void doFreeNode(Node node) {
+  if (debug) println("doFreeNode " + node.name);
+  //func =  "free node";
+  node.free();
+  selection = null;
+  previous_selection = null;
+}
+
+void doDragNode() {
+  //func =  "drag node";
+  draggingnode = true;
+  int dx = pointX - selection.x;
+  int dy = pointY - selection.y;
+  selection.fix();
+  //if (debug) println("mouseDragged " + selection.name + "\t(" + originalX + ",\t" + originalY + ") to (" + pointX + ",\t" + pointY + ")");
+  if (selection.vizgroup == null) {
+    selection.position(pointX, pointY);
+  } else {
+    move_vizgroup(dx, dy);
+  }
+  damper = 1.0;
+}
+
+////////  MENU  ///////////////////////////////////////////////////////////
+boolean isOverMenu(float x, float y, int w, int h) {
+  int pX, pY;
+  if (!isMouse) {
+    pX = touchX; 
+    pY = touchY;
+  }
+  else {
+    pX = mouseX; 
+    pY = mouseY;
+  }
+  adjustCoordinates(pX, pY);
+  if ((scrnX) >= x - w/2 && (scrnX) <= x + w/2 &&
+      (scrnY) >= y - h/2 && (scrnY) <= y + h/2) {
+    return true;
+  } 
+  else {
+    return false;
+  }
+}
+
+Vector2D adjustMenuPosition(int x, int y, int w, int h, int rows) {
+  float WORLD_HALF_SIZE_W = (WORLD_SIZE_W - w)/2;
+  float WORLD_HALF_SIZE_H = (WORLD_SIZE_H - h)/2;
+  float new_x = x;
+  float new_y = y;
+  if ( x < -WORLD_HALF_SIZE_W )            new_x = -WORLD_HALF_SIZE_W;
+  if ( x >  WORLD_HALF_SIZE_W )            new_x =  WORLD_HALF_SIZE_W;
+  if ( y < -WORLD_HALF_SIZE_H )            new_y = -WORLD_HALF_SIZE_H;
+  if ( y >  WORLD_HALF_SIZE_H - rows * h ) new_y =  WORLD_HALF_SIZE_H - rows * h;
+  Vector2D newPosition = new Vector2D(new_x, new_y);
+  return newPosition;
+}
+
+void showMenu() {
+  stroke(Silver);
+  strokeWeight(0.1);
+  int w = 100;
+  int h = 25;
+  Vector2D menuPosition;
+  float x0, y0;
+  if (editmode) {
+    menuPosition = adjustMenuPosition( menuX, menuY, w, h, 5);
+    x0 = menuPosition.x;
+    y0 = menuPosition.y;
+    int i = 0;
+    menuItem(x0, y0 + h*i++, w, h, nls(INFO,ln) );  
+    menuItem(x0, y0 + h*i++, w, h, nls(ROOT,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(SQUEEZE,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(HIDE,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(NEWREL,ln) );
+    if (isOverMenu(x0, y0, w, h)) {
+      menuHighlite(     x0, y0,   w, h, nls(INFO,ln) );    menu = INFO;  } 
+    else if (isOverMenu(x0, y0 + h, w, h)) {
+      menuHighlite(     x0, y0 + h, w, h, nls(ROOT,ln) );    menu = ROOT;  }  
+    else if (isOverMenu(x0, y0 + h*2, w, h)) {
+      menuHighlite(     x0, y0 + h*2, w, h, nls(SQUEEZE,ln) ); menu = SQUEEZE;  } 
+    else if (isOverMenu(x0, y0 + h*3, w, h)) {
+      menuHighlite(     x0, y0 + h*3, w, h, nls(HIDE,ln) );    menu = HIDE;  } 
+    else if (isOverMenu(x0, y0 + h*4, w, h)) {
+      menuHighlite(     x0, y0 + h*4, w, h, nls(NEWREL,ln) );  menu = NEWREL;  }  
+    else {
+      menu        = 0;    menu_active = false;    selected    = null;  }
+  }
+  else {
+    menuPosition = adjustMenuPosition( menuX, menuY, w, h, 2);
+    x0 = menuPosition.x;
+    y0 = menuPosition.y;
+    int i = 0;
+    menuItem(x0, y0 + h*i++, w, h, nls(INFO,ln) );  
+    menuItem(x0, y0 + h*i++, w, h, nls(ROOT,ln) );
+    if (isOverMenu(     x0, y0,       w, h)) {
+      menuHighlite(     x0, y0,       w, h, nls(INFO,ln) );    menu = INFO; } 
+    else if (isOverMenu(x0, y0 + h*1, w, h)) {
+      menuHighlite(     x0, y0 + h*1, w, h, nls(ROOT,ln) );    menu = ROOT;  } 
+    else {
+      menu        = 0;    menu_active = false;    selected    = null;  }    
+  }
+}
+
+void showEdgMenu() {
+  if (menu_active || apmenu_active) return;
+  stroke(Silver);
+  strokeWeight(0.1);
+  int w = 100;
+  int h = 25;
+  Vector2D menuPosition;
+  float x0, y0;
+  if (editmode) {
+    menuPosition = adjustMenuPosition( menuX, menuY, w, h, 2 );
+    x0 = menuPosition.x;
+    y0 = menuPosition.y;  
+    menuItem(x0, y0,       w, h, nls(EDITEDGE,ln) );
+    //menuItem(x0, y0 + h,   w, h, nls(HIDEEDGE,ln) );
+    if (isOverMenu(     x0, y0,     w, h)) {
+      menuHighlite(     x0, y0,     w, h, nls(EDITEDGE,ln) ); edgmenu = EDITEDGE;  } 
+    /*
+    else if (isOverMenu(x0, y0 + h, w, h)) {
+      menuHighlite(     x0, y0 + h, w, h, nls(HIDEEDGE,ln) ); edgmenu = HIDEEDGE;  } 
+    */
+    else {
+      edgmenu        = 0;    edgmenu_active = false;    selected_edge  = null;  
+    }
+  }
+}
+
+void showApMenu() {
+  if (menu_active) return;
+  stroke(Silver);
+  strokeWeight(0.1);
+  int w = 100;
+  int h = 25;
+  Vector2D menuPosition;
+  float x0, y0;
+  if (editmode) {
+    menuPosition = adjustMenuPosition( menuX, menuY, w, h, 9 );
+    x0 = menuPosition.x;
+    y0 = menuPosition.y;  
+    int i = 0;
+    menuItem(x0, y0 + h*i++, w, h, nls(FITALL,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(NEWTOPIC,ln) );  
+    menuItem(x0, y0 + h*i++, w, h, nls(PEDIA,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(ALLFREE,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(ALLFIX,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(GROUPSHAPE,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(ALLSAVE,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(ALLRETRIEVE,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(MODE,ln) );
+    if (isOverMenu(     x0, y0 + h*0, w, h)) {
+      menuHighlite(     x0, y0 + h*0, w, h, nls(FITALL,ln) );     apmenu = FITALL;  } 
+    else if (isOverMenu(x0, y0 + h*1, w, h)) {
+      menuHighlite(     x0, y0 + h*1, w, h, nls(NEWTOPIC,ln) );   apmenu = NEWTOPIC;  } 
+    else if (isOverMenu(x0, y0 + h*2, w, h)) {
+      menuHighlite(     x0, y0 + h*2, w, h, nls(PEDIA,ln) );      apmenu = PEDIA;  } 
+    else if (isOverMenu(x0, y0 + h*3, w, h)) {
+      menuHighlite(     x0, y0 + h*3, w, h, nls(ALLFREE,ln) );    apmenu = ALLFREE;  } 
+    else if (isOverMenu(x0, y0 + h*4, w, h)) {
+      menuHighlite(     x0, y0 + h*4, w, h, nls(ALLFIX,ln) );     apmenu = ALLFIX;  }
+    else if (isOverMenu(x0, y0 + h*5, w, h)) {
+      menuHighlite(     x0, y0 + h*5, w, h, nls(GROUPSHAPE,ln) ); apmenu = GROUPSHAPE;  } 
+    else if (isOverMenu(x0, y0 + h*6, w, h)) {
+      menuHighlite(     x0, y0 + h*6, w, h, nls(ALLSAVE,ln) );    apmenu = ALLSAVE;  } 
+    else if (isOverMenu(x0, y0 + h*7, w, h)) {
+      menuHighlite(     x0, y0 + h*7, w, h, nls(ALLRETRIEVE,ln)); apmenu = ALLRETRIEVE;  } 
+    else if (isOverMenu(x0, y0 + h*8, w, h)) {
+      menuHighlite(     x0, y0 + h*8, w, h, nls(MODE,ln));        apmenu = MODE;  } 
+    else {
+      apmenu = 0;    apmenu_active = false;  }
+  }
+  else {
+    menuPosition = adjustMenuPosition( menuX, menuY, w, h, 3 );
+    x0 = menuPosition.x;
+    y0 = menuPosition.y;  
+    int i = 0;
+    menuItem(x0, y0 + h*i++, w, h, nls(FITALL,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(PEDIA,ln) );
+    menuItem(x0, y0 + h*i++, w, h, nls(MODE,ln) );
+    if (isOverMenu(     x0, y0 + h*0, w, h)) {
+      menuHighlite(     x0, y0 + h*0, w, h, nls(FITALL,ln) );     apmenu = FITALL;  } 
+    else if (isOverMenu(x0, y0 + h*1, w, h)) {
+      menuHighlite(     x0, y0 + h*1, w, h, nls(PEDIA,ln) );      apmenu = PEDIA;  } 
+    else if (isOverMenu(x0, y0 + h*2, w, h)) {
+      menuHighlite(     x0, y0 + h*2, w, h, nls(MODE,ln));        apmenu = MODE;  } 
+    else {
+      apmenu = 0;    apmenu_active = false;  }
+  }
+}
+
+void menuItem(float x, float y, int w, int h, String name) {
+  fill(White);
+  rectMode(CENTER);
+  rect(x, y, w, h);
+  fill(0);
+  textAlign(CENTER, CENTER);
+  textSize(12);
+  text(name, x, y);
+}
+
+void menuHighlite(float x, float y, int w, int h, String name) {
+  fill(192);
+  rectMode(CENTER);
+  rect(x, y, w, h);
+  fill(White);
+  textAlign(CENTER, CENTER);
+  textSize(12);
+  text(name, x, y);
+}
+
+void fitAll() {
+  //func =  "fit all";
+  float minX=width/2, maxX=-width/2, minY=height/2, maxY=-height/2;
+  float area_width, area_height, area_centerX, area_centerY;
+  selection = null;
+  previous_selection = null;
+  //javascript.end_select();
+  Iterator it = nodes.entrySet().iterator(); 
+  while (it.hasNext ()) {
+    Map.Entry e = (Map.Entry)it.next();
+    String k  = (String) e.getKey();
+    Node n = (Node) e.getValue();
+    if (n.x < minX) minX = n.x;
+    if (n.x > maxX) maxX = n.x;
+    if (n.y < minY) minY = n.y;
+    if (n.y > maxY) maxY = n.y;
+  }  
+  area_width  = maxX - minX;
+  area_height  = maxY - minY;
+  area_centerX = minX + area_width/2;
+  area_centerY = minY + area_height/2;
+  //if (debug) print("width=" + width + "\theight=" + height + "\tX\tmin=" + minX + "\tmax=" + maxX + "\tY\tmin=" + minY + "\tmax=" + maxY + "\n");
+  //if (debug) print("Area\twidth=" + area_width + "\theight=" + area_height + "\tcentr\tx=" + area_centerX + "\ty=" + area_centerY + "\n");
+
+  Iterator it2 = nodes.entrySet().iterator(); 
+  while (it2.hasNext ()) {
+    Map.Entry e = (Map.Entry)it2.next();
+    String k  = (String) e.getKey();
+    Node n = (Node) e.getValue();   
+    n.x -= area_centerX;
+    n.y -= area_centerY;
+    n.position(n.x, n.y);
+  }
+  pressStartX = 0;
+  pressStartY = 0;
+  float sw = width/area_width, sh = height/area_height;
+  float smin = (sw<sh)? sw:sh;
+  float smax = (sw<sh)? sh:sw;
+  scl = smin;
+  if (scl > 4)  scl = 3.2;
+  else          scl *= 0.8; 
+  if (debug) print("scl="+scl+"\tmin="+smin+"\tmax="+smax+"\n");
+  zoom(scl);
+  if (javascript != null) javascript.setscale(scl);
+}
+
+String nls(int i, String ln) {
+  if (ln.equals("ja")){
+    switch(i) {
+  // menu
+      case EXPND:      return "展開";
+      case ROOT:       return "中心ノード";
+      case HIDE:       return "非表示";
+      case INFO:       return "情報";
+      case NEWREL:     return "関係定義";
+      case SQUEEZE:    return "折りたたみ";
+  // apmenu
+      case FITALL:     return "全体表示";
+      case ALLFIX:     return "全粘着";
+      case ALLFREE:    return "全開放";
+      case GROUPSHAPE: return "グループの形";
+      case ALLSAVE:    return "退避";
+      case ALLRETRIEVE: return "回復";
+      case NEWTOPIC:   return "新規トピック";      
+      case MODE:       return "実行モード";
+      case PEDIA:      return "世界大百科事典";
+  // edgemenu
+      case HIDEEDGE:   return "非表示";      
+      case EDITEDGE:   return "情報";      
+      case DELETEEDGE: return "削除";
+    }      
+  }
+  else {
+    switch(i) {
+  // menu
+      case EXPND:      return "EXPAND";
+      case ROOT:       return "ROOT";
+      case HIDE:       return "HIDE";
+      case INFO:       return "INFO";
+      case NEWREL:     return "NEWREL";
+      case SQUEEZE:    return "SQUEEZE";
+  // apmenu
+      case FITALL:     return "FIT ALL";
+      case ALLFIX:     return "ALL FIX";
+      case ALLFREE:    return "ALL FREE";
+      case GROUPSHAPE: return "GROUP SHAPE";
+      case ALLSAVE:    return "SAVE";
+      case ALLRETRIEVE: return "RETRIEVE";
+      case NEWTOPIC:   return "NEW TOPIC";
+      case MODE:       return "MODE";
+      case PEDIA:      return "ENCYCLOPEDIA";
+  // edgemenu
+      case HIDEEDGE:   return "HIDE EDGE";      
+      case EDITEDGE:   return "EDGE INFO";      
+      case DELETEEDGE: return "DELETE EDGE";
+    }
+  }
+  return null;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/model.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,2058 @@
+HashMap<String, UserColor> usercolors   = new HashMap<String, UserColor>();
+HashMap<String, Node> nodes             = new HashMap<String, Node>();
+HashMap<String, Edge> edges             = new HashMap<String, Edge>();
+HashMap<String, NodeShape> nodeshapes   = new HashMap<String, NodeShape>();
+HashMap<String, EdgeShape> edgeshapes   = new HashMap<String, EdgeShape>();
+HashMap<String, Thumb> thumbs           = new HashMap<String, Thumb>();
+HashMap<String, String> connected_edges = new HashMap<String, String>();
+HashMap<String, ImageMap> imageMaps     = new HashMap<String, ImageMap>();
+ArrayList<Node> vizgroup                = new ArrayList<Node>();
+PImage img;
+int bgColor = White;
+int u_clr; //user color
+
+// ADDED BY IRI TO ACCESS NODES AND EDGES FROM JS
+
+HashMap getNodes() {
+  return nodes;
+}
+
+HashMap getEdges() {
+  return edges;
+}
+
+////////  SAVE & LOAD NODES, EDGED  ////////////////////////////////////////////
+void allSave() {
+  String proj, asc_id, a_proj, id, from_proj, name, grp, to_id, to_proj, r_name, r_from, r_to;
+  boolean root, fixed;
+  int x, y, assoc;
+  Node node;
+  Edge edge;
+  String root_str = "";
+  Iterator it = nodes.entrySet().iterator();
+  while (it.hasNext ()) {
+    Map.Entry e = (Map.Entry)it.next();
+    String k  = (String) e.getKey();
+    node = (Node) e.getValue();
+    if (node!=null) {
+      id    = node.id;
+      proj  = node.proj;
+      name  = node.name;
+      grp   = node.grp;
+      assoc = node.assoc;
+      x     = node.x;
+      y     = node.y;
+      root  = node.root;
+      fixed = node.fixed;
+      if (root) 
+        root_str = "\"root\":[{ \"id\":\"" + id + "\", \"proj\":\""+ proj + "\", "
+		  + "\"name\":\""+ name + "\", \"grp\":\""+ grp + "\", " 
+                  + "\"assoc\":" + assoc + ", \"x\":" + x + ", \"y\":" + y +" }]";
+    }
+  }
+  //if (debug) println(root_str);
+  String nodes_str = "\"node\":[ ";
+  it = nodes.entrySet().iterator();
+  while (it.hasNext ()) {
+    Map.Entry e = (Map.Entry)it.next();
+    String k  = (String) e.getKey();
+    node = (Node) e.getValue();
+    if (node!=null) {
+      id    = node.id;
+      proj  = node.proj;
+      name  = node.name;
+      grp   = node.grp;
+      assoc = node.assoc;
+      x     = node.x;
+      y     = node.y;
+      root  = node.root;
+      fixed = node.fixed;
+      nodes_str += "{ \"id\":\"" + id + "\", \"proj\":\"" + proj + "\", "
+		 + "\"name\":\""+ name + "\", \"grp\":\""+ grp + "\", " 
+                 + "\"assoc\":" + assoc + ", \"x\":" + x + ", \"y\":" + y +" },";
+    }
+  }
+  //nodes_str += " {} ]";
+  nodes_str = nodes_str.substring(0, nodes_str.length()-1) + " ]";
+  //if (debug) println(nodes_str);
+  String edges_str = "\"edge\":[ ";
+  it = edges.entrySet().iterator();
+  while (it.hasNext ()) {
+    Map.Entry e = (Map.Entry)it.next();
+    String k  = (String) e.getKey();
+    edge = (Edge) e.getValue();
+    asc_id = edge.asc_id;    
+    a_proj = edge.proj;    
+    id     = edge.from.id;
+    from_proj = edge.from.proj;
+    to_id  = edge.to.id;
+    to_proj = edge.to.proj;
+    r_name = edge.r_name;
+    r_from = edge.r_from;
+    r_to   = edge.r_to;
+    edges_str += "{ \"asc_id\":\"" + asc_id + "\", \"a_proj\":\""+ a_proj +"\","
+		+ " \"id\":\"" + id + "\", \"from_proj\":\""+ from_proj +"\","
+		+ " \"to_id\":\"" + to_id + "\", \"to_proj\":\"" + to_proj + "\"," 
+                + " \"r_name\":\"" + r_name +"\", \"r_from\":\"" + r_from + "\", \"r_to\":\"" + r_to + "\" },";
+  }
+  //edges_str += " {} ]";
+  edges_str = edges_str.substring(0, edges_str.length()-1) + " ]";
+  if (debug) 
+    println("{" + root_str + ", " + nodes_str + ", " + edges_str + "}");
+  if (null != javascript) javascript.allbackup("{" + root_str + ", " + nodes_str + ", " + edges_str + "}");
+}
+
+void allRetrieve() {
+  physics.clear(); 
+  nodes.clear();
+  edges.clear();
+  connected_edges.clear();
+  javascript.allretrieve("");
+}
+
+int cvalue(int hex) {
+  int r = (hex & 0xff0000)>>16;
+  int g = (hex & 0xff00)>>8;
+  int b = hex & 0xff;
+  return r+g+b;
+}
+
+int toInt(String s) {
+  int i = 0;
+  if ("0".equals(s)) i = 0;
+  else if ("1".equals(s)) i = 1;
+  else if ("2".equals(s)) i = 2;
+  else if ("3".equals(s)) i = 3;
+  else if ("4".equals(s)) i = 4;
+  else if ("5".equals(s)) i = 5;
+  else if ("6".equals(s)) i = 6;
+  else if ("7".equals(s)) i = 7;
+  else if ("8".equals(s)) i = 8;
+  else if ("9".equals(s)) i = 9;
+  else if ("a".equals(s)) i = 10;
+  else if ("A".equals(s)) i = 10;
+  else if ("b".equals(s)) i = 11;
+  else if ("B".equals(s)) i = 11;
+  else if ("c".equals(s)) i = 12;
+  else if ("C".equals(s)) i = 12;
+  else if ("d".equals(s)) i = 13;
+  else if ("D".equals(s)) i = 13;
+  else if ("e".equals(s)) i = 14;
+  else if ("E".equals(s)) i = 14;
+  else if ("f".equals(s)) i = 15;
+  else if ("F".equals(s)) i = 15;
+  return i;
+}
+
+int hex2int(String s) {
+  String a = s.substring(0, 1);
+  String b = s.substring(1, 2);
+  int ret = 16 * toInt(a) + toInt(b);
+  //if (debug) println(s + " " + a + " " + b + " " + ret);
+  return ret;
+}
+
+int str2color(String str) {
+  int c;
+  int r = hex2int(str.substring(0, 2));
+  int g = hex2int(str.substring(2, 4));
+  int b = hex2int(str.substring(4, 6));
+  //if (debug) println(str + " " + r + " " + g + " " + b);
+  c = color(r, g, b);
+  return c;
+}
+
+void initNode(String id, String name, String grp, String uid, String proj) {
+  if (debug) println("initNode(id=" + id + " name=" + name + " grp=" + grp + " uid=" + uid + " proj=" + proj + ")");
+  //name = unescapeSTR( name );
+  Node n = findNode(id, proj);
+  if (n == null) n = new Node(id, name, grp, uid, proj);
+  rootNode(n);
+}
+
+////////  GROUP SHAPE & COLOR  /////////////////////////////////////////////////
+void groupShape() {
+  if (nodetrace) print("groupShape");
+  javascript.group_shapes();
+}
+
+int hexToColor(String clr) {
+  String s = clr.replaceFirst("^#", "");
+  int c = str2color( s );
+  return c;
+}
+
+int color2int(String clr) {
+  int c = 0;
+  if ( "#".equals(clr.substring(0, 1))) {
+    c = hexToColor(clr);
+  }
+  else if ( "AliceBlue".equals(clr) )         c = AliceBlue;
+  else if ( "AntiqueWhite".equals(clr) )      c = AntiqueWhite;
+  else if ( "Aqua".equals(clr) )              c = Aqua;
+  else if ( "Aquamarine".equals(clr) )        c = Aquamarine;
+  else if ( "Azure".equals(clr) )             c = Azure;
+  else if ( "Beige".equals(clr) )             c = Beige;
+  else if ( "Bisque".equals(clr) )            c = Bisque;
+  else if ( "Black".equals(clr) )             c = Black;
+  else if ( "BlanchedAlmond".equals(clr) )    c = BlanchedAlmond;
+  else if ( "Blue".equals(clr) )              c = Blue;
+  else if ( "BlueViolet".equals(clr) )        c = BlueViolet;
+  else if ( "Brown".equals(clr) )             c = Brown;
+  else if ( "BurlyWood".equals(clr) )         c = BurlyWood;
+  else if ( "CadetBlue".equals(clr) )         c = CadetBlue;
+  else if ( "Chartreuse".equals(clr) )        c = Chartreuse;
+  else if ( "Chocolate".equals(clr) )         c = Chocolate;
+  else if ( "Coral".equals(clr) )             c =  Coral;
+  else if ( "CornflowerBlue".equals(clr) )    c = CornflowerBlue;
+  else if ( "Cornsilk".equals(clr) )          c = Cornsilk;
+  else if ( "Crimson".equals(clr) )           c = Crimson;
+  else if ( "Cyan".equals(clr) )              c = Cyan;
+  else if ( "DarkBlue".equals(clr) )          c = DarkBlue;
+  else if ( "DarkCyan".equals(clr) )          c = DarkCyan;
+  else if ( "DarkGoldenRod".equals(clr) )     c = DarkGoldenRod;
+  else if ( "DarkGray".equals(clr) )          c = DarkGray;
+  else if ( "DarkGrey".equals(clr) )          c = DarkGrey;
+  else if ( "DarkGreen".equals(clr) )         c = DarkGreen;
+  else if ( "DarkKhaki".equals(clr) )         c = DarkKhaki;
+  else if ( "DarkMagenta".equals(clr) )       c = DarkMagenta;
+  else if ( "DarkOliveGreen".equals(clr) )    c = DarkOliveGreen;
+  else if ( "Darkorange".equals(clr) )        c = Darkorange;
+  else if ( "DarkOrchid".equals(clr) )        c = DarkOrchid;
+  else if ( "DarkRed".equals(clr) )           c = DarkRed;
+  else if ( "DarkSalmon".equals(clr) )        c = DarkSalmon;
+  else if ( "DarkSeaGreen".equals(clr) )      c = DarkSeaGreen;
+  else if ( "DarkSlateBlue".equals(clr) )     c = DarkSlateBlue;
+  else if ( "DarkSlateGray".equals(clr) )     c = DarkSlateGray;
+  else if ( "DarkSlateGrey".equals(clr) )     c = DarkSlateGrey;
+  else if ( "DarkTurquoise".equals(clr) )     c = DarkTurquoise;
+  else if ( "DarkViolet".equals(clr) )        c = DarkViolet;
+  else if ( "DeepPink".equals(clr) )          c = DeepPink;
+  else if ( "DeepSkyBlue".equals(clr) )       c = DeepSkyBlue;
+  else if ( "DimGray".equals(clr) )           c = DimGray;
+  else if ( "DimGrey".equals(clr) )           c = DimGrey;
+  else if ( "DodgerBlue".equals(clr) )        c = DodgerBlue;
+  else if ( "FireBrick".equals(clr) )         c = FireBrick;
+  else if ( "FloralWhite".equals(clr) )       c = FloralWhite;
+  else if ( "ForestGreen".equals(clr) )       c = ForestGreen;
+  else if ( "Fuchsia".equals(clr) )           c = Fuchsia;
+  else if ( "Gainsboro".equals(clr) )         c = Gainsboro;
+  else if ( "GhostWhite".equals(clr) )        c = GhostWhite;
+  else if ( "Gold".equals(clr) )              c = Gold;
+  else if ( "GoldenRod".equals(clr) )         c = GoldenRod;
+  else if ( "Gray".equals(clr) )              c = Gray;
+  else if ( "Grey".equals(clr) )              c = Grey;
+  else if ( "Green".equals(clr) )             c = Green;
+  else if ( "GreenYellow".equals(clr) )       c = GreenYellow;
+  else if ( "HoneyDew".equals(clr) )          c = HoneyDew;
+  else if ( "HotPink".equals(clr) )           c = HotPink;
+  else if ( "IndianRed ".equals(clr) )        c = IndianRed ;
+  else if ( "Indigo ".equals(clr) )           c = Indigo ;
+  else if ( "Ivory".equals(clr) )             c = Ivory;
+  else if ( "Khaki".equals(clr) )             c = Khaki;
+  else if ( "Lavender".equals(clr) )          c = Lavender;
+  else if ( "LavenderBlush".equals(clr) )     c = LavenderBlush;
+  else if ( "LawnGreen".equals(clr) )         c = LawnGreen;
+  else if ( "LemonChiffon".equals(clr) )      c = LemonChiffon;
+  else if ( "LightBlue".equals(clr) )         c = LightBlue;
+  else if ( "LightCoral".equals(clr) )        c = LightCoral;
+  else if ( "LightCyan".equals(clr) )         c = LightCyan;
+  else if ( "LightGoldenRodYellow".equals(clr) ) c = LightGoldenRodYellow;
+  else if ( "LightGray".equals(clr) )         c = LightGray;
+  else if ( "LightGrey".equals(clr) )         c = LightGrey;
+  else if ( "LightGreen".equals(clr) )        c = LightGreen;
+  else if ( "LightPink".equals(clr) )         c = LightPink;
+  else if ( "LightSalmon".equals(clr) )       c = LightSalmon;
+  else if ( "LightSeaGreen".equals(clr) )     c = LightSeaGreen;
+  else if ( "LightSkyBlue".equals(clr) )      c = LightSkyBlue;
+  else if ( "LightSlateGray".equals(clr) )    c = LightSlateGray;
+  else if ( "LightSlateGrey".equals(clr) )    c = LightSlateGrey;
+  else if ( "LightSteelBlue".equals(clr) )    c = LightSteelBlue;
+  else if ( "LightYellow".equals(clr) )       c = LightYellow;
+  else if ( "Lime".equals(clr) )              c = Lime;
+  else if ( "LimeGreen".equals(clr) )         c = LimeGreen;
+  else if ( "Linen".equals(clr) )             c = Linen;
+  else if ( "Magenta".equals(clr) )           c = Magenta;
+  else if ( "Maroon".equals(clr) )            c = Maroon;
+  else if ( "MediumAquaMarine".equals(clr) )  c = MediumAquaMarine;
+  else if ( "MediumBlue".equals(clr) )        c = MediumBlue;
+  else if ( "MediumOrchid".equals(clr) )      c = MediumOrchid;
+  else if ( "MediumPurple".equals(clr) )      c = MediumPurple;
+  else if ( "MediumSeaGreen".equals(clr) )    c = MediumSeaGreen;
+  else if ( "MediumSlateBlue".equals(clr) )   c = MediumSlateBlue;
+  else if ( "MediumSpringGreen".equals(clr) ) c = MediumSpringGreen;
+  else if ( "MediumTurquoise".equals(clr) )   c = MediumTurquoise;
+  else if ( "MediumVioletRed".equals(clr) )   c = MediumVioletRed;
+  else if ( "MidnightBlue".equals(clr) )      c = MidnightBlue;
+  else if ( "MintCream".equals(clr) )         c = MintCream;
+  else if ( "MistyRose".equals(clr) )         c = MistyRose;
+  else if ( "Moccasin".equals(clr) )          c = Moccasin;
+  else if ( "NavajoWhite".equals(clr) )       c = NavajoWhite;
+  else if ( "Navy".equals(clr) )              c = Navy;
+  else if ( "OldLace".equals(clr) )           c = OldLace;
+  else if ( "Olive".equals(clr) )             c = Olive;
+  else if ( "OliveDrab".equals(clr) )         c = OliveDrab;
+  else if ( "Orange".equals(clr) )            c = Orange;
+  else if ( "OrangeRed".equals(clr) )         c = OrangeRed;
+  else if ( "Orchid".equals(clr) )            c = Orchid;
+  else if ( "PaleGoldenRod".equals(clr) )     c = PaleGoldenRod;
+  else if ( "PaleGreen".equals(clr) )         c = PaleGreen;
+  else if ( "PaleTurquoise".equals(clr) )     c = PaleTurquoise;
+  else if ( "PaleVioletRed".equals(clr) )     c = PaleVioletRed;
+  else if ( "PapayaWhip".equals(clr) )        c = PapayaWhip;
+  else if ( "PeachPuff".equals(clr) )         c = PeachPuff;
+  else if ( "Peru".equals(clr) )              c = Peru;
+  else if ( "Pink".equals(clr) )              c = Pink;
+  else if ( "Plum".equals(clr) )              c = Plum;
+  else if ( "PowderBlue".equals(clr) )        c = PowderBlue;
+  else if ( "Purple".equals(clr) )            c = Purple;
+  else if ( "Red".equals(clr) )               c = Red;
+  else if ( "RosyBrown".equals(clr) )         c = RosyBrown;
+  else if ( "RoyalBlue".equals(clr) )         c = RoyalBlue;
+  else if ( "SaddleBrown".equals(clr) )       c = SaddleBrown;
+  else if ( "Salmon".equals(clr) )            c = Salmon;
+  else if ( "SandyBrown".equals(clr) )        c = SandyBrown;
+  else if ( "SeaGreen".equals(clr) )          c = SeaGreen;
+  else if ( "SeaShell".equals(clr) )          c = SeaShell;
+  else if ( "Sienna".equals(clr) )            c = Sienna;
+  else if ( "Silver".equals(clr) )            c = Silver;
+  else if ( "SkyBlue".equals(clr) )           c = SkyBlue;
+  else if ( "SlateBlue".equals(clr) )         c = SlateBlue;
+  else if ( "SlateGray".equals(clr) )         c = SlateGray;
+  else if ( "SlateGrey".equals(clr) )         c = SlateGrey;
+  else if ( "Snow".equals(clr) )              c = Snow;
+  else if ( "SpringGreen".equals(clr) )       c = SpringGreen;
+  else if ( "SteelBlue".equals(clr) )         c = SteelBlue;
+  else if ( "Tan".equals(clr) )               c = Tan;
+  else if ( "Teal".equals(clr) )              c = Teal;
+  else if ( "Thistle".equals(clr) )           c = Thistle;
+  else if ( "Tomato".equals(clr) )            c = Tomato;
+  else if ( "Turquoise".equals(clr) )         c = Turquoise;
+  else if ( "Violet".equals(clr) )            c = Violet;
+  else if ( "Wheat".equals(clr) )             c = Wheat;
+  else if ( "White".equals(clr) )             c = White;
+  else if ( "WhiteSmoke".equals(clr) )        c = WhiteSmoke;
+  else if ( "Yellow".equals(clr) )            c = Yellow;
+  else if ( "YellowGreen".equals(clr) )       c = YellowGreen;
+  return c;
+}
+
+void newUserColor(String uid, int c, String show) {
+  if (debug) println("newUserColor(" + uid + "," + c + "," + show + ")");
+  boolean s =("show".equals(show))? true:false;
+  UserColor usercolor = new UserColor(uid, uid, c, s);
+  usercolors.put( uid, usercolor );
+  if (javascript != null) javascript.username(uid); 
+}
+
+void setUserColor(String uid, String uname) {
+  if (debug) println("setUserColor(" + uid + "," + uname + ")");
+  UserColor usercolor = (UserColor) usercolors.get(uid);
+  if (usercolor != null) {
+    usercolor.uid  = uid;
+    usercolor.name = uname;
+    usercolors.put( uid, usercolor );
+  }
+}
+
+public class UserColor {
+  String  uid;
+  String  name;
+  int     c;
+  boolean show;
+
+  UserColor(String uid, String name, int c, boolean show) {
+    this.uid  = uid;
+    this.name  = name;
+    this.c    = c;
+    this.show = show;
+  }
+}
+// NodeShape
+void newNodeShape(String grp, String s, String clr) {
+  if (debug || nodetrace) println("newNodeshape(" + grp + "," + s + "," + clr + ")");
+  int c = color2int(clr);
+  NodeShape nodeshape = new NodeShape(grp, s, c);
+  addNodeShape(nodeshape);
+}
+
+void addNodeShape(NodeShape nodeshape) {
+  if (nodetrace) print("addNodeShape("+nodeshape.grp+","+nodeshape.s+","+hex(nodeshape.c,6)+")");
+  String grp = nodeshape.grp;
+  /*if (nodeshapes.containsKey( grp )) {
+    nodeshapes.remove( grp );
+  } */
+  nodeshapes.put( grp, nodeshape );
+}
+
+NodeShape findNodeShape(String grp) {
+  NodeShape node_shape = (NodeShape) nodeshapes.get(grp);
+  if (nodetrace) {
+    if(node_shape==null) 
+      println("\tfindNodeShape grp="+grp+" shape undefined.");
+    else 
+      println("\tfindNodeShape grp="+grp+" shape="+node_shape.s+" color="+hex(node_shape.c,6));
+  }
+  return node_shape;
+}
+
+public class NodeShape {
+  String grp;
+  String s; 
+  int    c;
+
+  NodeShape(String grp, String s, int c) {
+    this.grp  = grp;
+    this.s    = s;
+    this.c    = c;
+  }
+}
+
+// EdgeShape
+void newEdgeShape(String r_name, String s, String clr) {
+  if (edgetrace) println("newEdgeShape(" + r_name + "," + s + "," + clr + ")");
+  int c = color2int(clr);
+  EdgeShape edgeshape = new EdgeShape(r_name, s, c);
+  addEdgeShape(edgeshape);
+}
+
+void addEdgeShape(EdgeShape edgeshape) {
+  if (edgetrace) println("addEdgeShape edgeshape=("+edgeshape.r_name+","+edgeshape.s+","+hex(edgeshape.c,6)+") ");
+  String r_name = edgeshape.r_name;
+  /*if (edgeshapes.containsKey( r_name )) {
+    edgeshapes.remove( r_name );
+  } */
+  edgeshapes.put( r_name, edgeshape );
+}
+
+EdgeShape findEdgeShape(String r_name) {
+  if (edgetrace) println("\tfindEdgeShape("+r_name+")");  
+  if (r_name==null || r_name.equals("")) return null;
+  EdgeShape edgeshape = (EdgeShape) edgeshapes.get(r_name);
+  return edgeshape;
+}
+
+public class EdgeShape {
+  String r_name;
+  String s; 
+  int    c;
+
+  EdgeShape(String r_name, String s, int c) {
+    this.r_name  = r_name;
+    this.s    = s;
+    this.c    = c;
+  }
+}
+
+////////  IMAGE MAP MODEL  ///////////////////////////////////////////////
+ImageMap newImageMap(String url) {
+  //if (debug) println("newImageMap(" + url + ")");
+  ImageMap imap = findImageMap(url);
+  if (imap == null) {
+    imap = new ImageMap(url);
+    addImageMap(imap);
+  }
+  return imap;
+}
+
+ImageMap findImageMap(String url) {
+  //if (debug) println("\tfindImageMap("+url+")");  
+  if (url==null || url.equals("")) return null;
+  ImageMap imap = (ImageMap) imageMaps.get(url);
+  return imap;
+}
+
+void addImageMap(ImageMap imap) {
+  //if (debug) println("addImage imap=("+imap.url+") ");
+  String url = imap.url;
+  /*if (imageMaps.containsKey( url )) {
+    imageMaps.remove( url );
+  } */
+  imageMaps.put( url, imap );
+}
+
+class ImageMap {     
+    String url;   
+    PImage img;
+    int w,h;
+    
+    ImageMap(String url) {
+      this.url = url;          
+      int siz = 80;
+      w = h = 0;
+      this.img = loadImage(url);
+      if(this.img != null) {
+        w = img.width;
+        h = img.height;
+        if (w > h) {
+          h = round(siz * h / w);
+          w = siz; 
+        } else {
+          w = round(siz * w / h); 
+          h = siz;
+        }
+        img.resize(w, h);  
+      } 
+    }
+    
+    void show(int x, int y) {
+        image(this.img, x - this.w, y - this.h/2);      
+    }
+}
+
+////////  THUMBNAIL MODEL  ///////////////////////////////////////////////
+void newThumb(String id, int start, int end) {
+  if (debug) println("newThumb(" + id + "," + start + "," + end + ")");
+  Thumb thumb = new Thumb(id, start, end);
+  addThumb(thumb);
+}
+
+void addThumb(Thumb thumb) {
+  if (debug) println("addThumb thumb=("+thumb.start+","+thumb.end+") ");
+  String id = thumb.id;
+  /*if (thumbs.containsKey( id )) {
+    thumbs.remove( id );
+  } */
+  thumbs.put( id, thumb );
+}
+
+Thumb findThumb(String id) {
+  //if (debug) println("\tfindThumb("+id+")");  
+  if (id==null || id.equals("")) return null;
+  Thumb thumb = (Thumb) thumbs.get(id);
+  return thumb;
+}
+
+class Thumb {     
+    String id;   
+    String prefix;   
+    int start;  
+    int end;
+    String postfix;
+    PImage image;
+    
+    Thumb(String id, int start, int end) {
+      this.id      = id;
+      this.prefix  = "thumbs/";
+      this.start   = start;
+      this.end     = end;
+      this.postfix = "_in.jpg";     
+    }
+}
+
+////////  GRAPH DATA MODEL  ///////////////////////////////////////////////
+public class Node {
+  Particle p;
+  int     x, y;
+  int     mass;
+  String  id, proj, uid;
+  String  name, grp, abst;
+  String  url;
+  String  shape;
+  int     c;
+  int     size;
+  int     w, h;
+  //String  hide;
+  boolean checked;
+  boolean fixed;
+  boolean root;
+  String  isa;
+  int     count;
+  int     assoc;
+  String  freeDirection;
+  String  vizgroup;
+
+  Node(String _id, String _name, String _grp, String _uid, String _proj) {
+    this.id   = _id;
+    this.name = _name;
+    this.grp  = _grp;
+    this.uid  = _uid;
+    this.proj = _proj;
+    this.size = 18;
+    this.mass = 1;// MASS;
+    this.x    = Math.round( random(-128, 128) );
+    this.y    = Math.round( random(-128, 128) );
+    this.p    = physics.makeParticle(this.mass, this.x, this.y /*, 0*/);
+    this.checked = false;
+    this.fixed = false;
+    this.root  = false;
+    this.isa   = "";
+    this.count = 0;
+    this.assoc = 0;
+    String s   = "RECTANGLE";    // Default
+    int c      = LightSlateGray; // Default
+    NodeShape node_shape = findNodeShape( grp );
+    if (node_shape != null) {
+      s = node_shape.s;
+      c = node_shape.c;
+    }
+    this.shape = s;
+    this.c    = c;
+    this.w    = 10;
+    this.h    = 12;
+    if (nodetrace) println( "\tDEFINE Node( " + id + ", " + name + ", " + grp + ", " + uid + ", " + proj +" )" );
+  }
+
+  void increment() {
+    mass++;
+  }
+
+  void setURL(String url) {
+    this.url = url;
+  }
+
+  void fix() {
+    this.fixed = true;
+    this.p.makeFixed();
+  }
+
+  void free() {
+    this.fixed = false;
+    this.p.makeFree();
+  }
+
+  void position(int x, int y) {
+    this.x = x;
+    this.y = y;
+    this.p.position.x = x;    
+    this.p.position.y = y;
+  }
+
+  void update() {
+    this.x = Math.round( p.position.x );
+    this.y = Math.round( p.position.y );
+  }
+
+  void move(float mx, float my) {
+    this.x += mx;
+    this.y += my;
+    this.position(this.x, this.y);
+    damper = 1.0;
+  }
+
+  void affix(int diff, float x, float y, float w, float h, int s) {
+    if (diff > 0) {
+      if (diff>1000) s *= 2;
+      else if (diff>100) s *= 1.5;
+      String unopen = "" + diff;
+      stroke(White);
+      strokeWeight(1);
+      //stroke(edgeColor);
+      stroke(DeepPink);
+      fill(DeepPink);
+      rect(x + w/2, y - h/2, s, s);
+      fill(White);
+      textSize(8);
+      text(unopen, x + w/2, y - h/2);
+      textSize(12);
+    }
+  }
+
+  void drawShape(String label, float dispX, float dispY, boolean highlite) {
+    float sf = sqrt(mass);
+    sf = 1 + (sf-1)/8;       // scale factor of each node
+    int tsize = round(10*sf);
+    if (tsize > 48) {
+      textSize(48);
+    } else if (tsize > 12) {
+      textSize(tsize);
+    } else {
+      textSize(12);
+    }
+    w = round( textWidth(label) + 30 );
+    h = round( sf * this.size );
+    if (this.root) fill(rootColor);
+    if ("".equals(this.shape)) {
+      rectMode(CENTER);
+      rect(dispX, dispY, w, h);
+    }
+    else if ("CIRCLE".equals(this.shape)) {
+      ellipseMode(CENTER);
+      ellipse(dispX, dispY, w, w);
+    }
+    else if ("ELLIPSE".equals(this.shape)) {
+      ellipseMode(CENTER);
+      ellipse(dispX, dispY, w + 4, h + 4);
+    }
+    else if ("ROUNDED".equals(this.shape)) {
+      rectMode(CENTER);
+      ellipseMode(CENTER);
+      roundedRect(dispX, dispY, w, h, h/2);
+    }
+    else if ("MARK".equals(this.shape)) {
+      int radius = 18;
+      fill(bgColor, 10); 
+      stroke(bgColor, 10);
+      rectMode(CENTER);
+      rect(dispX + w*sf/2 + radius, dispY, w, h);      
+      fill(this.c);
+      stroke( (highlite)? selectColor:u_clr );
+      ellipseMode(CENTER);
+      //ellipse(dispX, dispY, h*sf, h*sf);
+      ellipse(dispX, dispY, radius, radius);
+    } 
+    else {//Default shape is RECTANGLE
+      rectMode(CENTER);
+      rect(dispX, dispY, w, h);
+    }
+
+    int diff = this.assoc - this.count;
+    String unopen = "";
+    if ( diff > 0 ) unopen += diff;
+    textAlign(CENTER, CENTER);
+    if ("MARK".equals(this.shape)) {
+      fill(Black); 
+      text(label, dispX + w*sf/2, dispY);
+      affix(diff, dispX - w*sf/2 + h*sf/2, dispY, w*sf, h*sf, 9);
+    } 
+    else {
+      fill(Black);
+      if ( cvalue(this.c) < cvalue(#A0A0A0) ) fill(White);  
+      text(label, dispX, dispY);
+      affix(diff, dispX, dispY, w*sf, h*sf, 9);
+    }
+    if (url!=null) {
+      ImageMap imap = newImageMap(url);
+      imap.show(round(dispX - w*sf/2), round(dispY));
+    }
+  }
+
+  void show() {
+    Particle p = this.p;
+    update();
+    if (p!=null) {
+      float dispX = factor * p.position.x;    
+      float dispY = factor * p.position.y;
+      String s   = "RECTANGLE";    // Default
+      int c      = LightSlateGray; // Default
+      NodeShape node_shape = findNodeShape( grp );
+      if (node_shape != null) {
+        s = node_shape.s;
+        c = node_shape.c;
+      }
+      this.shape = s;
+      this.c     = c;
+      fill(this.c);
+      UserColor usercolor = (UserColor) usercolors.get(this.uid);
+      strokeWeight(1.5);
+      if (usercolor!=null) {
+        u_clr = usercolor.c;
+        stroke(u_clr);
+      }
+      else {
+        u_clr = color(32*int(random(255)/32), 32*int(random(255)/32), 32*int(random(255)/32));
+        while (u_clr == selectColor){
+          u_clr = color(32*int(random(255)/32), 32*int(random(255)/32), 32*int(random(255)/32));          
+        }
+        newUserColor(uid, u_clr, "show");
+        stroke(u_clr);        
+      }
+      //textSize(12);      
+      String topicname = unescapeSTR(this.name);
+      UserColor uc = (UserColor) usercolors.get(uid);
+      String label = (showusername && uc.name!=null)? "["+uc.name+"] "+topicname:topicname;
+      if ( label.length() > LABEL_LENGTH ) label = label.substring(0, LABEL_LENGTH) + "...";
+      this.drawShape(label, dispX, dispY, false);
+    }
+  }
+
+  void highlight() {
+    Particle p = this.p;
+    float dispX=0, dispY=0;
+    int seq = 0;
+    if (p!=null) {
+      dispX = factor * p.position.x;    
+      dispY = factor * p.position.y;
+      stroke(selectColor);
+      strokeWeight(2.0);
+      fill(this.c);
+      //textSize(12); 
+      UserColor uc = (UserColor) usercolors.get(uid);
+      String topicname = unescapeSTR(this.name);
+      String label = (showusername && uc.name!=null)? "["+uc.name+"] "+topicname:topicname;
+      this.drawShape(label, dispX, dispY, true);
+      stroke(bgColor);
+      strokeWeight(0.1);
+      /*Thumb thumb = findThumb(id);
+      if (thumb!=null) {
+        int num = thumb.end-thumb.start;
+        num=(num>0)?num:1;
+        String filename = 
+  	    	    thumb.prefix + 
+  	    	    (thumb.start + (seq++ / 30) % num) +
+  	    	    thumb.postfix;
+        if (debug) println(filename);
+        img = loadImage(filename);
+        if (img!=null) image(img, dispX - 16, dispY + 4);
+      }
+      */
+    }
+    if (nodetrace) {
+      fill(Gray);
+      text(isa, dispX - 30, dispY + 12);
+      text("assoc="+assoc+" count="+count, dispX - 30, dispY + 24);
+      text("fixed="+fixed+" root="+root, dispX - 30, dispY + 36);      
+      text(id+"@"+proj+" grp="+grp+" url="+url, dispX - 30, dispY + 48);
+    }
+  }
+}
+
+void roundedRect(float x, float y, float w, float h, float r) {
+  pushMatrix();
+  translate(x, y);
+  beginShape();
+  vertex(  w/2 - r, -h/2 );
+  bezierVertex( w/2 - r, -h/2, w/2, -h/2, w/2, -h/2 + r);
+  vertex(  w/2, h/2 - r );
+  bezierVertex( w/2, h/2, w/2 - r, h/2, w/2 - r, h/2);
+  vertex( -w/2 + r, h/2 );
+  bezierVertex( - w/2, h/2, -w/2, h/2 - r, -w/2, h/2 - r );
+  vertex( -w/2, -h/2 + r );
+  bezierVertex( -w/2, -h/2, -w/2 + r, -h/2, -w/2 + r, -h/2 );
+  endShape(CLOSE);
+  popMatrix();
+}
+
+public class Edge {
+  Spring s;
+  Node   from;
+  Node   to;
+  String from_is;
+  String to_is;
+  String proj, uid;
+  String asc_id;
+  String id, to_id, to_name;
+  String r_name, r_from, r_to;
+  float  len;
+  int    count;
+  int    dup; //number of the same node pair
+  String shape;
+  int    c;
+  int    size;  
+  PVector centr, V1, V2, V3;
+  float  R;
+  char   dir;
+
+  Edge(Node from, Node to, String uid, String proj) {
+    if (edgetrace) print("\tDEFINE Edge("+from.name+", "+to.name+")");
+    if (from==null || to==null) return;
+    this.uid   = uid;
+    this.proj  = proj;
+    this.from  = from;
+    this.to    = to;
+    Particle a = from.p;
+    Particle b = to.p;
+    if (a!=null && b!=null) this.s = physics.makeSpring(a, b, SPRING_STRENGTH, SPRING_DAMPING, SPRING_LENGTH);
+    this.id    = from.id;
+    this.to_id = to.id;
+    this.len   = SPRING_LENGTH;
+    this.count = 0;
+  }
+
+  void setID( String asc_id ) {
+    this.asc_id = asc_id;
+  }
+
+  void setRole( String R, String F, String T ) {
+    this.r_name = R;
+    this.r_from = F;
+    this.r_to  = T;
+    String s   = "ARROW"; // Default edge shape
+    int c      = Silver;  // Default edge color
+    EdgeShape edge_shape = findEdgeShape(this.r_name);
+    if (edge_shape != null) {
+      s = edge_shape.s;
+      c = edge_shape.c;
+    }
+    this.shape = s;
+    this.c     = c;
+    if (edgetrace) println("\tshape="+s+" color="+hex(c,6));
+  } 
+
+  void increment() {
+    count++;
+  }
+
+  PVector vRotate(PVector To, PVector From, float theta, float r) {
+    PVector P = new PVector(From.x, From.y);
+    P.sub(To);
+    float x   = cos(theta)*P.x - sin(theta)*P.y;
+    float y   = sin(theta)*P.x + cos(theta)*P.y;
+    PVector Q = new PVector(x, y);
+    Q.normalize();
+    Q.mult(r);
+    Q.add(To);
+    return Q;
+  }
+  
+  float theta(PVector P, PVector Q) {
+    //float sigma  = 0.1;
+    float theta = 0;
+    if (P==null || Q==null) return 0;
+    float deltax = Q.x - P.x;
+    float deltay = Q.y - P.y;
+    float tan    = deltay/deltax;
+    theta  = atan(tan);
+    if (0 <= deltay) {
+      if (sigma < deltax)                      theta = theta;
+      if (-sigma <= deltax && deltax <= sigma) theta = HALF_PI;
+      if (deltax < -sigma)                     theta = PI + theta;
+    }
+    else {
+      if (deltax < -sigma)                     theta = PI + theta;
+      if (-sigma <= deltax && deltax <= sigma) theta = PI + HALF_PI;
+      if (sigma < deltax)                      theta = TWO_PI + theta;
+    }
+    return theta;
+  }
+  
+  void drawLine(PVector P, PVector Q) {
+    line(P.x, P.y, Q.x, Q.y);
+  }
+/*
+  void fatLine(PVector P, PVector Q, float thickness) {
+    float x1 = P.x, y1 = P.y;
+    float x2 = Q.x, y2 = Q.y;
+    float distance = dist(x1, y1, x2, y2);		
+    float angle = 360-atan2((y2-y1),x2-x1) ; 
+    pushMatrix();
+    rectMode(CORNER);
+    translate(x1,y1);
+    rotate(360 - angle);
+    rect(0, - (thickness / 2 ), distance, thickness);
+    rectMode(CENTER);
+    popMatrix();
+  }
+*/
+  PVector calcCoord(PVector P) {
+    if (P==null) return null;
+    float x  = factor*P.x; //WORLD_SIZE_W/2 + factor*P.x; //Math.round( (P.x - WORLD_SIZE_W/2)/factor );
+    float y  = factor*P.y; //WORLD_SIZE_H/2 + factor*P.y; //Math.round( (P.y - WORLD_SIZE_H/2)/factor );
+    PVector Q = new PVector(x, y);
+    return Q;
+  }
+  
+  void drawArrow(/*Node from, Node to, int dup*/) {
+    PVector From = new PVector(this.from.x, this.from.y);
+    From.mult(factor);
+    PVector To   = new PVector(this.to.x, this.to.y);
+    To.mult(factor);
+    //float sigma     = 0.1;
+    int m           = 4; // number of control vertex for edge
+    float w         = 0.7; // width of arrow
+    float phai      = 0;
+    float psi       = 0;
+    float L         = dist(From.x, From.y, To.x, To.y)/2;
+    PVector Q1;
+    PVector M       = new PVector( (To.x + From.x)/2, (To.y + From.y)/2 );
+    this.centr      = M;
+    PVector Start, Terminate, P_i, Vi;
+    PVector Outer[] = new PVector[m];
+    PVector Inner[] = new PVector[m];
+    int N           = 8;
+    int n           = (dup%2==0)? dup/2 : -1*(dup/2)-1;
+    if ( (From.x - To.x < -sigma) || 
+         (-sigma <= From.x - To.x && From.x - To.x < sigma && 
+          From.y - To.y < -sigma) )
+      n *= -1; 
+    if (n < 0)
+      Q1 = vRotate(M, From, -HALF_PI, -n*L/N);
+    else
+      Q1 = vRotate(M, From, HALF_PI, n*L/N);
+    float a = dist(M.x, M.y, Q1.x, Q1.y)/L;
+    float b = (1-a*a)/(2*a);
+    if (n < 0) {
+      this.centr = vRotate(M, From, HALF_PI, b*L);
+      psi = theta(centr, To) - theta(centr, From);
+      if (PI < psi) psi -= TWO_PI;
+      if (psi< -PI) psi += TWO_PI;
+      phai = psi/m;
+    }
+    else if (n == 0){
+      this.centr = vRotate(From, M, -HALF_PI, 1); 
+    }
+    else if (0 < n){
+      this.centr = vRotate(M,From, -HALF_PI, b*L);
+      psi = theta(centr, To) - theta(centr, From);
+      if (PI < psi) psi -= TWO_PI;
+      if (psi< -PI) psi += TWO_PI;
+      phai = psi/m;
+    }
+    R = dist(centr.x, centr.y, From.x, From.y);  
+    if (n == 0) {
+      P_i = vRotate(From, M, 0, GAP);
+      To  = vRotate(To,   M, 0, GAP);    
+    }
+    else {
+      if (0 < phai) {
+        P_i = vRotate(centr, From,  GAP/R, R);
+        To  = vRotate(centr, To,   -GAP/R, R);
+      }
+      else {
+        P_i = vRotate(centr, From, -GAP/R, R);
+        To  = vRotate(centr, To,    GAP/R, R);
+      }
+    }
+    Vi = new PVector(From.x - centr.x, From.y - centr.y);  
+    Vi.normalize();  
+    Vi.mult( m*w );
+    Start     = PVector.add(P_i, Vi); 
+    Terminate = PVector.sub(P_i, Vi);
+    if (m==2) {
+      P_i = Q1; 
+      Vi  = new PVector(P_i.x - centr.x, P_i.y - centr.y);  
+      Vi.normalize();  
+      Vi.mult( w );
+      Outer[1]= PVector.add(P_i, Vi); 
+      Inner[1]= PVector.sub(P_i, Vi);    
+    }
+    else {
+      for (int i=1; i < m; i++) {
+        P_i = vRotate(centr, From, i*phai, R); 
+        Vi  = new PVector(P_i.x - centr.x, P_i.y - centr.y);  
+        Vi.normalize();  
+        Vi.mult( (m-i)*w );
+        Outer[i]= PVector.add(P_i, Vi); 
+        Inner[i]= PVector.sub(P_i, Vi);
+      }
+    }
+    this.V1 = Outer[1];
+    this.V2 = Q1;
+    this.V3 = Outer[m-1];
+    //strokeJoin(MITER);
+    if (this.shape==null || "ARROW".equals(this.shape)) {
+      beginShape();
+      if ( n == 0 ) {
+        vertex(Start.x,     Start.y);
+        vertex(To.x,        To.y);
+        vertex(Terminate.x, Terminate.y);
+        vertex(Start.x,     Start.y);
+      }
+      else {
+        curveVertex(Start.x, Start.y);
+        curveVertex(Start.x, Start.y);
+        for (int j =1; j < m; j++) {
+          curveVertex(Outer[j].x, Outer[j].y);
+        }
+        curveVertex(To.x, To.y);
+        for (int k = m-1; k > 0; k--) {
+          curveVertex(Inner[k].x, Inner[k].y);
+        }
+        curveVertex(Terminate.x, Terminate.y);
+        curveVertex(Start.x, Start.y);
+      }
+      endShape();
+    } 
+    else if ("LINE".equals(this.shape)) {
+      noFill();
+      strokeWeight(4);
+      if ( n == 0 ) {
+        drawLine(From, To);
+      }
+      else {
+        beginShape();
+        curveVertex(From.x, From.y);        
+        curveVertex(From.x, From.y);
+        for (int j =1; j < m; j++) {
+          curveVertex((Outer[j].x+Inner[j].x)/2,( Outer[j].y+Inner[j].y)/2);
+        }
+        curveVertex(To.x, To.y);        
+        curveVertex(To.x, To.y);
+        endShape();
+      }
+    }
+    this.centr.mult(1/factor);
+    this.V1.mult(1/factor);
+    this.V2.mult(1/factor);
+    this.V3.mult(1/factor);
+    // draw edge degug info
+    strokeWeight(0.5);
+    if (debug) {
+      fill(Pink);
+      if (from_is !=null) text(this.from_is, From.x+20, From.y-30); 
+      if (to_is!=null)    text(this.to_is,   To.x+20,   To.y-30);    
+    }
+    if (edgetrace) {
+      fill(Red);
+      float pX = factor * (pointX);
+      float pY = factor * (pointY);
+      if (!dragging) ellipse(pX, pY, GAP, GAP);
+
+      fromX = factor * (from.x);
+      fromY = factor * (from.y);
+      toX   = factor * (to.x);
+      toY   = factor * (to.y);
+      if (dup>0) {
+        cX    = factor * (this.centr.x);
+        cY    = factor * (this.centr.y);
+      }
+      else {
+        cX    = M.x;
+        cY    = M.y;       
+      }
+      fill(Gray);
+      stroke(Gray);
+      line(cX, cY, fromX, fromY);
+      line(cX, cY, toX, toY);
+      textAlign(CORNER, CENTER);
+      text(asc_id+" dup="+dup+" shape="+this.shape+" color="+hex(c,6), cX, cY); 
+      PVector P    = new PVector(scrnX, scrnY);
+      PVector Cntr = new PVector(cX, cY);
+      if (P != null)  phai   = theta(Cntr, P);
+      PVector P1   = new PVector(fromX, fromY);
+      if (P1 != null) theta1 = theta(Cntr, P1);
+      PVector P2   = new PVector(toX, toY);
+      if (P2 != null) theta2 = theta(Cntr, P2);
+      if (dup>0) {
+        text("center x="+round(cX)+" y="+round(cY), cX, cY+10);
+        text("theta1="+round(degrees(theta1))+" theta2="+round(degrees(theta2))+" phai="+round(degrees(phai)), cX, cY+20);
+      }
+    }
+  }
+  
+  void show() {
+    UserColor usercolor = (UserColor) usercolors.get(this.uid);
+    strokeWeight(1.5);
+    if (usercolor!=null) {
+      u_clr = usercolor.c;
+      stroke(u_clr);
+    }
+    else {
+      u_clr = color(32*int(random(255)/32), 32*int(random(255)/32), 32*int(random(255)/32));
+      while (u_clr == selectColor){
+        u_clr = color(32*int(random(255)/32), 32*int(random(255)/32), 32*int(random(255)/32));          
+      }
+      newUserColor(uid, u_clr, "show");
+      stroke(u_clr);        
+    }
+    strokeWeight(0.6);
+    fill(this.c);
+    drawArrow();   
+  }
+
+  void highlight() {
+    Node from = this.from;    
+    Node to   = this.to;
+    if (from==null || to==null) return;
+    float dx  = to.x - from.x;
+    float dy  = to.y - from.y;
+    float len = sqrt( dx*dx + dy*dy );
+    float cs  = GAP * dx/len;
+    float sn  = GAP * dy/len;
+    float d   = 2;
+    fromX = centerX + factor * (from.x - centerX);
+    fromY = centerY + factor * (from.y - centerX);
+    toX   = centerX + factor * (to.x  - centerX);
+    toY   = centerY + factor * (to.y  - centerX);
+    // draw highlighted edge
+    strokeWeight(0.5);
+    if (debug || edgetrace) {
+      fill(Red);
+      float pX = centerX + factor * (pointX  - centerX);
+      float pY = centerY + factor * (pointY  - centerX);
+      ellipse(pX, pY, GAP, GAP);
+      fill(Gray);
+      stroke(Gray);
+      cX    = centerX + factor * (this.centr.x  - centerX);
+      cY    = centerY + factor * (this.centr.y  - centerX);
+      line(cX, cY, fromX, fromY);
+      line(cX, cY, toX, toY);
+      textAlign(CORNER, CENTER);
+      if (dup==0)
+        text(asc_id+" dup="+dup+" shape="+this.shape+" color="+hex(c,6), 20 + (fromX  + toX  )/2, 10 + (fromY  + toY  )/2);
+      else
+        text(asc_id+" dup="+dup+" shape="+this.shape+" color="+hex(c,6), cX, cY); 
+    }
+    stroke(selectColor);
+    fill(selectColor);
+    drawArrow();
+    //draw label of roles
+    fill(Red);
+    textAlign(CENTER, CENTER);
+    float diffY = toY - fromY;
+    float D = 10;
+    float dY = 0;
+    if ( -D < diffY && diffY <= 0) dY = -D; 
+    if (  0 < diffY && diffY < D ) dY =  D;
+    if (dup==0){
+      if (sel_edge.r_from != null)
+        text( sel_edge.r_from, (3*fromX + toX  )/4, (3*fromY + toY  )/4 - dY );
+      if (sel_edge.r_name != null)
+        text( sel_edge.r_name, (fromX  + toX  )/2,  (fromY  + toY  )/2 );   
+      if (sel_edge.r_to  != null)
+        text( sel_edge.r_to,   (fromX  + 3*toX)/4,  (fromY  + 3*toY)/4 + dY );
+    }
+    else {
+      text(sel_edge.r_from, factor*V1.x, factor*V1.y);
+      text(sel_edge.r_name, factor*V2.x, factor*V2.y);
+      text(sel_edge.r_to,   factor*V3.x, factor*V3.y);
+    }
+  }
+}
+
+void allUpdate() {
+  Iterator itv = nodes.keySet().iterator();
+  while (itv.hasNext ()) {
+    String key = (String) itv.next();
+    Node node = (Node) nodes.get(key);
+    node.update();
+  }
+}
+
+void allFix() {
+  Iterator it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    String key = (String) it.next();
+    Node node = (Node) nodes.get(key);
+    node.fix();
+  }
+}
+
+void allFree() {
+  Iterator it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    String key = (String) it.next();
+    Node node = (Node) nodes.get(key);
+    if (!node.root)
+      node.free();
+  }
+  damper = 1.0;
+}
+
+void allMove(float mx, float my) {
+  Iterator it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    String key = (String) it.next();
+    Node node = (Node) nodes.get(key);
+    node.move(mx, my);
+  }
+}
+
+void setEdgeValue(String asc_id, String r_name, String r_from, String r_to, String s, String clr) {
+  if (edgetrace) println("setEdgeValue("+asc_id+","+r_name+","+r_from+","+r_to+","+s+","+clr+") ");
+  Edge e = edges.get(asc_id);
+  if (e!=null) {
+    e.r_name = r_name;
+    e.r_from = r_from;
+    e.r_to   = r_to;
+    if (s!=null && !"".equals(s))     e.shape  = s;
+    if (clr!=null && !"".equals(clr)) e.c      = hexToColor(clr);
+    if (edgetrace)  println("\tEdge UPDATED");
+  }
+}
+
+Edge findEdge(String asc_id, String proj) {
+  if (debug) print("\n\tfindEdge asc_id=" + asc_id + " proj=" + proj);
+  Edge e = null;
+  Iterator it = edges.keySet().iterator();
+  while (it.hasNext ()) {
+    String k  = (String) it.next();
+    e = (Edge) edges.get(k);
+    asc_id = asc_id.trim();
+    proj   = proj.trim();
+    if(k.equals(asc_id) && e.proj.equals(proj)) {
+      if (debug) {
+        if (debug) println("\tedge FOUND."); 
+      }           
+      return e;
+    }
+  }
+  if (debug) println("\tedge NOT found.");
+  return null;
+}
+
+void addEdge(String asc_id, String fromId, String fromProj, String toId, String toProj,
+              String r_name, String r_from, String r_to, String uid, String proj) {
+  if (edgetrace) println("================\naddEdge asc_id=" + asc_id +
+                      " from id=" + fromId + " proj="+fromProj+" to id=" + toId +" proj="+toProj+ " uid=" + uid+ " proj=" + proj );
+  if("".equals(fromId) || fromId == null || "".equals(toId) || toId == null) return;
+  boolean newedge = false;
+  char dir;
+  String pair, reverse_pair;
+  Node from = findNode(fromId, fromProj);
+  if (from==null) from = newNode(fromId, fromId, "", uid, fromProj);
+  Node to   = findNode(toId, toProj);
+  if (to==null)   to   = newNode(toId,   toId, "", uid, toProj);
+  if (edgetrace) println("\tfromId="+fromId+" toId="+toId);
+  if(fromId.hashCode() < toId.hashCode()) {
+    pair         = fromId+"#"+toId;
+    reverse_pair = toId+"#"+fromId;
+  }
+  else {
+    pair         = toId+"#"+fromId;    
+    reverse_pair = fromId+"#"+toId;
+  }
+  if (edgetrace) println("\tpair="+pair+"\tasc_id="+asc_id);  
+  from.update();    
+  to.update();
+  if ("".equals(asc_id)){
+    newedge = true;
+    asc_id = fromId+"#"+toId;
+    Edge e = findEdge(asc_id, proj);
+    if (e!=null) return;
+  }  
+  Edge the_edge  = new Edge(from, to, uid, proj);
+  the_edge.asc_id = asc_id;
+  int count_connection = 0;
+  Iterator it = connected_edges.keySet().iterator();
+  while (it.hasNext ()) {
+    String k = (String) it.next();
+    if ( k.indexOf(pair)==0 || k.indexOf(reverse_pair)==0 ) count_connection++;
+  }
+  if (newedge) the_edge.dup = 0; else the_edge.dup   = count_connection;
+  the_edge.setRole(r_name, r_from, r_to);  
+  if (edgetrace) println("\tthe_edge.setRole("+r_name+","+ r_from+","+ r_to+")");  
+  if (edgetrace) println("\taddEdge edges put\t" + asc_id + "\t" + the_edge.from.name + "\t" + the_edge.to.name);   
+  edges.put(asc_id, the_edge);
+  if (edgetrace) println("\taddEdge connected_edges put\t" + pair + ":" + count_connection +"\t"+ asc_id);   
+  connected_edges.put(pair + ":" + count_connection, asc_id);
+  if (edgetrace) {
+    println("\tconnected_edges size=" + connected_edges.size());   
+    it = connected_edges.keySet().iterator();
+    while (it.hasNext()) {
+      String k = (String) it.next();
+      println("\t\tlist\t" + k + "\t" + connected_edges.get(k));   
+    }
+  }
+  if (edgetrace) println("================  addEdge");  
+}
+
+void addSpacersToNode( Node node ) {
+  Iterator it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    String key = (String) it.next();
+    Node n = (Node) nodes.get(key);
+    Particle q = n.p;
+    if ( node.p != q ) {
+      physics.makeAttraction( q, node.p, -SPACER_STRENGTH, MIN_DISTANCE );
+    }
+  }
+}
+
+Node newNode(String id, String name, String grp, String uid, String proj) {
+  if (nodetrace) print("----------------\nnewNode\tdefine\tid=" + id + " name=" + name + " grp=" + grp);
+  if (id == null || id.equals("")) return null;
+  if (name == null || name.equals("")) return null;
+  String label = name;//unescapeSTR(name);
+  Node node = findNode(id, proj);
+  if (node == null) node = new Node(id, label, grp, uid, proj);
+  node.name = label;
+  node.grp  = grp;
+  if (nodetrace) println("\tdefined\tid=" + node.id + " name=" + node.name + " grp=" + node.grp);
+  addNode(node);
+  if (nodetrace) println("----------------  newNode");
+  return node;
+}
+
+void urlNode(String url, String id, String proj) {
+  if (nodetrace) println("node="+id+" url="+url);
+  Node node = findNode(id, proj);
+  if (node != null && url != null) node.setURL(url);  
+}
+void addNode(Node node) {
+  if (nodetrace) print("\t--------\n\taddNode id=" + node.id + " name=" + node.name);
+  String the_id = node.id;
+  String proj   = node.proj;
+  Node the_node = findNode( the_id, proj ); 
+  if (the_node!=null) { // increment used count
+    if (nodetrace) println("\taddNode node Exists.");
+    the_node.count += 1;
+  } 
+  else {                // add new node
+    if (nodetrace) println("\taddNode ADD node.");
+    nodes.put( the_id, node );
+    addSpacersToNode( node );
+  }
+  if (nodetrace) {
+    println("\tnodes size=" + nodes.size() );
+    Iterator it = nodes.keySet().iterator();
+    while (it.hasNext ()) {
+      String key  = (String) it.next();
+      Node n = (Node) nodes.get(key);
+      println("\t\tlist\t" + key + "\t" + n.name);
+    }
+    println("\t--------  addNode");
+  }
+}
+
+void printHex(String str) {
+  char     chr;
+  int      i;
+  println(str);
+  println("length="+str.length());
+  for (i = 0; i < str.length(); i++) {
+    chr = str.charAt(i) ;
+    print(" " + hex((int)chr,4));
+  } 
+  print("\n");
+}
+
+Node findNode(String id, String proj) {
+  if (nodetrace) print("\n\tfindNode id=" + id + " proj=" + proj);
+  Node n = null;
+  Iterator it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    String k  = (String) it.next();
+    n = (Node) nodes.get(k);
+    id = id.trim();
+    proj=proj.trim();
+    if(k.equals(id) && n.proj.equals(proj)) {
+      if (nodetrace) {
+        println("\tnode FOUND."); 
+      }           
+      return n;
+    }
+  }
+  if (nodetrace) println("\tnode NOT found.");
+  return null;
+}
+
+void selectNode(Node node) {
+  if (nodetrace) println("selectNode(" + node.id + ")");
+  String the_id  = node.id;
+  topicX = Math.round( node.p.position.x );
+  topicY = Math.round( node.p.position.y );
+  if (node.vizgroup == null) 
+    vizgroup.clear();
+  else
+    vizgroupNodes(node);
+}
+
+void infoNode(Node node) {
+  //func =  "info node";
+  String the_id   = node.id;
+  String the_proj = node.proj;
+  if (nodetrace) println("infoNode name=" + node.name + " id=" + the_id + " proj=" + the_proj);
+  if ( javascript != null ) {
+    javascript.selectnode(the_id, the_proj);
+  }
+}
+
+void topicNode(Node node) {
+  //func =  "topic node";
+  String the_id  = node.id;
+  if ( javascript != null ) {
+    if (nodetrace) println("topicNode " + node.name + " (" + node.id + ")");
+    javascript.topicnode(the_id);
+  }
+}
+
+void setNodePosition(String id, String proj, int x, int y) {
+  if (nodetrace) println("setNodePosition id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if (node!=null) node.position(x, y);
+}
+
+void fixNode(String id, String proj) {
+  if (nodetrace) println("fixNode id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if (node!=null) node.fix();
+}
+
+void setNodeValue(String id, String proj, String name, String grp, String abst, String uid) {
+  if (nodetrace) println("setNodeValue id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if ( node != null ) {
+    if ( uid  != null && !uid.equals("") )  node.uid  = uid;  
+    if ( name != null && !name.equals("") ) node.name = name;  
+    if ( grp  != null && !grp.equals("") )  node.grp  = grp;  
+    if ( abst != null && !abst.equals("") ) node.abst = abst;
+  }
+}
+
+void setNodeName(String id, String proj, String name) {
+  if (nodetrace) println("setNodeName id=" + id + " proj=" + proj + " name=" + name);
+  Node node = findNode(id, proj);
+  if ( node != null ) {
+    if ( name != null && !name.equals("") ) node.name = name;  
+  }
+}
+
+void setNodeAssoc(String id, String proj, int assoc) {
+  if (nodetrace) println("setNodeAssoc id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if ( node!=null ) node.assoc = assoc;
+  if ( nodetrace ) println("\tset node "+node.name+"("+id+")"+"\tassoc="+node.assoc);
+}
+
+void setNodeCount(String id, String proj, int count) {
+  if (nodetrace) println("setNodeCount id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if ( node!=null ) node.count = count;
+  if (nodetrace) println("\tset node "+node.name+"("+id+")"+"\tcount="+node.count);
+}
+
+void setNodeMass(String id, String proj, int mass) {
+  if (nodetrace) println("setNodeMass id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if (node!=null) node.mass = mass;
+  if (nodetrace) println("\tset node "+node.name+"("+id+")"+"\tmass="+node.mass);
+}
+
+void setNodeFreeDirection(String id, String proj, String d) {
+  if (nodetrace) println("setNodeFreeDirection id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if ( node!=null ) {
+    node.freeDirection = d; 
+    node.p.freeDirection = d;
+  }
+}
+
+void setNodeVizgroup(String id, String proj, String g) {
+  if (nodetrace) println("setNodeVizgroup id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if ( node!=null ) {
+    node.vizgroup = g; 
+  }
+}
+
+void setNodeSize(String id, String proj, int size) {
+  if (nodetrace) println("setNodeSize id=" + id + " proj=" + proj);
+  Node node = findNode(id, proj);
+  if ( node!=null ) node.size = size;
+}
+
+void newTopic() {
+  if (debug) println("newTopic");
+  if (javascript!=null) {
+    javascript.new_topic();
+  }
+}
+
+void pedia() {
+  if (debug) println("pedia");
+  if (javascript!=null) {
+    javascript.pedia();
+  }
+}
+/*
+void setTopic(String oldid, String id, String name, String grp) {
+  if (debug) println("setTopic oldid="+oldid+" id=" + id + " name=" + name + " grp=" + grp);
+  //if (debug) println("setTopic id=" + id + " name=" + name + " grp=" + grp);
+  name = name; //unescapeSTR( name );
+  oldid  = oldid;
+  id     = id;
+  Node n = findNode(id);
+  if (n == null) n = newNode(id, name, grp);
+  if (debug) println( n.id );
+  n.name = name;
+  n.grp  = grp;
+  n.fix();
+  n.position(0, 0);
+  ArrayList<Edge> connected = new ArrayList<Edge>();
+  Node oldnode = findNode(oldid);
+  if (oldnode!=null) {
+    connected = connectedEdges(oldnode);
+    for (int i = 0; i < connected.size(); i++) {
+      Edge e = (Edge) connected.get(i);
+      if (debug) println("\told:" + oldnode.name + " relation:" + e.from.name + " " + e.to.name);
+      if ( oldnode.id.equals(e.from.id) ) {
+        if (debug) println("\tnew:" + name + " relation:" + name + " " + e.to.name);
+          e.from.id   = id;
+          e.from.name = name;
+          e.from.grp  = grp;
+      }
+      else if (oldnode.id.equals(e.to.id) ) {
+        if (debug) println("\tnew:" + name + " relation:" + e.from.name + " " + name);
+          e.to.id   = id;
+          e.to.name = name;
+          e.to.grp  = grp;
+      }
+    }
+  }
+  hideNode(oldnode);
+}
+*/
+void newRelation(Node n) {
+  if (debug) println("newRelation");
+  //func =  "new relation";
+  from_node = selected;
+  selection = null;
+  damper = 1.0;
+}
+
+void expandNode(Node node, String both) {
+  //func =  "expand node";
+  free = true;
+  connected_edges.clear();
+  String the_id   = node.id;  
+  String the_proj = node.proj;
+  fixNode(the_id, the_proj);
+  if (javascript!=null) {
+    javascript.startexpand();
+    ArrayList<Edge> connected = new ArrayList<Edge>();
+    connected = connectedEdges(node);
+    the_adjacents = "(''";
+    for (int i = 0; i < connected.size(); i++) {
+      Edge e = (Edge) connected.get(i);
+      String ajacent_id = "";
+      if ( node.id.equals(e.from.id) ) {
+        ajacent_id = e.to.id;
+      }
+      else if ( node.id.equals(e.to.id) ) {
+        ajacent_id = e.from.id;        
+      }
+      the_adjacents += ",'"+ajacent_id+"'";
+    }
+    the_adjacents += ")";
+    if (debug) println("expandNode name="+node.name+" id="+node.id+" proj="+node.proj+" ajacents="+the_adjacents);
+    javascript.adjacentnodes(the_id, the_proj, the_adjacents, both);
+    javascript.endexpand();
+  }
+  damper = 1.0;
+}
+
+void squeezeNode(Node node) {
+  //func =  "squeeze node";
+  ArrayList<Edge> connected = new ArrayList<Edge>();
+  String from_is, to_is;
+  from_is = to_is = "";
+  String the_id = node.id;
+  Node self, other;
+  self = other = null;
+  if (debug) println("squeezeNode " + the_id + " " + node.isa);
+  if (!node.isa.equals("leaf")) {
+    connected = connectedEdges(node);
+    if (debug) println(connected.size() + " connected edge(s)");
+    for (int i = 0; i < connected.size(); i++) {
+      Edge e = (Edge) connected.get(i);
+      if (the_id.equals(e.from.id)) {
+        self    = e.from;
+        from_is = e.from_is;
+        other  = e.to;
+        to_is  = e.to_is;
+      } 
+      else if (the_id.equals(e.to.id)) {
+        self    = e.to;
+        from_is = e.to_is;
+        other  = e.from;
+        to_is  = e.from_is;
+      }
+      if (debug) println("self="  + self.id  + ":" + self.isa  + "(" + from_is + ")\t" +
+                         "other=" + other.id + ":" + other.isa + "(" + to_is + ")");
+      if ( to_is != null && to_is.equals("leaf") ) {
+        if (other.isa.equals("leaf")) {
+          if (debug) println("remove edge " + e.asc_id + " leaf " + other.id);
+          edges.remove(e.asc_id);
+          nodes.remove(other.id);
+          for (int j = 0; j < physics.numberOfAttractions(); j++) {
+            Attraction attraction = physics.getAttraction(j);
+            if (attraction.getOneEnd() == other.p || attraction.getTheOtherEnd() == other.p) {
+              attraction.turnOff();
+            }
+          }
+          for (int j = 0; j < physics.numberOfAttractions(); j++) {
+            Attraction attraction = physics.getAttraction(j);
+            if ( attraction.isOff() ) physics.removeAttraction(j);
+          }
+        } 
+        else if (other.isa.equals("branch")) {
+          if (debug) println("squeeze branch " + other.id);
+          squeezeNode(other);
+          edges.remove(e.asc_id);
+          nodes.remove(other.id);
+          for (int j = 0; j < physics.numberOfAttractions(); j++) {
+            Attraction attraction = physics.getAttraction(j);
+            if (attraction.getOneEnd() == other.p || attraction.getTheOtherEnd() == other.p) {
+              attraction.turnOff();
+            }
+          }
+          for (int j = 0; j < physics.numberOfAttractions(); j++) {
+            Attraction attraction = physics.getAttraction(j);
+            if ( attraction.isOff() ) physics.removeAttraction(j);
+          }
+        }
+      }
+    }
+  }
+}
+
+void deleteTopic(String id, String proj) {
+  if (debug) println("deleteTopic " + id);
+  Node node = findNode(id, proj);
+  if (node!=null) hideNode(node);
+}
+
+void hideNode(Node node) {
+  if (debug) println("hideNode " + node.id + " " + node.isa);
+  //func =  "hide node";
+  if (node.vizgroup!=null) return;
+  ArrayList<Edge> connected = new ArrayList<Edge>();
+  connected = connectedEdges(node);
+  //if (!node.root) {
+  if (connected.size() > 0) squeezeNode(node);
+  String the_id = node.id;
+  if (debug) println(connected.size() + " connected edge(s)");
+  if (connected !=null) {
+    for (int i=0; i < connected.size(); i++) {
+      Edge e = connected.get(i);
+      edges.remove(e.asc_id);
+    }
+  }
+
+  if (debug) println("\tRemove " + node.id + " " + node.name);
+  nodes.remove(node.id);
+
+  for (int i = 0; i < physics.numberOfAttractions(); i++) {
+    Attraction attraction = physics.getAttraction(i);
+    if (attraction.getOneEnd() == node.p || attraction.getTheOtherEnd() == node.p) {
+      attraction.turnOff();
+    }
+  }
+  for (int i = 0; i < physics.numberOfAttractions(); i++) {
+    Attraction attraction = physics.getAttraction(i);
+    if ( attraction.isOff() ) physics.removeAttraction(i);
+  }
+  //}
+}
+
+void markNode() {
+  //Object k;
+  String k;
+  String key;
+  Node  node;
+  Edge  edge;
+  int    count;
+  Iterator it;
+  ArrayList<Node> adjacent = new ArrayList<Node>();
+  // clear node isa status
+  it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    key = (String) it.next();
+    node = (Node) nodes.get(key);
+    node.isa = "";
+    node.count = 0;
+    nodes.put(key, node);
+  }
+  // reset node conecctioncounts
+  it = edges.keySet().iterator();
+  while (it.hasNext ()) {
+    k = (String) it.next();
+    edge = (Edge) edges.get(k);
+    key = edge.from.id;
+    node = (Node) nodes.get(key);
+    node.count += 1;
+    key = edge.to.id;
+    node = (Node) nodes.get(key);
+    node.count += 1;
+  }
+  it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    key = (String) it.next();
+    node = (Node) nodes.get(key);
+    // A node is leaf iff a node is connected to one edge.
+    count = node.count;
+    if (!node.root && node.isa.equals("") && count == 1) {
+      node.isa = "leaf";
+      //if (debug) print(key + " is leaf.\n");
+      nodes.put(key, node);
+    }
+    // A node is trunk iff a node is root.
+    if (node.root) {
+      node.isa = "trunk";
+      //if (debug) print(key + " is trunk.\n");
+      nodes.put(key, node);
+      // A node is trunk iff a node is connected directry to root node.
+      adjacent = adjacentNodes(node);
+      for (int i = 0; i < adjacent.size(); i++) {
+        Node n = (Node) adjacent.get(i);
+        key = n.id;
+        n.isa = "trunk";
+        //if (debug) print(key + " is trunk connected to root.\n");
+        nodes.put(key, n);
+      }
+    }
+  }
+  // repeat 3 times
+  int repeat = 0;
+  while (repeat++ <= 3) {
+    it = nodes.keySet().iterator();
+    while (it.hasNext ()) {
+      key = (String) it.next();
+      node = (Node) nodes.get(key);
+      if (node.isa.equals("")); 
+      {
+        // A node is branch iff a node is connected directry to n-1 leaf and/or branch nodes.
+        // A node is trunk iff a node is connected directry to more than 2 trunk nodes.
+        adjacent = adjacentNodes(node);
+        int branch_count, trunc_count;
+        branch_count = trunc_count = 0;
+        for (int i = 0; i < adjacent.size(); i++) {
+          Node n = (Node) adjacent.get(i);
+          if (n.isa.equals("leaf") || n.isa.equals("branch"))  branch_count++;
+          else if (n.isa.equals("trunk")) trunc_count++;
+        }
+        if (node.isa.equals("") && branch_count == adjacent.size() - 1) {
+          node.isa = "branch";
+          //if (debug) print(key + " is branch.(Step2)\n");
+          nodes.put(key, node);
+        } 
+        else if (node.isa.equals("") && trunc_count >= 2) {
+          node.isa = "trunk";
+          //if (debug) print(key + " is trunk.(Step2)\n");
+          nodes.put(key, node);
+        }
+      }
+    }
+  }
+  // A node is trunk if a node is neigther leaf nor branch i.e. "".
+  it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    key = (String) it.next();
+    node = (Node) nodes.get(key);
+    if (node.isa.equals("")) {
+      node.isa = "trunk";
+      //if (debug) print(key + " is trunk.(Step3)\n");
+      nodes.put(key, node);
+    }
+  }
+}
+
+ArrayList<Node> adjacentNodes(Node node) {
+  ArrayList<Node> adjacent = new ArrayList<Node>();
+  String the_id = node.id;
+  Iterator it = edges.keySet().iterator();
+  while (it.hasNext ()) {
+    Object k = it.next();
+    Edge e = (Edge) edges.get(k);
+    Node other = null;
+    if (the_id.equals(e.from.id)) {
+      other = e.to;
+    } 
+    else if (the_id.equals(e.to.id)) {
+      other = e.from;
+    }
+    if (other != null) adjacent.add(other);
+  }
+  return adjacent;
+}
+
+void vizgroupNodes(Node node) {
+  vizgroup.clear();
+  String the_vizgroup = node.vizgroup;
+  Iterator it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    String key = (String) it.next();
+    Node n = (Node) nodes.get(key);
+    if (the_vizgroup.equals(n.vizgroup)) {
+      if (n != null) vizgroup.add(n);
+    }
+  }
+}
+
+void rootNode(Node node) {
+  if (node == null) {
+    if (debug) println("EMPTY node");
+    return;
+  }
+  if (debug) {
+    println("rootNode " + node.name);
+  }
+  physics.clear();
+  edges.clear();
+  nodes.clear();
+  usercolors.clear();
+  node.root = true;
+  node.fix();
+  node.position(0, 0);
+  node.c = rootColor;
+  addNode(node);
+  if ( javascript != null ) {
+    //javascript.countassoc(node.id, node.proj);
+  }  
+}
+
+void markEdge() {
+  Iterator it;
+  Object k;
+  Edge edge;
+  Node node;
+  String key;
+  ArrayList<Edge> connected = new ArrayList<Edge>();
+  int count;
+  // clear node isa status
+  it = edges.keySet().iterator();
+  while (it.hasNext ()) {
+    key = (String) it.next();
+    edge = (Edge) edges.get(key);
+    edge.from_is = "";
+    edge.to_is  = "";
+    edges.put(key, edge);
+  }
+  it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    k = it.next();
+    node = (Node) nodes.get(k);
+    String the_id = node.id;
+    if (node.isa.equals("leaf")) {
+      checkEdges(node);
+    }
+  }
+  it = nodes.keySet().iterator();
+  while (it.hasNext ()) {
+    k = it.next();
+    node = (Node) nodes.get(k);
+    String the_id = node.id;
+    if (!node.isa.equals("leaf")) {
+      checkEdges(node);
+    }
+  }
+}
+
+void checkEdges(Node node) {
+  String the_id = node.id;
+  ArrayList<Edge> connected = new ArrayList<Edge>();
+  connected = connectedEdges(node);
+  for (int i = 0; i < connected.size(); i++) {
+    Edge edge = (Edge) connected.get(i);
+    Node from = edge.from;
+    Node to = edge.to;
+    if (the_id.equals(from.id) && edge.from_is.equals("")) {
+      if (from.isa.equals("leaf")) {
+        edge.from_is = "leaf";
+        edge.to_is  = "trunk";
+      } 
+      else if (from.isa.equals("branch") && 
+        (to.isa.equals("trunk") || to.isa.equals("root")) ) {
+        edge.from_is = "leaf";
+        edge.to_is  = "trunk";
+      }
+    } 
+    else if (the_id.equals(to.id) && edge.to_is.equals("")) {
+      if (to.isa.equals("leaf")) {
+        edge.to_is  = "leaf";
+        edge.from_is = "trunk";
+      } 
+      else if (to.isa.equals("branch") && 
+        (from.isa.equals("trunk") || from.isa.equals("root")) ) {
+        edge.to_is  = "leaf";
+        edge.from_is = "trunk";
+      }
+    }
+  }
+  boolean found_trunk = false;
+  boolean found_leaf = false;
+  for (int i = 0; i < connected.size(); i++) {
+    Edge edge = (Edge) connected.get(i);
+    Node from = edge.from;
+    Node to = edge.to;
+    if ( (the_id.equals(from.id) && edge.from_is.equals("leaf")) ||
+      (the_id.equals(to.id)  && edge.to_is.equals("leaf")) ) {  
+      found_trunk = true;
+    }
+    if ( (the_id.equals(from.id) && edge.from_is.equals("trunk")) ||
+      (the_id.equals(to.id)  && edge.to_is.equals("trunk")) ) {  
+      found_leaf = true;
+    }
+  }
+  for (int i = 0; i < connected.size(); i++) {
+    Edge edge = (Edge) connected.get(i);
+    Node from = edge.from;
+    Node to = edge.to;
+    if (the_id.equals(from.id) && edge.from_is.equals("")) { 
+      if (from.isa.equals("branch") && to.isa.equals("branch")) {
+        if (found_leaf) {
+          edge.from_is = "leaf";
+          edge.to_is  = "trunk";
+        }
+        if (found_trunk) {
+          edge.from_is = "trunk";
+          edge.to_is  = "leaf";
+        }
+      }
+    } 
+    else if (the_id.equals(to.id) && edge.to_is.equals("")) { 
+      if (to.isa.equals("branch") && from.isa.equals("branch")) {
+        if (found_leaf) {
+          edge.to_is  = "leaf";
+          edge.from_is = "trunk";
+        }
+        if (found_trunk) {
+          edge.to_is  = "trunk";        
+          edge.from_is = "leaf";
+        }
+      }
+    }
+  }
+}
+
+void infoEdge(Edge e) {
+  if (debug) println("infoEdge " + e.asc_id);
+  //func =  "info edge";
+  String asc_id  = e.asc_id;
+  if ( javascript != null ) {
+    javascript.selectedge(asc_id);
+  }
+}
+
+void removeEdge(String asc_id) {
+  Edge e = edges.get(asc_id);
+  hideEdge(e); 
+}
+
+void hideEdge(Edge e) { //TODO renumber duplicate eges between the same node pair.
+  if (debug) println("hideEdge " + e.asc_id);
+  String asc_id = e.asc_id;
+  Node from     = e.from;  
+  Node to       = e.to;
+  edges.remove(asc_id);
+  Iterator it = edges.keySet().iterator();
+  int count_from=0;  
+  int count_to=0;
+  while (it.hasNext ()) {
+    Object k = it.next();
+    Edge edge = (Edge) edges.get(k);
+    if (edge.from.id.equals(from.id) || edge.to.id.equals(from.id)) {
+      count_from++;
+    }
+    if (edge.from.id.equals(to.id) || edge.to.id.equals(to.id)) {
+      count_to++;
+    }
+  }
+  if (count_from == 0) {
+    squeezeNode(from);
+    hideNode(from);
+  }
+  else {
+    from.count = count_from;
+    from.assoc -= 1;
+  }
+  if (count_to == 0) {
+    squeezeNode(to);
+    hideNode(to);    
+  }
+  else {
+    to.count = count_to;
+    to.assoc -= 1;
+  }
+}
+
+ArrayList<Edge> connectedEdges(Node node) {
+  ArrayList<Edge> connected = new ArrayList<Edge>();
+  String the_id = node.id;
+  Iterator it = edges.keySet().iterator();
+  while (it.hasNext ()) {
+    Object k = it.next();
+    Edge edge = (Edge) edges.get(k);
+    if (the_id.equals(edge.from.id) || the_id.equals(edge.to.id)) {
+      connected.add(edge);
+    }
+  }
+  return connected;
+}
+
+ArrayList<Node> connectedNodes(Node node) {
+  ArrayList<Node> connected = new ArrayList<Node>();
+  String the_id = node.id;
+  Iterator it = edges.keySet().iterator();
+  while (it.hasNext ()) {
+    Object k = it.next();
+    Edge e = (Edge) edges.get(k);
+    if ( the_id.equals(e.from.id) )
+      connected.add(e.to);
+    else if ( the_id.equals(e.to.id) )
+      connected.add(e.from);
+  }
+  return connected;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/physics.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,1162 @@
+/*
+This is a cached copy of  http://geoviz.googlecode.com/svn
+Search all code Search in http://geoviz.googlecode.com/svn
+http://geoviz.googlecode.com/svn/trunk/touchgraph/src/main/java/geovista/touchgraph/TGLayout.java
+ */ 
+/*
+ * TouchGraph LLC. Apache-Style Software License
+ *
+ *
+ * Copyright (c) 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.
+ * ====================================================================
+ * Following codes EulerIntegrator are inspired by TGLayout.java.
+ * http://geoviz.googlecode.com/svn/trunk/touchgraph/src/main/java/geovista/touchgraph/TGLayout.java
+ */
+// PHYSICS ////////////////////////////////////////////
+/*
+* May 29, 2005
+* @author jeffrey traer bernstein
+* moified by ns 2012
+*/
+public interface Force {
+  public void turnOn();
+  public void turnOff();
+  public boolean isOn();
+  public boolean isOff();
+  public void apply();
+}
+
+public interface Integrator {
+  public void step( float t );
+}
+
+public class EulerIntegrator implements Integrator {
+  ParticleSystem s;
+
+  public EulerIntegrator( ParticleSystem s ) {
+    this.s = s;
+  }
+
+  public void step( float t ) {
+    s.clearForces();
+    s.applyForces();
+
+    for ( int i = 0; i < s.numberOfParticles(); i++ ) {
+      float lastMaxMotion = maxMotion;
+      maxMotion = 0;
+      motionRatio = 1.0;
+      Particle p = (Particle)s.getParticle( i );
+      if ( p.isFree() ) {
+        p.velocity.add( p.force.x/(p.mass * t), p.force.y/(p.mass * t) );
+        
+        float squared = p.velocity.x*p.velocity.x + p.velocity.y*p.velocity.y;
+        float len = (float) sqrt( squared );
+        maxMotion = max( maxMotion, len );
+        if (maxMotion>0) motionRatio = lastMaxMotion/maxMotion - 1; //subtract 1 to make a positive value mean that
+        else motionRatio = 0;                                       //things are moving faster
+        if ( motionRatio <= 0.001 ) { 
+          if ((maxMotion<0.2 || (maxMotion>1 && damper<0.9)) && damper > 0.01) damper -= 0.005;
+          //If we've slowed down significanly, damp more aggresively (then the line two below)
+          else if (maxMotion<0.4 && damper > 0.003) damper -= 0.003;
+          //If max motion is pretty high, and we just started damping, then only damp slightly
+          else if (damper>0.0001) damper -=0.0001;
+        }
+        if ( maxMotion<0.001 ) {
+          damper=0;
+        }
+        p.velocity.multiplyBy( damper );
+        
+        float dx = p.velocity.x/t;
+        float dy = p.velocity.y/t;
+        dx = max(-MAX_VELOCITY, min(MAX_VELOCITY, dx));
+        dy = max(-MAX_VELOCITY, min(MAX_VELOCITY, dy));
+        p.position.add(dx, dy );
+      }
+    }
+  }
+}
+
+public class RungeKuttaIntegrator implements Integrator {	
+  ArrayList originalPositions;
+  ArrayList originalVelocities;
+  ArrayList k1Forces;
+  ArrayList k1Velocities;
+  ArrayList k2Forces;
+  ArrayList k2Velocities;
+  ArrayList k3Forces;
+  ArrayList k3Velocities;
+  ArrayList k4Forces;
+  ArrayList k4Velocities;
+
+  ParticleSystem s;
+
+  public RungeKuttaIntegrator( ParticleSystem s ) {
+    this.s = s;
+
+    originalPositions = new ArrayList();
+    originalVelocities = new ArrayList();
+    k1Forces    = new ArrayList();
+    k1Velocities = new ArrayList();
+    k2Forces    = new ArrayList();
+    k2Velocities = new ArrayList();
+    k3Forces    = new ArrayList();
+    k3Velocities = new ArrayList();
+    k4Forces    = new ArrayList();
+    k4Velocities = new ArrayList();
+  }
+
+  final void allocateParticles() {
+    while ( s.particles.size () > originalPositions.size() ) {
+      originalPositions.add( new Vector2D() );
+      originalVelocities.add( new Vector2D() );
+      k1Forces.add( new Vector2D() );
+      k1Velocities.add( new Vector2D() );
+      k2Forces.add( new Vector2D() );
+      k2Velocities.add( new Vector2D() );
+      k3Forces.add( new Vector2D() );
+      k3Velocities.add( new Vector2D() );
+      k4Forces.add( new Vector2D() );
+      k4Velocities.add( new Vector2D() );
+    }
+  }
+
+  public final void step( float deltaT ) {	
+    allocateParticles();
+    /////////////////////////////////////////////////////////
+    // save original position and velocities
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      if ( p.isFree() ) {
+        ((Vector2D)originalPositions.get( i )).set( p.position );
+        ((Vector2D)originalVelocities.get( i )).set( p.velocity );
+      }
+      p.force.clear();	// and clear the forces
+    }
+
+    ////////////////////////////////////////////////////////////
+    // get all the k1 values
+    s.applyForces();
+
+    // save the intermediate forces
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      if ( p.isFree() ) {
+        ((Vector2D)k1Forces.get( i )).set( p.force );
+        ((Vector2D)k1Velocities.get( i )).set( p.velocity );
+      }
+      p.force.clear();
+    }
+
+    ////////////////////////////////////////////////////////////
+    // get k2 values
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      String freeDirection = p.freeDirection;
+      if ( p.isFree() ) {
+        Vector2D originalPosition = (Vector2D)originalPositions.get( i );
+        Vector2D k1Velocity = (Vector2D)k1Velocities.get( i );
+
+        if(freeDirection.indexOf("x")>=0)
+          p.position.x = originalPosition.x + k1Velocity.x * deltaT;
+        if(freeDirection.indexOf("y")>=0)
+          p.position.y = originalPosition.y + k1Velocity.y * deltaT;
+        //p.position.z = originalPosition.z + k1Velocity.z * deltaT;
+
+        Vector2D originalVelocity = (Vector2D)originalVelocities.get( i );
+        Vector2D k1Force = (Vector2D)k1Forces.get( i );
+
+        if(freeDirection.indexOf("x")>=0)
+          p.velocity.x = originalVelocity.x + k1Force.x * deltaT / p.mass;
+        if(freeDirection.indexOf("y")>=0)
+          p.velocity.y = originalVelocity.y + k1Force.y * deltaT / p.mass;
+        // p.velocity.z = originalVelocity.z + k1Force.z * deltaT / p.mass;
+      }
+    }
+
+    s.applyForces();
+
+    // save the intermediate forces
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      if ( p.isFree() ) {
+        ((Vector2D)k2Forces.get( i )).set( p.force );
+        ((Vector2D)k2Velocities.get( i )).set( p.velocity );
+      }
+      p.force.clear();	// and clear the forces now that we are done with them
+    }
+
+    /////////////////////////////////////////////////////
+    // get k3 values
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      String freeDirection = p.freeDirection;
+      if ( p.isFree() ) {
+        Vector2D originalPosition = (Vector2D)originalPositions.get( i );
+        Vector2D k2Velocity = (Vector2D)k2Velocities.get( i );
+
+        if(freeDirection.indexOf("x")>=0)
+          p.position.x = originalPosition.x + k2Velocity.x * deltaT;
+        if(freeDirection.indexOf("y")>=0)
+          p.position.y = originalPosition.y + k2Velocity.y * deltaT;
+        //p.position.z = originalPosition.z + k2Velocity.z * deltaT;
+
+        Vector2D originalVelocity = (Vector2D)originalVelocities.get( i );
+        Vector2D k2Force = (Vector2D)k2Forces.get( i );
+
+        if(freeDirection.indexOf("x")>=0)
+          p.velocity.x = originalVelocity.x + k2Force.x * deltaT / p.mass;
+        if(freeDirection.indexOf("y")>=0)
+          p.velocity.y = originalVelocity.y + k2Force.y * deltaT / p.mass;
+        //p.velocity.z = originalVelocity.z + k2Force.z * deltaT / p.mass;
+      }
+    }
+
+    s.applyForces();
+
+    // save the intermediate forces
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      if ( p.isFree() ) {
+        ((Vector2D)k3Forces.get( i )).set( p.force );
+        ((Vector2D)k3Velocities.get( i )).set( p.velocity );
+      }
+      p.force.clear();	// and clear the forces now that we are done with them
+    }
+
+    //////////////////////////////////////////////////
+    // get k4 values
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      String freeDirection = p.freeDirection;
+      if ( p.isFree() ) {
+        Vector2D originalPosition = (Vector2D)originalPositions.get( i );
+        Vector2D k3Velocity = (Vector2D)k3Velocities.get( i );
+
+        if(freeDirection.indexOf("x")>=0)
+          p.position.x = originalPosition.x + k3Velocity.x * deltaT;
+        if(freeDirection.indexOf("y")>=0)
+          p.position.y = originalPosition.y + k3Velocity.y * deltaT;
+        //p.position.z = originalPosition.z + k3Velocity.z * deltaT;
+
+        Vector2D originalVelocity = (Vector2D)originalVelocities.get( i );
+        Vector2D k3Force = (Vector2D)k3Forces.get( i );
+
+        if(freeDirection.indexOf("x")>=0)
+          p.velocity.x = originalVelocity.x + k3Force.x * deltaT / p.mass;
+        if(freeDirection.indexOf("y")>=0)
+          p.velocity.y = originalVelocity.y + k3Force.y * deltaT / p.mass;
+        //p.velocity.z = originalVelocity.z + k3Force.z * deltaT / p.mass;
+      }
+    }
+
+    s.applyForces();
+
+    // save the intermediate forces
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      if ( p.isFree() ) {
+        ((Vector2D)k4Forces.get( i )).set( p.force );
+        ((Vector2D)k4Velocities.get( i )).set( p.velocity );
+      }
+    }
+
+    /////////////////////////////////////////////////////////////
+    // put them all together and what do you get?
+    float lastMaxMotion = maxMotion;
+    maxMotion = 0;
+    motionRatio = 1.0;
+    for ( int i = 0; i < s.particles.size(); ++i ) {
+      Particle p = (Particle)s.particles.get( i );
+      String freeDirection = p.freeDirection;
+      p.age += deltaT;
+      if ( p.isFree() ) {
+        // update position
+        Vector2D originalPosition = (Vector2D)originalPositions.get( i );
+        Vector2D k1Velocity = (Vector2D)k1Velocities.get( i );
+        Vector2D k2Velocity = (Vector2D)k2Velocities.get( i );
+        Vector2D k3Velocity = (Vector2D)k3Velocities.get( i );
+        Vector2D k4Velocity = (Vector2D)k4Velocities.get( i );
+
+        Vector2D dPosition = new Vector2D();
+        if(freeDirection.indexOf("x")>=0)
+          dPosition.x = deltaT / 6.0f * ( k1Velocity.x + 2.0f * k2Velocity.x + 2.0f * k3Velocity.x + k4Velocity.x );
+        if(freeDirection.indexOf("y")>=0)
+          dPosition.y = deltaT / 6.0f * ( k1Velocity.y + 2.0f * k2Velocity.y + 2.0f * k3Velocity.y + k4Velocity.y );
+        //dPosition.z = deltaT / 6.0f * ( k1Velocity.z + 2.0f * k2Velocity.z + 2.0f * k3Velocity.z + k4Velocity.z );
+        
+        float squared = dPosition.x*dPosition.x + dPosition.y*dPosition.y /*+ dPosition.z*dPosition.z*/;
+        float len = (float) Math.sqrt( squared );
+        maxMotion = Math.max( maxMotion, len );
+        if (maxMotion>0) motionRatio = lastMaxMotion/maxMotion - 1; //subtract 1 to make a positive value mean that
+        else motionRatio = 0;                                      //things are moving faster
+        if ( motionRatio <= 0.001 ) { 
+          //This is important.  Only damp when the graph starts to move faster
+          //When there is noise, you damp roughly half the time. (Which is a lot)
+          //If things are slowing down, then you can let them do so on their own,
+          //without damping.
+          //If max motion<0.2, damp away
+          //If by the time the damper has ticked down to 0.9, maxMotion is still>1, damp away
+          //We never want the damper to be negative though
+//          if ((maxMotion<0.1 || (maxMotion>1 && damper<0.3)) && damper > 0.01) damper -= 0.01;
+          if ((maxMotion<0.2 || (maxMotion>1 && damper<0.7)) && damper > 0.01) damper -= 0.01;
+          //If we've slowed down significanly, damp more aggresively (then the line two below)
+          else if (maxMotion<0.4 && damper > 0.003) damper -= 0.003;
+          //If max motion is pretty high, and we just started damping, then only damp slightly
+          else if (damper>0.0001) damper -=0.0001;
+        }
+        if ( maxMotion<0.001 ) {
+          damper=0;
+        }
+        
+        dPosition.multiplyBy( damper );
+        
+        if (len > MAX_DISTANCE) {
+          dPosition.multiplyBy( MAX_DISTANCE/len );
+        }
+
+        if(freeDirection.indexOf("x")>=0)
+          p.position.x = originalPosition.x + dPosition.x;
+        if(freeDirection.indexOf("y")>=0)
+          p.position.y = originalPosition.y + dPosition.y;
+        //p.position.z = originalPosition.z + dPosition.z;
+
+        // update velocity
+        Vector2D originalVelocity = (Vector2D)originalVelocities.get( i );
+        Vector2D k1Force = (Vector2D)k1Forces.get( i );
+        Vector2D k2Force = (Vector2D)k2Forces.get( i );
+        Vector2D k3Force = (Vector2D)k3Forces.get( i );
+        Vector2D k4Force = (Vector2D)k4Forces.get( i );
+        Vector2D dVelocity = new Vector2D();
+
+        if(freeDirection.indexOf("x")>=0)
+          dVelocity.x = deltaT / ( 6.0f * p.mass ) * ( k1Force.x + 2.0f * k2Force.x + 2.0f * k3Force.x + k4Force.x );
+        if(freeDirection.indexOf("y")>=0)
+          dVelocity.y = deltaT / ( 6.0f * p.mass ) * ( k1Force.y + 2.0f * k2Force.y + 2.0f * k3Force.y + k4Force.y );
+        //dVelocity.z = deltaT / ( 6.0f * p.mass ) * ( k1Force.z + 2.0f * k2Force.z + 2.0f * k3Force.z + k4Force.z );
+
+        squared = dVelocity.x*dVelocity.x + dVelocity.y*dVelocity.y /*+ dVelocity.z*dVelocity.z*/;
+        len = (float) Math.sqrt(squared);      
+        if (len > MAX_VELOCITY) {
+          if(freeDirection.indexOf("x")>=0)
+            dVelocity.x *= MAX_VELOCITY/len;        
+          if(freeDirection.indexOf("y")>=0)
+            dVelocity.y *= MAX_VELOCITY/len;        
+          //dVelocity.z *= MAX_VELOCITY/len;
+        } 
+
+        if(freeDirection.indexOf("x")>=0)
+          p.velocity.x = originalVelocity.x + dVelocity.x;
+        if(freeDirection.indexOf("y")>=0)
+          p.velocity.y = originalVelocity.y + dVelocity.y;
+        //p.velocity.z = originalVelocity.z + dVelocity.z;
+      }
+    }
+  }
+}
+
+public class Attraction implements Force {
+  Particle a;
+  Particle b;
+  float k;
+  boolean on;
+  float distanceMin;
+  float distanceMinSquared;
+
+  public Attraction( Particle a, Particle b, float k, float distanceMin ) {
+    this.a = a;
+    this.b = b;
+    this.k = k;
+    on = true;
+    this.distanceMin = distanceMin;
+    this.distanceMinSquared = distanceMin*distanceMin;
+  }
+
+  public void setA( Particle p ) {
+    a = p;
+  }
+
+  public void setB( Particle p ) {
+    b = p;
+  }
+
+  public final float getMinimumDistance() {
+    return distanceMin;
+  }
+
+  public final void setMinimumDistance( float d ) {
+    distanceMin = d;
+    distanceMinSquared = d*d;
+  }
+
+  public final void turnOff() {
+    on = false;
+  }
+
+  public final void turnOn() {
+    on = true;
+  }
+
+  public final void setStrength( float k ) {
+    this.k = k;
+  }
+
+  public final Particle getOneEnd() {
+    return a;
+  }
+
+  public final Particle getTheOtherEnd() {
+    return b;
+  }
+
+  public void apply() {
+    if ( on && ( a.isFree() || b.isFree() ) ) {
+      float a2bX = a.position.x - b.position.x;
+      float a2bY = a.position.y - b.position.y;
+      float a2bDistanceSquared = a2bX*a2bX + a2bY*a2bY;
+
+      if ( a2bDistanceSquared < distanceMinSquared ) {
+        a2bX = random(-distanceMin,distanceMin);        
+        a2bY = random(-distanceMin,distanceMin);
+      }
+      
+      float force = k * a.mass * b.mass / a2bDistanceSquared;
+
+      float length = (float)Math.sqrt( a2bDistanceSquared );
+
+      // make unit vector
+      a2bX /= length;
+      a2bY /= length;
+
+      // multiply by force 
+      a2bX *= force;
+      a2bY *= force;
+
+      // apply
+      if (length < 600) {
+        if ( a.isFree() )
+          a.force.add( -a2bX, -a2bY );
+        if ( b.isFree() )
+          b.force.add( a2bX, a2bY );
+      }
+    }
+  }
+
+  public final float getStrength() {
+    return k;
+  }
+
+  public final boolean isOn() {
+    return on;
+  }
+
+  public final boolean isOff() {
+    return !on;
+  }
+}
+
+public class Particle {
+  public Vector2D position;
+  public Vector2D velocity;
+  public Vector2D force;
+  public float mass;
+  public float age;
+  public boolean dead;
+  public boolean fixed;
+  public String freeDirection;
+
+  public Particle( float m ) {
+    position = new Vector2D();
+    velocity = new Vector2D();
+    force    = new Vector2D();
+    mass    = m;
+    age      = 0;
+    dead    = false;
+    fixed    = false;
+    freeDirection = "xy";
+  }
+
+  public final float distanceTo( Particle p ) {
+    return this.position.distanceTo( p.position );
+  }
+
+  public final void makeFixed() {
+    fixed = true;
+    velocity.clear();
+  }
+
+  public final boolean isFixed() {
+    return fixed;
+  }
+
+  public final boolean isFree() {
+    return !fixed;
+  }
+
+  public final void makeFree() {
+    fixed = false;
+  }
+
+  public final Vector2D position() {
+    return position;
+  }
+
+  public final Vector2D velocity() {
+    return velocity;
+  }
+
+  public final float mass() {
+    return mass;
+  }
+
+  public final void setMass( float m ) {
+    mass = m;
+  }
+
+  public final Vector2D force() {
+    return force;
+  }
+
+  public final float age() {
+    return age;
+  }
+
+  public void reset() {
+    age = 0;
+    dead = false;
+    position.clear();
+    velocity.clear();
+    force.clear();
+    mass = 1f;
+  }
+}
+
+public class ParticleSystem {
+  public static final color RUNGE_KUTTA = 0;
+  public static final color EULER = 1;  
+  public static final color MODIFIED_EULER = 2;
+
+  public static final float DEFAULT_GRAVITY = 0;
+  public static final float DEFAULT_DRAG = 0.001f;	
+
+  ArrayList particles;
+  ArrayList springs;
+  ArrayList attractions;
+  ArrayList customForces = new ArrayList();
+
+  Integrator integrator;
+
+  Vector2D gravity;
+  float drag;
+
+  boolean hasDeadParticles = false;
+
+  public final void setIntegrator( int integrator ) {
+    switch ( integrator ) {
+    case RUNGE_KUTTA:
+      this.integrator = new RungeKuttaIntegrator( this );
+      break;
+    case EULER:
+      this.integrator = new EulerIntegrator( this );
+      break;
+    }
+  }
+
+  public final void setGravity( float x, float y/*, float z*/ ) {
+    gravity.set( x, y );
+  }
+
+  // default down gravity
+  public final void setGravity( float g ) {
+    gravity.set( 0, g );
+  }
+
+  public final void setDrag( float d ) {
+    drag = d;
+  }
+
+  public final void tick() {
+    tick( 1 );
+  }
+
+  public final void tick( float t ) {  
+    integrator.step( t );
+  }
+
+  public final Particle makeParticle( float mass, float x, float y/*, float z*/ ) {
+    Particle p = new Particle( mass );
+    p.position.set( x, y );
+    particles.add( p );
+    return p;
+  }
+
+  public final Particle makeParticle() {  
+    return makeParticle( 1.0f, 0f, 0f );
+  }
+
+  public final Spring makeSpring( Particle a, Particle b, float ks, float d, float r ) {
+    Spring s = new Spring( a, b, ks, d, r );
+    springs.add( s );
+    return s;
+  }
+
+  public final Attraction makeAttraction( Particle a, Particle b, float k, float minDistance ) {
+    Attraction m = new Attraction( a, b, k, minDistance );
+    attractions.add( m );
+    return m;
+  }
+
+  public final void clear() {
+    particles.clear();
+    springs.clear();
+    attractions.clear();
+  }
+
+  public ParticleSystem( float g, float somedrag ) {
+    integrator  = new RungeKuttaIntegrator( this );
+    particles  = new ArrayList();
+    springs    = new ArrayList();
+    attractions = new ArrayList();
+    gravity    = new Vector2D( 0, g );
+    drag        = somedrag;
+  }
+
+  public ParticleSystem( float gx, float gy, float gz, float somedrag ) {
+    integrator  = new RungeKuttaIntegrator( this );
+    particles  = new ArrayList();
+    springs    = new ArrayList();
+    attractions = new ArrayList();
+    gravity    = new Vector2D( gx, gy );
+    drag        = somedrag;
+  }
+
+  public ParticleSystem() {
+    integrator  = new RungeKuttaIntegrator( this );
+    particles  = new ArrayList();
+    springs    = new ArrayList();
+    attractions = new ArrayList();
+    gravity    = new Vector2D( 0, ParticleSystem.DEFAULT_GRAVITY );
+    drag        = ParticleSystem.DEFAULT_DRAG;
+  }
+
+  public final void applyForces() {
+    if ( !gravity.isZero() ) {
+      for ( int i = 0; i < particles.size(); ++i ) {
+        Particle p = (Particle)particles.get( i );
+        p.force.add( gravity );
+      }
+    }
+
+    for ( int i = 0; i < particles.size(); ++i ) {
+      Particle p = (Particle)particles.get( i );
+      p.force.add( p.velocity.x * -drag, p.velocity.y * -drag );
+    }
+
+    for ( int i = 0; i < springs.size(); i++ ) {
+      Spring f = (Spring)springs.get( i );
+      f.apply();
+    }
+
+    for ( int i = 0; i < attractions.size(); i++ ) {
+      Attraction f = (Attraction)attractions.get( i );
+      f.apply();
+    }
+
+    for ( int i = 0; i < customForces.size(); i++ ) {
+      Force f = (Force)customForces.get( i );
+      f.apply();
+    }
+  }
+
+  public final void clearForces() {
+    Iterator i = particles.iterator();
+    while ( i.hasNext () ) {
+      Particle p = (Particle)i.next();
+      p.force.clear();
+    }
+  }
+
+  public final int numberOfParticles() {
+    return particles.size();
+  }
+
+  public final int numberOfSprings() {
+    return springs.size();
+  }
+
+  public final int numberOfAttractions() {
+    return attractions.size();
+  }
+
+  public final Particle getParticle( int i ) {
+    return (Particle)particles.get( i );
+  }
+
+  public final Spring getSpring( int i ) {
+    return (Spring)springs.get( i );
+  }
+
+  public final Attraction getAttraction( int i ) {
+    return (Attraction)attractions.get( i );
+  }
+
+  public final void addCustomForce( Force f ) {
+    customForces.add( f );
+  }
+
+  public final int numberOfCustomForces() {
+    return customForces.size();
+  }
+
+  public final Force getCustomForce( int i ) {
+    return (Force)customForces.get( i );
+  }
+
+  public final Force removeCustomForce( int i ) {
+    return (Force)customForces.remove( i );
+  }
+
+  public final void removeParticle( Particle p ) {
+    particles.remove( p );
+  }
+
+  public final Spring removeSpring( int i ) {
+    return (Spring)springs.remove( i );
+  }
+
+  public final Attraction removeAttraction( int i  ) {
+    return (Attraction)attractions.remove( i );
+  }
+
+  public final void removeAttraction( Attraction s ) {
+    attractions.remove( s );
+  }
+
+  public final void removeSpring( Spring a ) {
+    springs.remove( a );
+  }
+
+  public final void removeCustomForce( Force f ) {
+    customForces.remove( f );
+  }
+}
+
+public class Spring implements Force {
+  float springConstant;
+  float damping;
+  float restLength;
+  Particle a, b;
+  boolean on;
+
+  public Spring( Particle A, Particle B, float ks, float d, float r ) {
+    springConstant = ks;
+    damping = d;
+    restLength = r;
+    a = A;
+    b = B;
+    on = true;
+  }
+
+  public final void turnOff() {
+    on = false;
+  }
+
+  public final void turnOn() {
+    on = true;
+  }
+
+  public final boolean isOn() {
+    return on;
+  }
+
+  public final boolean isOff() {
+    return !on;
+  }
+
+  public final Particle getOneEnd() {
+    return a;
+  }
+
+  public final Particle getTheOtherEnd() {
+    return b;
+  }
+
+  public final float currentLength() {
+    return a.position.distanceTo( b.position );
+  }
+
+  public final float restLength() {
+    return restLength;
+  }
+
+  public final float strength() {
+    return springConstant;
+  }
+
+  public final void setStrength( float ks ) {
+    springConstant = ks;
+  }
+
+  public final float damping() {
+    return damping;
+  }
+
+  public final void setDamping( float d ) {
+    damping = d;
+  }
+
+  public final void setRestLength( float l ) {
+    restLength = l;
+  }
+
+  public final void apply() {	
+    if ( on && ( a.isFree() || b.isFree() ) ) {
+      float a2bX = a.position.x - b.position.x;
+      float a2bY = a.position.y - b.position.y;
+
+      float a2bDistance = (float)Math.sqrt( a2bX*a2bX + a2bY*a2bY );
+
+      if ( a2bDistance == 0 ) {
+        a2bX = 0;
+        a2bY = 0;
+        //a2bZ = 0;
+      }
+      else {
+        a2bX /= a2bDistance;
+        a2bY /= a2bDistance;
+      }
+
+      // spring force is proportional to how much it stretched 
+      float springForce = -( a2bDistance - restLength ) * springConstant; 
+
+      // want velocity along line b/w a & b, damping force is proportional to this
+      float Va2bX = a.velocity.x - b.velocity.x;
+      float Va2bY = a.velocity.y - b.velocity.y;
+
+
+      float dampingForce = -damping * ( a2bX*Va2bX + a2bY*Va2bY  );
+
+      // forceB is same as forceA in opposite direction
+      float r = springForce + dampingForce;
+
+      a2bX *= r;
+      a2bY *= r;
+
+      if (a2bDistance < MIN_DISTANCE) {
+        if ( a.isFree() )
+          a.force.add( a2bX, a2bY );
+        if ( b.isFree() )
+          b.force.add( -a2bX, -a2bY );
+      }
+      else {
+        if ( a.isFree() )
+          a.force.add( a2bX/10, a2bY/10 );
+        if ( b.isFree() )
+          b.force.add( -a2bX/10, -a2bY/10 );
+      }
+
+    }
+  }
+
+  public void setA( Particle p ) {
+    a = p;
+  }
+
+  public void setB( Particle p ) {
+    b = p;
+  }
+}
+
+public class Vector2D {
+  float x;    
+  float y;
+
+  public Vector2D( float X, float Y ) {
+    x = X;    
+    y = Y;
+  }
+
+  public Vector2D( ) {
+    x = 0;    
+    y = 0;
+  }
+
+  public Vector2D( Vector2D p ) {
+    x = p.x;  
+    y = p.y;
+  }
+
+  public final void set( float X, float Y ) { 
+    x = X; 
+    y = Y;
+  }
+
+  public final void set( Vector2D p ) { 
+    x = p.x; 
+    y = p.y;
+  }
+
+  public final void add( Vector2D p ) { 
+    x += p.x; 
+    y += p.y;
+  }
+  public final void subtract( Vector2D p ) { 
+    x -= p.x; 
+    y -= p.y;
+  }
+
+  public final void add( float a, float b ) { 
+    x += a; 
+    y += b;
+  } 
+  public final void subtract( float a, float b ) { 
+    x -= a; 
+    y -= b;
+  } 
+
+  public final Vector2D multiplyBy( float f ) { 
+    x *= f; 
+    y *= f; 
+    return this;
+  }
+
+  public final float distanceTo( Vector2D p ) { 
+    return (float)Math.sqrt( distanceSquaredTo( p ) );
+  }
+
+  public final float distanceSquaredTo( Vector2D p ) { 
+    float dx = x-p.x; 
+    float dy = y-p.y; 
+    return dx*dx + dy*dy;
+  }
+
+  public final float distanceTo( float x, float y ) {
+    float dx = this.x - x;
+    float dy = this.y - y;
+    return (float)Math.sqrt( dx*dx + dy*dy );
+  }
+
+  public final float length() { 
+    return (float)Math.sqrt( x*x + y*y );
+  }
+
+  public final float lengthSquared() { 
+    return x*x + y*y;
+  }
+
+  public final void clear() { 
+    x = 0; 
+    y = 0;
+  }
+
+  public final String toString() { 
+    return new String( "(" + x + ", " + y + ")" );
+  }
+
+  public boolean isZero() {
+    return x == 0 && y == 0;
+  }
+
+  float dot( Vector2D p ) {
+    return x * p.x + y * p.y;
+  }
+
+  float cross( Vector2D p ) {
+    float c = (x * p.y - y * p.x);
+    return (float) Math.sqrt( c * c );
+  }
+
+  float abs() {
+    return (float) Math.sqrt( x * x + y * y );
+  }
+
+  float distance( Vector2D a, Vector2D b) {
+    float EPS = 0.001f;
+    Vector2D b_a = new Vector2D( b.x - a.x, b.y - a.y );
+    Vector2D p_a = new Vector2D( x - a.x, y - a.y );
+    Vector2D a_b = new Vector2D( a.x - b.x, a.y - b.y );
+    Vector2D p_b = new Vector2D( x - b.x, y - b.y );
+    if ( b_a.dot(p_a) < EPS )      return p_a.abs();
+    else if ( a_b.dot(p_b) < EPS ) return p_b.abs();
+    else                          return ( b_a.cross(p_a) / b_a.abs() );
+  }
+}
+
+public class Vector3D {
+  float x;
+  float y;
+  float z;
+
+  public Vector3D( float X, float Y, float Z ) { 
+    x = X; 
+    y = Y; 
+    z = Z;
+  }
+
+  public Vector3D() { 
+    x = 0; 
+    y = 0; 
+    z = 0;
+  }
+
+  public Vector3D( Vector3D p ) { 
+    x = p.x; 
+    y = p.y; 
+    z = p.z;
+  }
+
+  public final float z() { 
+    return z;
+  }
+
+  public final float y() { 
+    return y;
+  }
+
+  public final float x() { 
+    return x;
+  }
+
+  public final void setX( float X ) { 
+    x = X;
+  }
+
+  public final void setY( float Y ) { 
+    y = Y;
+  }
+
+  public final void setZ( float Z ) { 
+    z = Z;
+  }
+
+  public final void set( float X, float Y, float Z ) { 
+    x = X; 
+    y = Y; 
+    z = Z;
+  }
+
+  public final void set( Vector3D p ) { 
+    x = p.x; 
+    y = p.y; 
+    z = p.z;
+  }
+
+  public final void add( Vector3D p ) { 
+    x += p.x; 
+    y += p.y; 
+    z += p.z;
+  }
+  public final void subtract( Vector3D p ) { 
+    x -= p.x; 
+    y -= p.y; 
+    z -= p.z;
+  }
+
+  public final void add( float a, float b, float c ) { 
+    x += a; 
+    y += b; 
+    z += c;
+  } 
+  public final void subtract( float a, float b, float c ) { 
+    x -= a; 
+    y -= b; 
+    z -= c;
+  } 
+
+  public final Vector3D multiplyBy( float f ) { 
+    x *= f; 
+    y *= f; 
+    z *= f; 
+    return this;
+  }
+
+  public final float distanceTo( Vector3D p ) { 
+    return (float)Math.sqrt( distanceSquaredTo( p ) );
+  }
+
+  public final float distanceSquaredTo( Vector3D p ) { 
+    float dx = x-p.x; 
+    float dy = y-p.y; 
+    float dz = z-p.z; 
+    return dx*dx + dy*dy + dz*dz;
+  }
+
+  public final float distanceTo( float x, float y, float z ) {
+    float dx = this.x - x;
+    float dy = this.y - y;
+    float dz = this.z - z;
+    return (float)Math.sqrt( dx*dx + dy*dy + dz*dz );
+  }
+
+  public final float dot( Vector3D p ) { 
+    return x*p.x + y*p.y + z*p.z;
+  }
+
+  public final float length() { 
+    return (float)Math.sqrt( x*x + y*y + z*z );
+  }
+
+  public final float lengthSquared() { 
+    return x*x + y*y + z*z;
+  }
+
+  public final void clear() { 
+    x = 0; 
+    y = 0; 
+    z = 0;
+  }
+
+  public final String toString() { 
+    return new String( "(" + x + ", " + y + ", " + z + ")" );
+  }
+
+  public final Vector3D cross( Vector3D p ) {
+    return new Vector3D( 
+    this.y * p.z - this.z * p.y, 
+    this.x * p.z - this.z * p.x, 
+    this.x * p.y - this.y * p.x );
+  }
+
+  public boolean isZero() {
+    return x == 0 && y == 0 && z == 0;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/preload.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,1 @@
+/* @pjs preload="/blog/wp-content/uploads/2012/05/genbakunoko.jpg"; */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hp/static/hp/tmgraph/tmgraph.pde	Wed Nov 14 17:26:06 2012 +0100
@@ -0,0 +1,370 @@
+String title = "17 June, 2012 ver.02";
+PFont font;
+
+String ln = "en";// "ja" "fr"
+boolean personal        = true;
+boolean editmode        = true;
+boolean debug           = false;
+boolean nodetrace       = false;
+boolean edgetrace       = false;
+boolean eventtrace      = false;
+boolean attractiontrace = false;
+boolean load            = false;
+boolean showusername    = false;
+
+ParticleSystem physics;
+Node selection, 
+     selected, 
+     previous_selection,
+     from_node, to_node,
+     dragnode; 
+Edge sel_edge, 
+     selected_edge;
+int edgeCount = 0;
+int topicCount = 0;
+
+boolean touch          = false;
+boolean free           = true;
+boolean dragging       = false;
+boolean draggingnode   = false;
+boolean menu_active    = false;
+boolean apmenu_active  = false;
+boolean edgmenu_active = false;
+boolean mouseMode      = false;
+boolean dowbleclick    = false;
+boolean touched        = false;
+boolean holding        = false;
+boolean dragmode       = false;
+
+int menu    = 0;
+int edgmenu = 0;
+int apmenu  = 0;
+int centerX,     
+    centerY;
+int touchX,      
+    touchY;
+int menuX,       
+    menuY;
+int scrnX,       
+    scrnY;
+int pressStartX, 
+    pressStartY;
+int pointX,      
+    pointY;
+int topicX,      
+    topicY;
+
+float start             = 0;
+float elapsedTimeMillis = 0;
+float elapsedTimeSec    = 0.0;
+float factor            = 1.0;
+float damper            = 1.0;
+float maxMotion         = 0;
+float motionRatio;
+float start_dist, 
+      currt_dist;
+float scl;
+float theta1, 
+      phai, 
+      theta2;
+float fromX, 
+      fromY, 
+      toX, 
+      toY, 
+      cX, 
+      cY;
+
+String both = "both";
+String msg  = "";
+String str  = "";
+String event= "";
+String func = "";
+
+String unescapeSTR(String text) {
+  String str=text;
+  if (str == null || str.equals("")) return "";
+  str = str.replaceAll("&amp;","&");
+  str = str.replaceAll("&#034;","\"");	    
+  str = str.replaceAll("&#039;","'");
+  str = str.replaceAll("&quot;","\"");
+  str = str.replaceAll("&lt;","<");
+  str = str.replaceAll("&gt;",">");
+  str = str.replaceAll("<br>","\n");
+  return str;
+}
+
+String trimSTR(String text) {
+  String str=text;
+  if (str == null || str.equals("")) return "";
+  str = str.replaceAll("\n","");
+  str = str.replaceAll("\f","");	    
+  str = str.replaceAll(" ","");
+  str = str.replaceAll(" ","");
+  str = str.replaceAll("\r","");
+  return str;
+}
+
+void setDebugmode(){
+  debug = true;
+}
+
+////////  PROCESSING  //////////////////////////////////////////////////////////
+int WORLD_SIZE_W, WORLD_SIZE_H;
+
+void setSize(int w, int h) { 
+  if (javascript != null) {
+    WORLD_SIZE_W = w;
+    WORLD_SIZE_H = h;  
+    size(w, h);  
+  }
+}
+
+void setup() {
+  WORLD_SIZE_W = 600;
+  WORLD_SIZE_H = 600;
+  size(WORLD_SIZE_W, WORLD_SIZE_H);
+  //size(600, 600);
+
+  physics = new ParticleSystem( 0, DRAGFACTOR/* 0.1*/ ); 
+  // Runge-Kutta, the default integrator is stable and snappy,
+  // but slows down quickly as you add particles.
+  // Try this to see how Euler is faster, but borderline unstable.
+  // physics.setIntegrator( ParticleSystem.EULER ); 
+  // Now try this to see make it more damped, but stable.
+  physics.setDrag( DRAGFACTOR );
+  physics.clear(); 
+  
+  setNodeShapes();
+  if (load) loadData();
+  
+  frameRate(FRAME);
+  smooth();
+  
+  selected      = null;
+  selection    = null;
+  menu_active  = false;
+  apmenu_active = false;
+  mouseMode    = false;
+  damper        = 1.0f;
+  
+  if( debug || load || nodetrace || edgetrace || eventtrace || attractiontrace || editmode ) setMode();
+  stopDoubleClickTimer();
+}
+
+void draw() {
+  physics.tick(TICK); 
+  if (dragmode)
+    bgColor = MintCream;
+  else
+    bgColor = White;
+  background(bgColor);
+  smooth();  
+  
+  translate( WORLD_SIZE_W/2, WORLD_SIZE_H/2); 
+  if (dragging == true) {
+    pushMatrix();
+    translate((pointX - pressStartX) * factor, 
+              (pointY - pressStartY) * factor);
+  }  
+  
+  drawNetwork();  
+  
+  if (dragging == true) popMatrix();
+  if (menu_active)         showMenu();
+  else if (apmenu_active)  showApMenu();  
+  else if (edgmenu_active) showEdgMenu();
+  translate( -WORLD_SIZE_W/2, -WORLD_SIZE_H/2); 
+  debugTrace();
+  checkHoldTimer();
+}
+
+void zoom(float value) {
+  factor = round(value*10)/10;
+}
+
+void drawNetwork() {
+  noStroke();
+  // draw attractions
+  if (attractiontrace) {
+    Iterator ita = physics.attractions.iterator();
+    while (ita.hasNext ()) {
+      Attraction attraction = (Attraction) ita.next();
+      if ( attraction.isOn() ) {
+        strokeWeight(1);
+        stroke(LightPink);
+        line(factor*attraction.getOneEnd().position.x,      factor*attraction.getOneEnd().position.y,
+            factor*attraction.getTheOtherEnd().position.x, factor*attraction.getTheOtherEnd().position.y);
+      }
+    }
+  }  
+  // draw edges
+  Iterator ite = edges.entrySet().iterator();
+  while (ite.hasNext ()) {
+    Map.Entry ee = (Map.Entry)ite.next();
+    String k  = (String) ee.getKey();
+    Edge edge = (Edge) ee.getValue();
+    edge.show();
+  }
+  if (sel_edge != null)    sel_edge.highlight();
+  // draw vertices
+  Iterator itv = nodes.entrySet().iterator();
+  while (itv.hasNext ()) {
+    Map.Entry ev = (Map.Entry)itv.next();
+    String k  = (String) ev.getKey();
+    Node node = (Node) ev.getValue();
+    node.update();
+    if(node != selection && node != selected) node.show();
+  }  
+  if ( selection != null)  {
+    selection.highlight();
+    highlite_vizgroup();
+  }
+  if ( selected  != null )  selected.highlight();  
+  if ( from_node  != null ) from_node.highlight();  
+  if ( sel_edge  != null )  sel_edge.highlight();
+  markNode();
+  markEdge();
+}
+
+void highlite_vizgroup() {//Displays selected group topics
+  if (vizgroup!=null){
+    for (int i = 0; i < vizgroup.size(); i++) {
+      Node n = (Node) vizgroup.get(i);
+      n.highlight();
+    }  
+  }
+}
+
+////////  DEBUG TRACE  /////////////////////////////////////////////////////////
+void funcTrace(String f) {
+  func = f;
+  fill(Gray);
+  textSize(12);
+  textAlign(RIGHT, TOP);
+  text(f, WORLD_SIZE_W - 20, 10);
+}
+
+void debugTrace() {
+  funcTrace(func);
+  fill(Gray);
+  textSize(12);
+  textAlign(LEFT, TOP);
+  str = "scale:" + factor; text(str, 10, 10);
+  str = (personal)?"personal":"team"; 
+  str += " work";
+  text(str, 70, 10);
+  
+  Iterator it = usercolors.keySet().iterator();
+  int i=0,loc=10,h=10;
+  while (it.hasNext ()) {
+    String key = (String) it.next();
+    UserColor usercolor = (UserColor) usercolors.get(key);
+    stroke(usercolor.c); 
+    strokeWeight(3);   
+    line (loc,WORLD_SIZE_H-h,loc+20,WORLD_SIZE_H-h);
+    text(usercolor.name,loc+25,WORLD_SIZE_H-h-10);
+    loc += textWidth(usercolor.name)+35;
+    if (loc > WORLD_SIZE_W - 100) {
+      loc=10; h -= 10; 
+    }
+  }
+ 
+  //str = "motion:" + toStr(mouse_motion); text(str, 10, 10);
+  if (debug) {
+    if (!isMouse) {
+      str = "touch menu active:" + menu_active;            text(str, 10, 20);
+      str = "apmenu active:" + apmenu_active;              text(str, 150, 20);
+      str = "menu:" + toStr(menu);                         text(str, 10, 30);
+      str = "apmenu:" + toStr(apmenu);                     text(str, 150, 30);
+      str = "tuoch x=" + touchX + " y=" + touchY;          text(str, 10, 40);
+      str = "screen x=" + scrnX + " y=" + scrnY;           text(str, 140, 40);
+      str = "point x=" + pointX + " y=" + pointY;          text(str, 270, 40);
+      str = "screen x=" + scrnX + " y=" + scrnY;           text(str, 400, 40);
+      str = "start x=" + pressStartX + " y=" + pressStartY; text(str, 530, 40);
+      str = (selection != null)? selection.name : "";  
+      str = "selection:\t" + str;                          text(str, 10, 50);
+      str = (selected != null)?  selected.name  : "";
+      str = "selected:\t"  + str;                          text(str, 10, 60);
+      str = event;                                         text(str, 10, 70);
+      str = "dragging=" + dragging;                        text(str, 140, 70);
+      str = "dragging node=" + draggingnode;               text(str, 270, 70);
+      str = "func=" + func;                                text(str, 400, 70);
+      str = "touch count=" + touch_count;                  text(str, 10, 80);
+      //str = "start time(ms)=" + start;                      text(str, 140, 80);
+      //str = "elapsed time=" + elapsedTimeSec;              text(str, 270, 80);
+      //str = "holding=" + holding;                          text(str, 400, 80);
+      //str = "touched=" + touched;                          text(str, 530, 80);
+    }
+    else {
+      str = "menu active:" + menu_active;                  text(str,  10, 20);
+      str = "apmenu active:" + apmenu_active;              text(str, 150, 20);
+      str = "menu:" + toStr(menu);                         text(str,  10, 30);
+      str = "apmenu:" + toStr(apmenu);                     text(str, 150, 30);
+      str = "mouse x=" + mouseX + " y=" + mouseY;          text(str,  10, 40);
+      str = "point x=" + pointX + " y=" + pointY;          text(str, 150, 40);
+      str = "screen x=" + scrnX + " y=" + scrnY;           text(str, 290, 40);
+      str = "start x=" + pressStartX + " y=" + pressStartY;text(str, 430, 40);
+      str = (selection != null)? selection.name : "";      
+      str = "selection:\t" + str;                          text(str,  10, 50);
+      str = (from_node != null)? from_node.name : "";      
+      str = "from:\t" + str;                               text(str, 150, 50);
+      str = (selection != null)? selection.shape : "";      
+      str = "shape:\t" + str;                              text(str, 290, 50);
+      str = (selected != null)? selected.name : "";      
+      str = "selected:\t"  + str;                          text(str,  10, 60);
+      str = (to_node != null)? to_node.name : "";      
+      str = "to:\t"  + str;                          text(str, 150, 60);
+      str = event;                                         text(str,  10, 70);        
+      str = "mouse=" + mouseMode;                          text(str, 150, 70);    
+      str = "dragging=" + dragging;                        text(str, 290, 70);
+      str = "func=" + func;                                text(str, 430, 70);
+      str = "start=" + start;                              text(str, 570, 70);
+      str = (sel_edge != null)? sel_edge.from.name : "";      
+      str = "edge from:\t"  + str;                         text(str,  10, 80);
+      str = (sel_edge != null)? sel_edge.to.name : "";      
+      str = "to:\t"  + str;                                text(str, 150, 80);
+      str = (sel_edge != null)? sel_edge.asc_id : "";      
+      str = "asc_id:\t"  + str;                            text(str, 290, 80);
+      str = (sel_edge != null)? "" + sel_edge.dup : "";      
+      str = "dup:\t"  + str;                               text(str, 430, 80);
+      str = (previous_selection != null)? "" + previous_selection.name : "";            
+      str = "previous_selection=" + str;                   text(str,  10, 90);
+      //str = "theta1=" + degrees(theta1);                   text(str,  10, 90);
+      //str = "phai=" + degrees(phai);                       text(str, 150, 90);
+      //str = "theta2=" + degrees(theta2);                   text(str, 290, 90);
+  }
+}
+
+if (eventtrace) {  
+    str = "mouse :"  + toStr(mouse_event); text(str, 10, 110);
+    str = "touch :"  + toStr(touch_event); text(str, 10, 120);
+    str = "motion:" + toStr(mouse_motion); text(str, 10, 130);
+    str = "action:" + toStr(mouse_action); text(str, 10, 140);
+    str = "time:" + millis()/1000F;        text(str, 10, 150);
+    str = "Ct:" + touchedC;                text(str, 10, 160);
+    str = "startC:" + startC;              text(str, 10, 170);
+    str = "CTimer:" + elapsedTimeClick;    text(str, 10, 180);
+    str = "Dt:" + touchedD;                text(str,100, 160);
+    str = "startD:" + startD;              text(str,100, 170);
+    str = "DTimer:" + elapsedTimeDoubleClick;          text(str,100, 180);
+    str = "Ht:" + touchedH;                text(str,180, 160);
+    str = "startH:" + startH;              text(str,180, 170);
+    str = "HTimer:" + elapsedTimeHold;    text(str,180, 180);
+    str = "pressed:" + mousePressed;      text(str,120, 110);
+    str = "is down:" + is_down;            text(str,120, 120);
+    str = "start__:" + start_dist;        text(str, 10, 190);
+    str = "current:" + currt_dist;        text(str, 10, 200);
+    str = "scale__:" + scl/100;           text(str, 10, 210);
+  }
+}
+/*
+void setParams(float frame, float spring_length, float spring_strength, float spring_damping, float spacer_strength, float drag, int mass) {
+  FRAME          = frame;
+  SPRING_LENGTH  = spring_length;
+  SPRING_STRENGTH = spring_strength; // 0.2;
+  SPRING_DAMPING  = spring_damping; // 0.2;
+  SPACER_STRENGTH = spacer_strength;// 1000;
+  DRAGFACTOR      = drag; //0.2
+  //MASS            = mass;
+}
+*/
--- a/src/hp/templates/hp/partial/embed_player.html	Wed Nov 14 16:35:12 2012 +0100
+++ b/src/hp/templates/hp/partial/embed_player.html	Wed Nov 14 17:26:06 2012 +0100
@@ -2,15 +2,18 @@
 {% load i18n %}
 {% load thumbnail %}
 {% load staticfiles %}
-<div>
-<div id="{{ player_id }}_embed" class="iri_player_embed">
+
+<div id="LdtPlayer" class="iri_player_embed">
 </div>
 <script type="text/javascript" src="{{LDT_STATIC_URL}}ldt/metadataplayer/LdtPlayer-core.js"></script>
 <script type="text/javascript">
 
-	IriSP.libFiles.defaultDir = "libs/";
+	IriSP.libFiles.defaultDir = "{{LDT_STATIC_URL}}ldt/js";
+    IriSP.libFiles.locations.jwPlayerSWF = "{{LDT_STATIC_URL}}ldt/swf/player.swf";
+    IriSP.libFiles.locations.zeroClipboardSwf = "{{LDT_STATIC_URL}}ldt/swf/ZeroClipboard10.swf";
+    IriSP.libFiles.locations.cssjQueryUI = "{{LDT_STATIC_URL}}ldt/css/jq-css/themes/base/jquery-ui.css"
 	IriSP.language = '{{LANGUAGE_CODE}}';
-	IriSP.widgetsDir = "metadataplayer";
+	IriSP.widgetsDir = "{{LDT_STATIC_URL}}ldt/metadataplayer";
 	var _metadata = {
 	    url: '{{LDT_URL}}ldt/cljson/id/{{project_id}}?callback=?',
 	    format: 'ldt'
@@ -42,7 +45,6 @@
 	        { type: "Controller" },
 	        { type: "Polemic" },
 	        { type: "Segments" },
-	        { type: "Arrow" },
 	        {
 	            type: "Annotation",
 	            search_on_tag_click: false,
@@ -52,16 +54,18 @@
 	        {
 	            type: "KnowledgeConcierge",
 	            container: "KcContainer",
-	            proxy: "{% url kc_proxy '' %}",
+	            kc_api_root: "{% url kc_proxy '' %}",
 	            width: 480,
 	            height: 420,
+	            sketch_path: '{% static "hp/tmgraph" %}',
 	            project_id: "{{kc_id}}",
-	            topic_id: "{{topic_id}}"
+	            topic_id: "{{topic_id}}",
+	            related_api_endpoint: '{% url videos_recommended %}',
+	            video_url_base: '{% url hp.views.show_video_details "" %}'
 	        }
 	    ]
 	};
 	var _myPlayer = new IriSP.Metadataplayer(_config);
 </script>
-</div>
 {% endspaceless %}
 
--- a/src/hp/templates/hp/video_player.html	Wed Nov 14 16:35:12 2012 +0100
+++ b/src/hp/templates/hp/video_player.html	Wed Nov 14 17:26:06 2012 +0100
@@ -20,14 +20,11 @@
 <div class="main row">
     <div class="column left-column">
         <h2>{% trans 'Watch' %} "{{content.title}}"</h2>
-        <div class="ldt_player" id="wrapper_{{player_id}}">
         {% include "hp/partial/embed_player.html" %}
-        </div>
     </div>
     <div class="column right-column">
         <h2>{% trans 'Explore topics' %}</h2>
         <div id="KcContainer"></div>
-        <h2>{% trans 'Related videos' %}</h2>
     </div>
 </div>