Improved layout for relation tree

Change-Id: I6909cf3cf5a39a9153875e40a84cdef2aa87d28b
diff --git a/dev/demo/relations.html b/dev/demo/relations.html
index 6435fb9..d651988 100644
--- a/dev/demo/relations.html
+++ b/dev/demo/relations.html
@@ -5,7 +5,6 @@
     <script data-main="relationsdemo.js" src="../js/lib/require.js" async="async"></script>
     <link type="text/css" rel="stylesheet" href="../css/kalamar.css" />
     <style>
-
         tspan, text {  
           font-size: 11pt; 
           stroke-width: 0; 
@@ -13,17 +12,32 @@
           stroke-opacity:0; 
           padding-right: 3pt 
           fill: black; 
-        } 
+        }
+        /*
+        svg.relTree g > text > tspan {
+          text-anchor: middle;
+        }
+        */
         g.arcs text { 
           font-size: 9pt; 
           fill: blue;
-        } 
+        }
         path { 
           stroke-width: 2; 
           stroke: black;  
           fill: none;
         }
-
+        path.anchor {
+          stroke: green;
+          z-index: 20;
+        }
+        marker > path {
+          fill-opacity:1;
+          fill: black;
+        }
+        marker {
+          overflow:visible
+        }
     </style>
     
   </head>
diff --git a/dev/demo/relationsdemo.js b/dev/demo/relationsdemo.js
index 5c28f9a..17fcfb3 100644
--- a/dev/demo/relationsdemo.js
+++ b/dev/demo/relationsdemo.js
@@ -5,5 +5,32 @@
 require(['match/relations'], function (relClass) {
   var rel = relClass.create();
   document.getElementById("tree").appendChild(rel.element());
+
+  /*
+   * Start and end may be spans, i.e. arrays
+   */
+
+  rel
+    .addToken("Der")
+    .addToken("alte")
+    .addToken("Mann")
+    .addToken("ging")
+    .addToken("über")
+    .addToken("die")
+    .addToken("breite")
+    .addToken("nasse")
+    .addToken("Straße")
+  ;
+
+  rel
+    .addRel({ start: 0, end: 1, label: "a"})
+    .addRel({ start: 0, end: 1, label: "b" })
+    .addRel({ start: 1, end: 2, label: "c", direction: "bi"   })
+    .addRel({ start: 0, end: 2, label: "d" })
+    .addRel({ start: [2,4], end: 5, label: "e", direction: "uni"  })
+    .addRel({ start: [5,6], end: 7, label: "g" })
+    .addRel({ start: 4, end: [6,8], label: "f", direction: "bi" })
+  ;
+  
   rel.show();
 });
diff --git a/dev/js/src/match.js b/dev/js/src/match.js
index b5f0d5a..5943d88 100644
--- a/dev/js/src/match.js
+++ b/dev/js/src/match.js
@@ -110,7 +110,7 @@
           continue;
         };
       };
-
+      
       return this;
     },
 
diff --git a/dev/js/src/match/relations.js b/dev/js/src/match/relations.js
index 28a279b..bf80fb3 100644
--- a/dev/js/src/match/relations.js
+++ b/dev/js/src/match/relations.js
@@ -7,15 +7,16 @@
     create : function (snippet) {
       var obj = Object.create(this)._init(snippet);
       obj._tokens = [];
-      obj._tokenElements = [];
       obj._arcs = []
+      obj._tokenElements = [];
+      obj._y = 0;
 
       // Some configurations
       obj.maxArc = 200; // maximum height of the bezier control point
       obj.overlapDiff = 20;
       obj.arcDiff = 15;
       obj.anchorDiff = 6;
-      obj.anchorStart = 10;
+      obj.anchorStart = 15;
       obj.tokenSep = 30;
       return obj;
     },
