Added span anchors to relation visualization

Change-Id: Id106c4659869a7162e1c0b3f1838e69fad688886
diff --git a/dev/js/src/match/relations.js b/dev/js/src/match/relations.js
index 040a674..b1809a0 100644
--- a/dev/js/src/match/relations.js
+++ b/dev/js/src/match/relations.js
@@ -6,16 +6,21 @@
   return {
     create : function (snippet) {
       var obj = Object.create(this)._init(snippet);
-      obj._tokens = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
+      obj._tokens = ["Der", "alte", "Mann", "ging", "über", "die", "breite", "nasse", "Straße"];
       obj._tokenElements = [];
       obj._arcs = [
+
+
+        /*
+         * Start and end may be spans, i.e. arrays
+         */
         { start: 0, end: 1, label: "a" },
         { start: 0, end: 1, label: "b" },
         { start: 1, end: 2, label: "c" },
         { start: 0, end: 2, label: "d" },
-        { start: 1, end: 5, label: "e" },
-        { start: 4, end: 8, label: "f" },
-        { start: 6, end: 7, label: "g" },
+        { start: [2,4], end: 5, label: "e" },
+        { start: 4, end: [6,8], label: "f" },
+        { start: [5,6], end: 7, label: "g" },
       ]
       obj.maxArc = 200; // maximum height of the bezier control point
       return obj;
@@ -40,14 +45,42 @@
       return box.x + (box.width / 2);
     },
 
+    _drawAnchor : function (anchor) {
+      var startPos = this._tokenElements[anchor.first].getBoundingClientRect().left;
+      var endPos = this._tokenElements[anchor.last].getBoundingClientRect().right;
+
+      var y = (anchor.overlaps * -5) - 10;
+      var l = this._c('path');
+      l.setAttribute("d", "M " + startPos + " " + y + " L " + endPos + " " + y);
+      l.setAttribute("class", "anchor");
+      anchor.element = l;
+      anchor.y = y;
+      return l;
+    },
+    
     // Create an arc with a label
     // Potentially needs a height parameter for stacks
     _drawArc : function (arc) {
 
-      var startPos = this._tokenPoint(this._tokenElements[arc.first]);
-      var endPos = this._tokenPoint(this._tokenElements[arc.last]);
+      var startPos, endPos;
+      var startY = 0, endY = 0;
 
-      var y = 0;
+      if (arc.startAnchor !== undefined) {
+        startPos = this._tokenPoint(arc.startAnchor.element)
+        startY = arc.startAnchor.y;
+      }
+      else {
+        startPos = this._tokenPoint(this._tokenElements[arc.first]);
+      };
+
+      if (arc.endAnchor !== undefined) {
+        endPos = this._tokenPoint(arc.endAnchor.element)
+        endY = arc.endAnchor.y;
+      }
+      else {
+        endPos = this._tokenPoint(this._tokenElements[arc.last]);
+      };
+
       var g = this._c("g");
       var p = g.appendChild(this._c("path"));
 
@@ -60,10 +93,11 @@
 
       var x = Math.min(startPos, endPos);
       
-      var arcE = "M "+ startPos + " " + y +
-          " C " + startPos + " " + (y-cHeight) +
-          " " + endPos + " " + (y-cHeight) +
-          " " + endPos + " " + y;
+      var arcE = "M "+ startPos + " " + startY +
+          " C " + startPos + " " + (startY + endY - cHeight) +
+          " " + endPos + " " + (startY + endY - cHeight) +
+          " " + endPos + " " + endY;
+
       p.setAttribute("d", arcE);
 
       if (arc.label !== undefined) {
@@ -100,6 +134,13 @@
      */
     _sortArcs : function () {
 
+
+      // TODO:
+      //   Keep in mind that the arcs may have long anchors!
+      //   1. Iterate over all arcs
+      //   2. Sort all multi
+      var anchors = [];
+      
       // 1. Sort by length
       // 2. Tag all spans with the number of overlaps before
       //    a) Iterate over all spans
@@ -111,6 +152,36 @@
 
       // Normalize start and end
       var sortedArcs = this._arcs.map(function (v) {
+
+        // Check for long anchors
+        if (v.start instanceof Array) {
+          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],
+            "length" : v.start[1] - v.start[0]
+          };
+
+          // Add to anchors list
+          anchors.push(v.startAnchor);
+          v.start = middle;
+        };
+
+        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],
+            "length" : v.end[1] - v.end[0]
+          };
+
+          // Add to anchors list
+          anchors.push(v.endAnchor);
+          v.end = middle;
+        };
+
+        // calculate the arch length
         if (v.start < v.end) {
           v.first = v.start;
           v.last = v.end;
@@ -132,49 +203,9 @@
           return 1;
       });
 
