Improved MS Edge support for relation visualizations slightly
Change-Id: I9ac2a3b364fac782cdb0e2306ab6f92ec42599a2
diff --git a/dev/js/src/match/relations.js b/dev/js/src/match/relations.js
index 6f69cbb..92be073 100644
--- a/dev/js/src/match/relations.js
+++ b/dev/js/src/match/relations.js
@@ -1,699 +1,701 @@
-/**
- * Parse a relational tree and visualize using arcs.
- *
- * @author Nils Diewald
- */
-
-define([], function () {
-  "use strict";
-
-  var svgNS = "http://www.w3.org/2000/svg";
-  var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
-
-  return {
-    create : function (snippet) {
-      return Object.create(this)._init(snippet);
-    },
-
-    // Initialize the state of the object
-    _init : function (snippet) {
-
-      // Predefine some values
-      this._tokens  = [];
-      this._arcs    = [];
-      this._tokenElements = [];
-      this._y = 0;
-
-      // Some configurations
-      this.maxArc      = 200; // maximum height of the bezier control point
-      this.overlapDiff = 40;  // Difference on overlaps and minimum height for self-refernces
-      this.arcDiff     = 15;
-      this.anchorDiff  = 8;
-      this.anchorStart = 15;
-      this.tokenSep    = 30;
-      this.xPadding    = 10;
-      this.yPadding    = 5;
-
-      // No snippet to process
-      if (snippet == undefined || snippet == null)
-        return this;
-
-      // Parse the snippet
-      var html = document.createElement("div");
-      html.innerHTML = snippet;
-
-      // Establish temporary parsing memory
-      this.temp = {
-        target : {}, // Remember the map id => pos        
-        edges  : [], // Remember edge definitions
-        pos    : 0   // Keep track of the current token position
-      };
-
-      // Start parsing from root
-      this._parse(0, html.childNodes, undefined);
-
-      // Establish edge list
-      var targetMap = this.temp['target'];
-      var edges = this.temp['edges'];
-
-      // Iterate over edge lists
-      // TODO:
-      //   Support spans for anchors!
-      for (var i in edges) {
-        var edge = edges[i];
-
-        // Check the target identifier
-        var targetID = edge.targetID;
-        var target = targetMap[targetID];
-
-        if (target != undefined) {
-
-          // Check if the source is a span anchor
-          /*
-          var start = edge.srcStart;
-          if (start !== edge.srcEnd) {
-            start = [start, edge.srcEnd];
-          };
-          */
-          
-          // Add relation
-          var relation = {
-            start : [edge.srcStart, edge.srcEnd],
-            end : target,
-            direction : 'uni',
-            label : edge.label
-          };
-          // console.log(relation);
-          this.addRel(relation);
-        };
-      };
-
-      // Reset parsing memory
-      this.temp = {};
-
-      return this;
-    },
-
-    // Parse a node of the tree snippet
-    _parse : function (parent, children, mark) {
-
-      // Iterate over all child nodes
-      for (var i in children) {
-        var c = children[i];
-
-        // Element node
-        if (c.nodeType == 1) {
-
-          var xmlid, target;
-
-          // Node is an identifier
-          if (c.hasAttribute('xml:id')) {
-
-            // Remember that pos has this identifier
-            xmlid = c.getAttribute('xml:id');
-            this.temp['target'][xmlid] = [this.temp['pos'], this.temp['pos']];
-          }
-
-          // Node is a relation
-          else if (c.hasAttribute('xlink:href')) {
-            var label;
-
-            // Get target id
-            target = c.getAttribute('xlink:href').replace(/^#/, "");
-
-            if (c.hasAttribute('xlink:title')) {
-              label = this._clean(c.getAttribute('xlink:title'));
-            };
-
-            // Remember the defined edge
-            var edge = {
-              label    : label,
-              srcStart : this.temp['pos'],
-              targetID : target
-            };
-            this.temp['edges'].push(edge);
-          };
-
-          // Go on with child nodes
-          if (c.hasChildNodes()) {
-            this._parse(0, c.childNodes, mark);
-          };
-
-          if (xmlid !== undefined) {
-            this.temp['target'][xmlid][1] = this.temp['pos'] -1;
-
-            /*
-            console.log('Target ' + xmlid + ' spans from ' +
-                        this.temp['target'][xmlid][0] +
-                        ' to ' +
-                        this.temp['target'][xmlid][1]
-                       );
-            */
-            xmlid = undefined;
-          }
-          else if (target !== undefined) {
-            edge["srcEnd"] = this.temp['pos'] -1;
-
-            /*
-            console.log('Source spans from ' +
-                        edge["srcStart"] +
-                        ' to ' +
-                        edge["srcEnd"]
-                       );
-            */
-            target = undefined;
-          };
-        }
-
-        // Text node
-        else if (c.nodeType == 3) {
-
-          // Check, if there is a non-whitespace token
-          if (c.nodeValue !== undefined) {
-            var str = c.nodeValue.trim();
-            if (str !== undefined && str.length > 0) {
-
-              // Add token to token list
-              this.addToken(str);
-
-              // Move token position
-              this.temp['pos']++;
-            };
-          };
-        }
-      };
-    },
-
-    
-    // Remove foundry and layer for labels
-    _clean : function (title) {
-      return title.replace(_TermRE, "$3");
-    },
-
-    
-    // Return the number of leaf nodes
-    // (not necessarily part of a relation).
-    // Consecutive nodes that are not part of any
-    // relation are summarized in one node.
-    size : function () {
-      return this._tokens.length;
-    },
-
-    
-    // This is a shorthand for SVG element creation
-    _c : function (tag) {
-      return document.createElementNS(svgNS, tag);
-    },
-
-    // Get bounding box - with workaround for text nodes
-    _rect : function (node) {
-      if (node.tagName == "tspan") {
-        var range = document.createRange();
-        range.selectNode(node);
-        var rect = range.getBoundingClientRect();
-        range.detach();
-        return rect;
-      };
-      return node.getBoundingClientRect();
-    },
-
-    // Returns the center point of the requesting token
-    _tokenPoint : function (node) {
-      var box = this._rect(node);
-      return box.x + (box.width / 2);
-    },
-
-
-    // Draws an anchor
-    _drawAnchor : function (anchor) {
-
-      // Calculate the span of the first and last token, the anchor spans
-      var firstBox = this._rect(this._tokenElements[anchor.first]);
-      var lastBox  = this._rect(this._tokenElements[anchor.last]);
-
-      var startPos = firstBox.left - this.offsetLeft;
-      var endPos   = lastBox.right - this.offsetLeft;
-      
-      var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
-
-      var l = this._c('path');
-      this._arcsElement.appendChild(l);
-      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 an optional label
-    // Potentially needs a height parameter for stacks
-    _drawArc : function (arc) {
-
-      var startPos, endPos;
-      var startY = this._y;
-      var endY = this._y;
-
-      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]);
-      };
-
-      startPos -= this.offsetLeft;
-      endPos -= this.offsetLeft;
-
-      // Special treatment for self-references
-      var overlaps = arc.overlaps;
-      if (startPos == endPos) {
-        startPos -= this.overlapDiff / 3;
-        endPos   += this.overlapDiff / 3;
-        overlaps += .5;
-      };
-
-      var g = this._c("g");
-      g.setAttribute("class", "arc");
-      var p = g.appendChild(this._c("path"));
-      p.setAttribute('class', 'edge');
-      
-      // Attach the new arc before drawing, so computed values are available
-      this._arcsElement.appendChild(g);
-
-      // Create arc
-      var middle = Math.abs(endPos - startPos) / 2;
-
-      // TODO:
-      //   take the number of tokens into account!
-      var cHeight = this.arcDiff + (overlaps * this.overlapDiff) + (middle / 2);
-
-      // Respect the maximum height
-      cHeight = cHeight < this.maxArc ? cHeight : this.maxArc;
-
-      var x = Math.min(startPos, endPos);
-
-      //var controlY = (startY + endY - cHeight);
-      var controlY = (endY - cHeight);
-      
-      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)");
-        };
-      };
-
-      if (arc.label === undefined)
-        return g;
-      
-      /*
-       * Calculate the top point of the arc for labeling using
-       * de Casteljau's algorithm, see e.g.
-       * http://blog.sklambert.com/finding-the-control-points-of-a-bezier-curve/
-       * of course simplified to symmetric arcs ...
-       */
-      // Interpolate one side of the control polygon
-      var middleY = (((startY + controlY) / 2) + controlY) / 2;
-
-      // Create a boxed label
-      g = this._c("g");
-      g.setAttribute("class", "label");
-      this._labelsElement.appendChild(g);
-
-      var that = this;
-      g.addEventListener('mouseenter', function () {
-        that._labelsElement.appendChild(this);
-      });
-
-      var labelE = g.appendChild(this._c("text"));
-      labelE.setAttribute("x", x + middle);
-      labelE.setAttribute("y", middleY + 3);
-      labelE.setAttribute("text-anchor", "middle");
-      var textNode = document.createTextNode(arc.label);
-      labelE.appendChild(textNode);
-
-      var labelBox   = labelE.getBBox();
-      var textWidth  = labelBox.width; // labelE.getComputedTextLength();
-      var textHeight = labelBox.height; // labelE.getComputedTextLength();
-
-      // Add box with padding to left and right
-      var labelR = g.insertBefore(this._c("rect"), labelE);
-      var boxWidth = textWidth + 2 * this.xPadding;
-      labelR.setAttribute("x", x + middle - (boxWidth / 2));
-      labelR.setAttribute("ry", 5);
-      labelR.setAttribute("y", labelBox.y - this.yPadding);
-      labelR.setAttribute("width", boxWidth);
-      labelR.setAttribute("height", textHeight + 2 * this.yPadding);
-    },
-
-    // Get the svg element
-    element : function () {
-      if (this._element !== undefined)
-        return this._element;
-
-      // Create svg
-      var svg = this._c("svg");
-
-      window.addEventListener("resize", function () {
-        // TODO:
-        //   Only if text-size changed!
-        // TODO:
-        //   This is currently untested
-        this.show();
-      }.bind(this));
-
-      // Define marker arrows
-      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;
-    },
-
-    // Add a relation with a start, an end,
-    // a direction value and an optional label text
-    addRel : function (rel) {
-      this._arcs.push(rel);
-      return this;
-    },
-
-
-    // Add a token to the list (this will mostly be a word)
-    addToken : function(token) {
-      this._tokens.push(token);
-      return this;
-    },
-    
-    /*
-     * All arcs need to be sorted before shown,
-     * to avoid nesting.
-     */
-    _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
-      //    b) check the latest preceeding overlapping span (lpos)
-      //       -> not found: tag with 0
-      //       -> found: Add +1 to the level of the (lpos)
-      //    c) If the new tag is smaller than the previous element,
-      //       reorder
-
-      // Normalize start and end
-      var sortedArcs = this._arcs.map(function (v) {
-
-        // Check for long anchors
-        if (v.start instanceof Array) {
-
-          if (v.start[0] == v.start[1]) {
-            v.start = v.start[0];
-          }
-
-          else {
-          
-            var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
-
-            // Calculate signature to avoid multiple anchors
-            var anchorSig = "#" + v.start[0] + "_" + v.start[1];
-            if (v.start[0] > v.start[1]) {
-              anchorSig = "#" + v.start[1] + "_" + v.start[0];
-            };
-            
-            // Check if the anchor already exist
-            var anchor = anchors[anchorSig];
-            if (anchor === undefined) {
-              anchor = {
-                "first":   v.start[0],
-                "last" :   v.start[1],
-                "length" : v.start[1] - v.start[0]
-              };
-              anchors[anchorSig] = anchor;
-              // anchors.push(v.startAnchor);
-            };
-
-            v.startAnchor = anchor;
-
-            // Add to anchors list
-            v.start = middle;
-          };
-        };
-
-        if (v.end instanceof Array) {
-
-          if (v.end[0] == v.end[1]) {
-            v.end = v.end[0];
-          }
-
-          else {
-
-            var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
-
-            // Calculate signature to avoid multiple anchors
-            var anchorSig = "#" + v.end[0] + "_" + v.end[1];
-            if (v.end[0] > v.end[1]) {
-              anchorSig = "#" + v.end[1] + "_" + v.end[0];
-            };
-
-            // Check if the anchor already exist
-            var anchor = anchors[anchorSig];
-            if (anchor === undefined) {
-              anchor = {
-                "first":   v.end[0],
-                "last" :   v.end[1],
-                "length" : v.end[1] - v.end[0]
-              };
-              anchors[anchorSig] = anchor;
-              // anchors.push(v.startAnchor);
-            };
-            
-            v.endAnchor = anchor;
-
-            // Add to anchors list
-            // anchors.push(v.endAnchor);
-            v.end = middle;
-          };
-        };
-
-        v.first = v.start;
-        v.last = v.end;
-
-        // calculate the arch length
-        if (v.start < v.end) {
-          v.length = v.end - v.start;
-        }
-        else {
-          // v.first = v.end;
-          // v.last = v.start;
-          v.length = v.start - v.end;
-        };
-
-        return v;
-      });
-
-      // Sort based on length
-      sortedArcs.sort(function (a, b) {
-        if (a.length < b.length)
-          return -1;
-        else
-          return 1;
-      });
-
-      // Add sorted arcs and anchors
-      this._sortedArcs    = lengthSort(sortedArcs, false);
-
-      // Translate map to array (there is probably a better JS method)
-      var sortedAnchors = [];
-      for (var i in anchors) {
-        sortedAnchors.push(anchors[i]);
-      };
-      this._sortedAnchors = lengthSort(sortedAnchors, true);
-    },
-
-    /**
-     * Center the viewport of the canvas
-     * TODO:
-     *   This is identical to tree
-     */
-    center : function () {
-      if (this._element === undefined)
-       return;
-
-      var treeDiv = this._element.parentNode;
-
-      var cWidth = parseFloat(window.getComputedStyle(this._element).width);
-      var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
-      // Reposition:
-      if (cWidth > treeWidth) {
-       var scrollValue = (cWidth - treeWidth) / 2;
-       treeDiv.scrollLeft = scrollValue;
-      };
-    },
-
-
-    // Show the element
-    show : function () {
-      var svg = this._element;
-      var height = this.maxArc;
-
-      // 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"));
-
-      // Draw token list
-      var text = g.appendChild(this._c("text"));
-      text.setAttribute('class', 'leaf');
-      text.setAttribute("text-anchor", "start");
-      text.setAttribute("y", height);
-
-      // Calculate the start position
-      this._y = height - (this.anchorStart);
-
-      // Introduce some prepending whitespace (yeah - I know ...)
-      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 x = text.appendChild(this._c("text"));
-        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!
-        tspan.setAttribute("dx", this.tokenSep);
-      };
-
-      // Get some global position data that may change on resize
-      var globalBoundingBox = this._rect(g);
-      this.offsetLeft = globalBoundingBox.left;
-
-      // The group of arcs
-      var arcs = g.appendChild(this._c("g"));
-      this._arcsElement = arcs;
-      arcs.classList.add("arcs");
-
-      var labels = g.appendChild(this._c("g"));
-      this._labelsElement = labels;
-      labels.classList.add("labels");
-
-      // Sort arcs if not sorted yet
-      if (this._sortedArcs === undefined)
-        this._sortArcs();
-
-      // 1. Draw all anchors
-      var i;
-      for (i in this._sortedAnchors) {
-        this._drawAnchor(this._sortedAnchors[i]);
-      };
-
-      // 2. Draw all arcs
-      for (i in this._sortedArcs) {
-        this._drawArc(this._sortedArcs[i]);
-      };
-
-      // Resize the svg with some reasonable margins
-      var width = this._rect(text).width;
-      svg.setAttribute("width", width + 20);
-      svg.setAttribute("height", height + 20);
-      svg.setAttribute("class", "relTree");
-    }
-  };
-
-  // Sort relations regarding their span
-  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;
-  };
-});
+/**
+ * Parse a relational tree and visualize using arcs.
+ *
+ * @author Nils Diewald
+ */
+
+define([], function () {
+  "use strict";
+
+  var svgNS = "http://www.w3.org/2000/svg";
+  var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+
+  return {
+    create : function (snippet) {
+      return Object.create(this)._init(snippet);
+    },
+
+    // Initialize the state of the object
+    _init : function (snippet) {
+
+      // Predefine some values
+      this._tokens  = [];
+      this._arcs    = [];
+      this._tokenElements = [];
+      this._y = 0;
+
+      // Some configurations
+      this.maxArc      = 200; // maximum height of the bezier control point
+      this.overlapDiff = 40;  // Difference on overlaps and minimum height for self-refernces
+      this.arcDiff     = 15;
+      this.anchorDiff  = 8;
+      this.anchorStart = 15;
+      this.tokenSep    = 30;
+      this.xPadding    = 10;
+      this.yPadding    = 5;
+
+      // No snippet to process
+      if (snippet == undefined || snippet == null)
+        return this;
+
+      // Parse the snippet
+      var html = document.createElement("div");
+      html.innerHTML = snippet;
+
+      // Establish temporary parsing memory
+      this.temp = {
+        target : {}, // Remember the map id => pos        
+        edges  : [], // Remember edge definitions
+        pos    : 0   // Keep track of the current token position
+      };
+
+      // Start parsing from root
+      this._parse(0, html.childNodes, undefined);
+
+      // Establish edge list
+      var targetMap = this.temp['target'];
+      var edges = this.temp['edges'];
+
+      // Iterate over edge lists
+      // TODO:
+      //   Support spans for anchors!
+      for (var i in edges) {
+        var edge = edges[i];
+
+        // Check the target identifier
+        var targetID = edge.targetID;
+        var target = targetMap[targetID];
+
+        if (target != undefined) {
+
+          // Check if the source is a span anchor
+          /*
+          var start = edge.srcStart;
+          if (start !== edge.srcEnd) {
+            start = [start, edge.srcEnd];
+          };
+          */
+          
+          // Add relation
+          var relation = {
+            start : [edge.srcStart, edge.srcEnd],
+            end : target,
+            direction : 'uni',
+            label : edge.label
+          };
+          // console.log(relation);
+          this.addRel(relation);
+        };
+      };
+
+      // Reset parsing memory
+      this.temp = {};
+
+      return this;
+    },
+
+    // Parse a node of the tree snippet
+    _parse : function (parent, children, mark) {
+
+      // Iterate over all child nodes
+      for (var i in children) {
+        var c = children[i];
+
+        // Element node
+        if (c.nodeType == 1) {
+
+          var xmlid, target;
+
+          // Node is an identifier
+          if (c.hasAttribute('xml:id')) {
+
+            // Remember that pos has this identifier
+            xmlid = c.getAttribute('xml:id');
+            this.temp['target'][xmlid] = [this.temp['pos'], this.temp['pos']];
+          }
+
+          // Node is a relation
+          else if (c.hasAttribute('xlink:href')) {
+            var label;
+
+            // Get target id
+            target = c.getAttribute('xlink:href').replace(/^#/, "");
+
+            if (c.hasAttribute('xlink:title')) {
+              label = this._clean(c.getAttribute('xlink:title'));
+            };
+
+            // Remember the defined edge
+            var edge = {
+              label    : label,
+              srcStart : this.temp['pos'],
+              targetID : target
+            };
+            this.temp['edges'].push(edge);
+          };
+
+          // Go on with child nodes
+          if (c.hasChildNodes()) {
+            this._parse(0, c.childNodes, mark);
+          };
+
+          if (xmlid !== undefined) {
+            this.temp['target'][xmlid][1] = this.temp['pos'] -1;
+
+            /*
+            console.log('Target ' + xmlid + ' spans from ' +
+                        this.temp['target'][xmlid][0] +
+                        ' to ' +
+                        this.temp['target'][xmlid][1]
+                       );
+            */
+            xmlid = undefined;
+          }
+          else if (target !== undefined) {
+            edge["srcEnd"] = this.temp['pos'] -1;
+
+            /*
+            console.log('Source spans from ' +
+                        edge["srcStart"] +
+                        ' to ' +
+                        edge["srcEnd"]
+                       );
+            */
+            target = undefined;
+          };
+        }
+
+        // Text node
+        else if (c.nodeType == 3) {
+
+          // Check, if there is a non-whitespace token
+          if (c.nodeValue !== undefined) {
+            var str = c.nodeValue.trim();
+            if (str !== undefined && str.length > 0) {
+
+              // Add token to token list
+              this.addToken(str);
+
+              // Move token position
+              this.temp['pos']++;
+            };
+          };
+        }
+      };
+    },
+
+    
+    // Remove foundry and layer for labels
+    _clean : function (title) {
+      return title.replace(_TermRE, "$3");
+    },
+
+    
+    // Return the number of leaf nodes
+    // (not necessarily part of a relation).
+    // Consecutive nodes that are not part of any
+    // relation are summarized in one node.
+    size : function () {
+      return this._tokens.length;
+    },
+
+    
+    // This is a shorthand for SVG element creation
+    _c : function (tag) {
+      return document.createElementNS(svgNS, tag);
+    },
+
+    // Get bounding box - with workaround for text nodes
+    _rect : function (node) {
+      if (node.tagName == "tspan" && !navigator.userAgent.match(/Edge/)) {
+        var range = document.createRange();
+        range.selectNode(node);
+        var rect = range.getBoundingClientRect();
+        range.detach();
+        return rect;
+      };
+      return node.getBoundingClientRect();
+    },
+
+    // Returns the center point of the requesting token
+    _tokenPoint : function (node) {
+	    var box = this._rect(node);
+	    return box.left + (box.width / 2);
+    },
+
+
+    // Draws an anchor
+    _drawAnchor : function (anchor) {
+
+      // Calculate the span of the first and last token, the anchor spans
+      var firstBox = this._rect(this._tokenElements[anchor.first]);
+      var lastBox  = this._rect(this._tokenElements[anchor.last]);
+	
+      var startPos = firstBox.left - this.offsetLeft;
+      var endPos   = lastBox.right - this.offsetLeft;
+      
+      var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
+
+      var l = this._c('path');
+	    this._arcsElement.appendChild(l);
+	    var pathStr = "M " + startPos + "," + y + " L " + endPos + "," + y;
+      l.setAttribute("d", pathStr);
+      l.setAttribute("class", "anchor");
+      anchor.element = l;
+      anchor.y = y;
+      return l;
+    },
+    
+
+    // Create an arc with an optional label
+    // Potentially needs a height parameter for stacks
+    _drawArc : function (arc) {
+
+      var startPos, endPos;
+      var startY = this._y;
+      var endY = this._y;
+	
+      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]);
+      };
+
+
+      startPos -= this.offsetLeft;
+      endPos -= this.offsetLeft;
+
+	    // Special treatment for self-references
+      var overlaps = arc.overlaps;
+      if (startPos == endPos) {
+        startPos -= this.overlapDiff / 3;
+        endPos   += this.overlapDiff / 3;
+        overlaps += .5;
+      };
+
+      var g = this._c("g");
+      g.setAttribute("class", "arc");
+      var p = g.appendChild(this._c("path"));
+      p.setAttribute('class', 'edge');
+      
+      // Attach the new arc before drawing, so computed values are available
+      this._arcsElement.appendChild(g);
+
+      // Create arc
+      var middle = Math.abs(endPos - startPos) / 2;
+
+      // TODO:
+      //   take the number of tokens into account!
+      var cHeight = this.arcDiff + (overlaps * this.overlapDiff) + (middle / 2);
+
+      // Respect the maximum height
+      cHeight = cHeight < this.maxArc ? cHeight : this.maxArc;
+
+      var x = Math.min(startPos, endPos);
+
+      //var controlY = (startY + endY - cHeight);
+      var controlY = (endY - cHeight);
+      
+      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)");
+        };
+      };
+
+      if (arc.label === undefined)
+        return g;
+      
+      /*
+       * Calculate the top point of the arc for labeling using
+       * de Casteljau's algorithm, see e.g.
+       * http://blog.sklambert.com/finding-the-control-points-of-a-bezier-curve/
+       * of course simplified to symmetric arcs ...
+       */
+      // Interpolate one side of the control polygon
+      var middleY = (((startY + controlY) / 2) + controlY) / 2;
+
+      // Create a boxed label
+      g = this._c("g");
+      g.setAttribute("class", "label");
+      this._labelsElement.appendChild(g);
+
+      var that = this;
+      g.addEventListener('mouseenter', function () {
+        that._labelsElement.appendChild(this);
+      });
+
+      var labelE = g.appendChild(this._c("text"));
+      labelE.setAttribute("x", x + middle);
+      labelE.setAttribute("y", middleY + 3);
+      labelE.setAttribute("text-anchor", "middle");
+      var textNode = document.createTextNode(arc.label);
+      labelE.appendChild(textNode);
+
+      var labelBox   = labelE.getBBox();
+      var textWidth  = labelBox.width; // labelE.getComputedTextLength();
+      var textHeight = labelBox.height; // labelE.getComputedTextLength();
+
+      // Add box with padding to left and right
+      var labelR = g.insertBefore(this._c("rect"), labelE);
+      var boxWidth = textWidth + 2 * this.xPadding;
+      labelR.setAttribute("x", x + middle - (boxWidth / 2));
+      labelR.setAttribute("ry", 5);
+      labelR.setAttribute("y", labelBox.y - this.yPadding);
+      labelR.setAttribute("width", boxWidth);
+      labelR.setAttribute("height", textHeight + 2 * this.yPadding);
+    },
+
+    // Get the svg element
+    element : function () {
+      if (this._element !== undefined)
+        return this._element;
+
+      // Create svg
+      var svg = this._c("svg");
+
+      window.addEventListener("resize", function () {
+        // TODO:
+        //   Only if text-size changed!
+        // TODO:
+        //   This is currently untested
+        this.show();
+      }.bind(this));
+
+      // Define marker arrows
+      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;
+    },
+
+    // Add a relation with a start, an end,
+    // a direction value and an optional label text
+    addRel : function (rel) {
+      this._arcs.push(rel);
+      return this;
+    },
+
+
+    // Add a token to the list (this will mostly be a word)
+    addToken : function(token) {
+      this._tokens.push(token);
+      return this;
+    },
+    
+    /*
+     * All arcs need to be sorted before shown,
+     * to avoid nesting.
+     */
+    _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
+      //    b) check the latest preceeding overlapping span (lpos)
+      //       -> not found: tag with 0
+      //       -> found: Add +1 to the level of the (lpos)
+      //    c) If the new tag is smaller than the previous element,
+      //       reorder
+
+      // Normalize start and end
+      var sortedArcs = this._arcs.map(function (v) {
+
+        // Check for long anchors
+        if (v.start instanceof Array) {
+
+          if (v.start[0] == v.start[1]) {
+            v.start = v.start[0];
+          }
+
+          else {
+          
+            var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
+
+            // Calculate signature to avoid multiple anchors
+            var anchorSig = "#" + v.start[0] + "_" + v.start[1];
+            if (v.start[0] > v.start[1]) {
+              anchorSig = "#" + v.start[1] + "_" + v.start[0];
+            };
+            
+            // Check if the anchor already exist
+            var anchor = anchors[anchorSig];
+            if (anchor === undefined) {
+              anchor = {
+                "first":   v.start[0],
+                "last" :   v.start[1],
+                "length" : v.start[1] - v.start[0]
+              };
+              anchors[anchorSig] = anchor;
+              // anchors.push(v.startAnchor);
+            };
+
+            v.startAnchor = anchor;
+
+            // Add to anchors list
+            v.start = middle;
+          };
+        };
+
+        if (v.end instanceof Array) {
+
+          if (v.end[0] == v.end[1]) {
+            v.end = v.end[0];
+          }
+
+          else {
+
+            var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
+
+            // Calculate signature to avoid multiple anchors
+            var anchorSig = "#" + v.end[0] + "_" + v.end[1];
+            if (v.end[0] > v.end[1]) {
+              anchorSig = "#" + v.end[1] + "_" + v.end[0];
+            };
+
+            // Check if the anchor already exist
+            var anchor = anchors[anchorSig];
+            if (anchor === undefined) {
+              anchor = {
+                "first":   v.end[0],
+                "last" :   v.end[1],
+                "length" : v.end[1] - v.end[0]
+              };
+              anchors[anchorSig] = anchor;
+              // anchors.push(v.startAnchor);
+            };
+            
+            v.endAnchor = anchor;
+
+            // Add to anchors list
+            // anchors.push(v.endAnchor);
+            v.end = middle;
+          };
+        };
+
+        v.first = v.start;
+        v.last = v.end;
+
+        // calculate the arch length
+        if (v.start < v.end) {
+          v.length = v.end - v.start;
+        }
+        else {
+          // v.first = v.end;
+          // v.last = v.start;
+          v.length = v.start - v.end;
+        };
+
+        return v;
+      });
+
+      // Sort based on length
+      sortedArcs.sort(function (a, b) {
+        if (a.length < b.length)
+          return -1;
+        else
+          return 1;
+      });
+
+      // Add sorted arcs and anchors
+      this._sortedArcs    = lengthSort(sortedArcs, false);
+
+      // Translate map to array (there is probably a better JS method)
+      var sortedAnchors = [];
+      for (var i in anchors) {
+        sortedAnchors.push(anchors[i]);
+      };
+      this._sortedAnchors = lengthSort(sortedAnchors, true);
+    },
+
+    /**
+     * Center the viewport of the canvas
+     * TODO:
+     *   This is identical to tree
+     */
+    center : function () {
+      if (this._element === undefined)
+       return;
+
+      var treeDiv = this._element.parentNode;
+
+      var cWidth = parseFloat(window.getComputedStyle(this._element).width);
+      var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
+      // Reposition:
+      if (cWidth > treeWidth) {
+       var scrollValue = (cWidth - treeWidth) / 2;
+       treeDiv.scrollLeft = scrollValue;
+      };
+    },
+
+
+    // Show the element
+    show : function () {
+      var svg = this._element;
+      var height = this.maxArc;
+
+      // 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"));
+
+      // Draw token list
+      var text = g.appendChild(this._c("text"));
+      text.setAttribute('class', 'leaf');
+      text.setAttribute("text-anchor", "start");
+      text.setAttribute("y", height);
+
+      // Calculate the start position
+      this._y = height - (this.anchorStart);
+
+      // Introduce some prepending whitespace (yeah - I know ...)
+      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 x = text.appendChild(this._c("text"));
+        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!
+        tspan.setAttribute("dx", this.tokenSep);
+      };
+
+      // Get some global position data that may change on resize
+      var globalBoundingBox = this._rect(g);
+      this.offsetLeft = globalBoundingBox.left;
+
+      // The group of arcs
+      var arcs = g.appendChild(this._c("g"));
+      this._arcsElement = arcs;
+      arcs.classList.add("arcs");
+
+      var labels = g.appendChild(this._c("g"));
+      this._labelsElement = labels;
+      labels.classList.add("labels");
+
+      // Sort arcs if not sorted yet
+      if (this._sortedArcs === undefined)
+        this._sortArcs();
+
+      // 1. Draw all anchors
+      var i;
+      for (i in this._sortedAnchors) {
+        this._drawAnchor(this._sortedAnchors[i]);
+      };
+
+      // 2. Draw all arcs
+      for (i in this._sortedArcs) {
+        this._drawArc(this._sortedArcs[i]);
+      };
+
+      // Resize the svg with some reasonable margins
+      var width = this._rect(text).width;
+      svg.setAttribute("width", width + 20);
+      svg.setAttribute("height", height + 20);
+      svg.setAttribute("class", "relTree");
+    }
+  };
+
+  // Sort relations regarding their span
+  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;
+  };
+});