@@ -40,12 +41,16 @@
     },
 
     _drawAnchor : function (anchor) {
-      var startPos = this._tokenElements[anchor.first].getBoundingClientRect().left;
-      var endPos = this._tokenElements[anchor.last].getBoundingClientRect().right;
+      var firstBox = this._tokenElements[anchor.first].getBoundingClientRect();
+      var lastBox = this._tokenElements[anchor.last].getBoundingClientRect();
 
-      var y = (anchor.overlaps * -1 * this.anchorDiff) - this.anchorStart; // - this.arcDiff;
+      var startPos = firstBox.left;
+      var endPos = lastBox.right;
+
+      var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
+
       var l = this._c('path');
-      l.setAttribute("d", "M " + startPos + " " + y + " L " + endPos + " " + y);
+      l.setAttribute("d", "M " + startPos + "," + y + " L " + endPos + "," + y);
       l.setAttribute("class", "anchor");
       anchor.element = l;
       anchor.y = y;
@@ -57,10 +62,10 @@
     _drawArc : function (arc) {
 
       var startPos, endPos;
-      var startY = 0, endY = 0;
+      var startY = this._y, endY = this._y;
 
       if (arc.startAnchor !== undefined) {
-        startPos = this._tokenPoint(arc.startAnchor.element)
+        startPos = this._tokenPoint(arc.startAnchor.element);
         startY = arc.startAnchor.y;
       }
       else {
@@ -89,15 +94,23 @@
 
       var x = Math.min(startPos, endPos);
 
-      var controlY = (startY + endY - cHeight);
+      //var controlY = (startY + endY - cHeight);
+      var controlY = (endY - cHeight);
       
-      var arcE = "M "+ startPos + " " + startY +
-          " C " + startPos + " " + controlY +
-          " " + endPos + " " + controlY +
-          " " + endPos + " " + endY;
+      var arcE = "M "+ startPos + "," + startY +
+          " C " + startPos + "," + controlY +
+          " " + endPos + "," + controlY +
+          " " + endPos + "," + endY;
 
       p.setAttribute("d", arcE);
 
+      if (arc.direction !== undefined) {
+        p.setAttribute("marker-end", "url(#arr)");
+        if (arc.direction === 'bi') {
+          p.setAttribute("marker-start", "url(#arr)");
+        };
+      };
+
       /*
        * Calculate the top point of the arc for labeling using
        * de Casteljau's algorithm, see e.g.
@@ -119,15 +132,6 @@
         labelE.setAttribute("text-anchor", "middle");
         labelE.appendChild(document.createTextNode(arc.label));
       };
-
-      /*
-      var circle = this._c("circle");
-      circle.setAttribute("cx", x + middle);
-      circle.setAttribute("cy", middleY);
-      circle.setAttribute("r", 4);
-      circle.setAttribute("fill", "red");
-      g.appendChild(circle);
-      */      
       return g;
     },
 
@@ -137,8 +141,24 @@
 
       // Create svg
       var svg = this._c("svg");
-      svg.setAttribute("width", 700);
-      svg.setAttribute("height", 300);
+
+      window.addEventListener("resize", function () {
+        // TODO: Only if text-size changed!
+        this.show();
+      }.bind(this));
+      
+      var defs = svg.appendChild(this._c("defs"));
+      var marker = defs.appendChild(this._c("marker"));
+      marker.setAttribute("refX", 9);
+      marker.setAttribute("id", "arr");
+      marker.setAttribute("orient", "auto-start-reverse");
+      marker.setAttribute("markerUnits","userSpaceOnUse");
+
+      var arrow = this._c("path");
+      arrow.setAttribute("transform", "scale(0.8)");
+      arrow.setAttribute("d", "M 0,-5 0,5 10,0 Z");
+      marker.appendChild(arrow);
+
       this._element = svg;
       return this._element;
     },
@@ -186,8 +206,8 @@
           var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
 
           v.startAnchor = {
-            "first": v.start[0],
-            "last" : v.start[1],
+            "first":   v.start[0],
+            "last" :   v.start[1],
             "length" : v.start[1] - v.start[0]
           };
 
@@ -199,8 +219,8 @@
         if (v.end instanceof Array) {
           var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
           v.endAnchor = {
-            "first": v.end[0],
-            "last" : v.end[1],
+            "first":   v.end[0],
+            "last" :   v.end[1],
             "length" : v.end[1] - v.end[0]
           };
 
@@ -231,46 +251,68 @@
           return 1;
       });
 