-      var arcStack = [];
+      this._sortedArcs = lengthSort(sortedArcs, false);
 
-      // Iterate over all arc definitions
-      for (var i = 0; i < sortedArcs.length; i++) {
-        var currentArc = sortedArcs[i];
-
-        // Check the stack order
-        var overlaps = 0;
-
-        for (var j = (arcStack.length - 1); j >= 0; j--) {
-          var checkArc = arcStack[j];
-
-          // (a..(b..b)..a)
-          if (currentArc.first <= checkArc.first && currentArc.last >= checkArc.last) {
-            overlaps = checkArc.overlaps + 1;
-            break;
-          }
-
-          // (a..(b..a)..b)
-          else if (currentArc.first < checkArc.first && currentArc.last > checkArc.first) {
-            overlaps = checkArc.overlaps + (currentArc.length == checkArc.length ? 0 : 1);
-          }
-
-          // (b..(a..b)..a)
-          else if (currentArc.first < checkArc.last && currentArc.last > checkArc.last) {
-            overlaps = checkArc.overlaps + (currentArc.length == checkArc.length ? 0 : 1);
-          };
-        };
-
-        // Set overlaps
-        currentArc.overlaps = overlaps;
-
-        arcStack.push(currentArc);
-
-        // Although it is already sorted,
-        // the new item has to be put at the correct place
-        // TODO: Use something like splice() instead
-        arcStack.sort(function (a,b) {
-          b.overlaps - a.overlaps
-        });
-      };
-
-      return arcStack;
+      this._sortedAnchors = lengthSort(anchors, true);
     },
     
     show : function () {
@@ -215,11 +246,78 @@
        * needs to be calculated to make it possible to "stack" arcs.
        * That means, the arcs need to be presorted, so massively
        * overlapping arcs are taken first.
+       * On the other hand, anchors need to be sorted as well
+       * in the same way.
        */
-      var sortedArcs = this._sortArcs();
-      for (var i in sortedArcs) {
-        this.arcs.appendChild(this._drawArc(sortedArcs[i]));
+      this._sortArcs();
+
+      var i;
+      for (i in this._sortedAnchors) {
+        this.arcs.appendChild(this._drawAnchor(this._sortedAnchors[i]));
+      };
+      
+      for (i in this._sortedArcs) {
+        this.arcs.appendChild(this._drawArc(this._sortedArcs[i]));
       };
     }
-  }
+  };
+
+  function lengthSort (list, inclusive) {
+
+    /*
+     * The "inclusive" flag allows to
+     * modify the behaviour for inclusivity check,
+     * e.g. if identical start or endpoints mean overlap or not.
+     */
+    
+    var stack = [];
+
+    // Iterate over all definitions
+    for (var i = 0; i < list.length; i++) {
+      var current = list[i];
+
+      // Check the stack order
+      var overlaps = 0;
+
+      for (var j = (stack.length - 1); j >= 0; j--) {
+        var check = stack[j];
+
+        // (a..(b..b)..a)
+        if (current.first <= check.first && current.last >= check.last) {
+          overlaps = check.overlaps + 1;
+          break;
+        }
+
+        // (a..(b..a)..b)
+        else if (current.first <= check.first && current.last >= check.first) {
+
+          if (inclusive || (current.first != check.first && current.last != check.first)) {
+            overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
+          };
+        }
+        
+        // (b..(a..b)..a)
+        else if (current.first <= check.last && current.last >= check.last) {
+
+          if (inclusive || (current.first != check.last && current.last != check.last)) {
+            overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
+          };
+        };
+      };
+
+      // Set overlaps
+      current.overlaps = overlaps;
+
+      stack.push(current);
+
+      // Although it is already sorted,
+      // the new item has to be put at the correct place
+      // TODO: Use something like splice() instead
+      stack.sort(function (a,b) {
+        b.overlaps - a.overlaps
+      });
+    };
+
+    return stack;
+  };
 });