-      this._sortedArcs = lengthSort(sortedArcs, false);
-
+      this._sortedArcs    = lengthSort(sortedArcs, false);
       this._sortedAnchors = lengthSort(anchors, true);
     },
     
     show : function () {
       var svg = this._element;
+      var height = this.maxArc;
+
+      /*
+      svg.setAttribute("width", 700);
+      svg.setAttribute("height", 300);
+      */
+
+      // Delete old group
+      if (svg.getElementsByTagName("g")[0] !== undefined) {
+        var group = svg.getElementsByTagName("g")[0];
+        svg.removeChild(group);
+        this._tokenElements = [];
+      };
+
+      var g = svg.appendChild(this._c("g"));
 
       /*
        * Generate token list
        */
-      var text = svg.appendChild(this._c("text"));
-      text.setAttribute("y", 135);
-      text.setAttribute("x", 160);
+      var text = g.appendChild(this._c("text"));
+      text.setAttribute("text-anchor", "start");
+      text.setAttribute("y", height);
 
+      this._y = height - (this.anchorStart);
+
+      var ws = text.appendChild(this._c("tspan"));
+      ws.appendChild(document.createTextNode('\u00A0'));
+      ws.style.textAnchor = "start";
+      
       var lastRight = 0;
       for (var node_i in this._tokens) {
         // Append svg
         var tspan = text.appendChild(this._c("tspan"));
         tspan.appendChild(document.createTextNode(this._tokens[node_i]));
-
+        tspan.setAttribute("text-anchor", "middle");
+        
         this._tokenElements.push(tspan);
 
         // Add whitespace!
-        // var whitespace = text.appendChild(document.createTextNode(" "));
-        var ws = text.appendChild(this._c("tspan"));
-        ws.appendChild(document.createTextNode(" "));
-        ws.setAttribute("class", "rel-ws");
-        ws.setAttribute("dx", this.tokenSep);
+        //var ws = text.appendChild(this._c("tspan"));
+        //ws.appendChild(document.createTextNode(" "));
+        // ws.setAttribute("class", "rel-ws");
+        tspan.setAttribute("dx", this.tokenSep);
       };
 
-      this.arcs = svg.appendChild(this._c("g"));
-      this.arcs.classList.add("arcs");
+      var arcs = g.appendChild(this._c("g"));
+      arcs.classList.add("arcs");
 
+      /*
       var textBox = text.getBoundingClientRect();
 
-      this.arcs.setAttribute(
+      arcs.setAttribute(
         "transform",
         "translate(0," + textBox.y +")"
       );
+      */
       
       /*
        * TODO:
@@ -281,16 +323,29 @@
        * On the other hand, anchors need to be sorted as well
        * in the same way.
        */
-      this._sortArcs();
+      if (this._sortedArcs === undefined) {
+        this._sortArcs();
+      };
 
       var i;
       for (i in this._sortedAnchors) {
-        this.arcs.appendChild(this._drawAnchor(this._sortedAnchors[i]));
+        arcs.appendChild(this._drawAnchor(this._sortedAnchors[i]));
       };
       
       for (i in this._sortedArcs) {
-        this.arcs.appendChild(this._drawArc(this._sortedArcs[i]));
+        arcs.appendChild(this._drawArc(this._sortedArcs[i]));
       };
+
+      var width = text.getBoundingClientRect().width;
+      svg.setAttribute("width", width);
+      svg.setAttribute("height", height);
+      svg.setAttribute("class", "relTree");
+
+      // svg.setAttribute("viewbox", "0 0 500 200");
+      /*
+        console.log(size.width);
+        console.log(size.height);
+      */
     }
   };