Cleanup relation drawing code

Change-Id: I4d4b939a2e3888a001d03c0bf60d6dde9c132a95
diff --git a/dev/demo/relationsdemo.js b/dev/demo/relationsdemo.js
index 200894e..0ffbc8a 100644
--- a/dev/demo/relationsdemo.js
+++ b/dev/demo/relationsdemo.js
@@ -156,7 +156,7 @@
     .addRel({ start: 1, end: 2, label: "c", direction: "bi"   })
     .addRel({ start: 0, end: 2, label: "dreizehn", direction: "uni" })
     .addRel({ start: [2,4], end: 5, label: "e", direction: "uni" })
-    .addRel({ start: [5,6], end: 7, label: "g", direction: "uni" })
+    .addRel({ start: [5,6], end: 7, direction: "uni" })
     .addRel({ start: 4, end: [6,8], label: "f", direction: "bi" })
   ;
 
diff --git a/dev/js/src/match/relations.js b/dev/js/src/match/relations.js
index 137eb63..22fbc51 100644
--- a/dev/js/src/match/relations.js
+++ b/dev/js/src/match/relations.js
@@ -1,3 +1,9 @@
+/**
+ * Parse a relational tree and visualize using arcs.
+ *
+ * @author Nils Diewald
+ */
+
 define([], function () {
   "use strict";
 
@@ -6,74 +12,82 @@
 
   return {
     create : function (snippet) {
-      var obj = Object.create(this);
-      obj._tokens = [];
-      obj._arcs = []
-      obj._tokenElements = [];
-      obj._y = 0;
-
-      // Some configurations
-      obj.maxArc      = 200; // maximum height of the bezier control point
-      obj.overlapDiff = 40;
-      obj.arcDiff     = 15;
-      obj.anchorDiff  = 8;
-      obj.anchorStart = 15;
-      obj.tokenSep    = 30;
-      obj.xPadding    = 10;
-      obj.yPadding    = 5;
-      return obj._init(snippet);
+      return Object.create(this)._init(snippet);
     },
-    
+
+    // Initialize the state of the object
     _init : function (snippet) {
 
-      if (snippet != undefined && snippet != null) {
-        var html = document.createElement("div");
-        html.innerHTML = snippet;
+      // Predefine some values
+      this._tokens = [];
+      this._arcs = []
+      this._tokenElements = [];
+      this._y = 0;
 
-        // Establish temporary parsing memory
-        this.temp = {
+      // Some configurations
+      this.maxArc      = 200; // maximum height of the bezier control point
+      this.overlapDiff = 40;
+      this.arcDiff     = 15;
+      this.anchorDiff  = 8;
+      this.anchorStart = 15;
+      this.tokenSep    = 30;
+      this.xPadding    = 10;
+      this.yPadding    = 5;
 
-          // Remember the map id => pos
-          target : {},
+      // No snippet to process
+      if (snippet == undefined || snippet == null)
+        return this;
 
-          // Remember edge definitions
-          edges : [],
+      // Parse the snippet
+      var html = document.createElement("div");
+      html.innerHTML = snippet;
 
-          // Keep track of the current token position
-          pos : 0
-        };
-        this._parse(0, html.childNodes, undefined);
-
-        // Establish edge list
-        var targetMap = this.temp['target'];
-        var edges = this.temp['edges'];
-        for (var i in edges) {
-          var edge = edges[i];
-
-          // Check the target identifier
-          var targetID = edge.targetID;
-          var target = targetMap[targetID];
-
-          if (target != undefined) {
-
-            // Add relation
-            this.addRel({
-              start : edge.src,
-              end : target,
-              direction : 'uni',
-              label : edge.label
-            });
-          };
-        };
-
-        // Reset parsing memory
-        this.temp = {};
+      // 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) {
+
+          // Add relation
+          this.addRel({
+            start : edge.src,
+            end : target,
+            direction : 'uni',
+            label : edge.label
+          });
+        };
+      };
+
+      // 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];
 
@@ -84,28 +98,33 @@
           if (c.hasAttribute('xml:id')) {
 
             // Remember that pos has this identifier
+            // TODO:
+            //   Target may be a span!
             this.temp['target'][c.getAttribute('xml:id')] = this.temp['pos'];
           }
 
           // Node is a relation
           else if (c.hasAttribute('xlink:href')) {
-            var label, target;
+            var label;
 
-            target = c.getAttribute('xlink:href');
-            target = target.replace(/^#/, "");
+            // Get target id
+            var target = c.getAttribute('xlink:href').replace(/^#/, "");
 
             if (c.hasAttribute('xlink:title')) {
               label = this._clean(c.getAttribute('xlink:title'));
             };
 
             // Remember the defined edge
+            // TODO:
+            //   src may be a span!
             this.temp['edges'].push({
-              label : label,
-              src : this.temp['pos'],
+              label    : label,
+              src      : this.temp['pos'],
               targetID : target
             });
           };
 
+          // Go on with child nodes
           if (c.hasChildNodes()) {
             this._parse(0, c.childNodes, mark);
           };
@@ -130,15 +149,22 @@
       };
     },
 
+    
     // 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);
@@ -157,10 +183,10 @@
 
       // Calculate the span of the first and last token, the anchor spans
       var firstBox = this._tokenElements[anchor.first].getBoundingClientRect();
-      var lastBox = this._tokenElements[anchor.last].getBoundingClientRect();
+      var lastBox  = this._tokenElements[anchor.last].getBoundingClientRect();
 
       var startPos = firstBox.left - this.offsetLeft;
-      var endPos = lastBox.right - this.offsetLeft;
+      var endPos   = lastBox.right - this.offsetLeft;
       
       var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
 
@@ -173,7 +199,8 @@
       return l;
     },
     
-    // Create an arc with a label
+
+    // Create an arc with an optional label
     // Potentially needs a height parameter for stacks
     _drawArc : function (arc) {
 
@@ -235,6 +262,9 @@
         };
       };
 
+      if (arc.label === undefined)
+        return g;
+      
       /*
        * Calculate the top point of the arc for labeling using
        * de Casteljau's algorithm, see e.g.
@@ -242,40 +272,31 @@
        * of course simplified to symmetric arcs ...
        */
       // Interpolate one side of the control polygon
-      // var controlInterpY1 = (startY + controlY) / 2;
-      // var controlInterpY2 = (controlInterpY1 + controlY) / 2;
       var middleY = (((startY + controlY) / 2) + controlY) / 2;
 
-      // WARNING!
-      // This won't respect span anchors, adjusting startY and endY!
+      // Create a boxed label
+      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);
 
-      if (arc.label !== undefined) {
-        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();
 
-        var labelBox = labelE.getBBox();
-        var textWidth = labelBox.width; // labelE.getComputedTextLength();
-        var textHeight = labelBox.height; // labelE.getComputedTextLength();
-
-        // Add padding to left and right
-
-        // var labelR = g.appendChild(this._c("rect"));
-        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);
-      };
-
-      // return g;
+      // 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;
@@ -284,17 +305,20 @@
       var svg = this._c("svg");
 
       window.addEventListener("resize", function () {
-        // TODO: Only if text-size changed!
+        // 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");
@@ -305,13 +329,14 @@
     },
 
     // Add a relation with a start, an end,
-    // a direction value and a label text
+    // 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;
@@ -323,7 +348,6 @@
      */
     _sortArcs : function () {
 
-
       // TODO:
       //   Keep in mind that the arcs may have long anchors!
       //   1. Iterate over all arcs
@@ -395,7 +419,9 @@
       this._sortedArcs    = lengthSort(sortedArcs, false);
       this._sortedAnchors = lengthSort(anchors, true);
     },
-    
+
+
+    // Show the element
     show : function () {
       var svg = this._element;
       var height = this.maxArc;
@@ -409,16 +435,16 @@
 
       var g = svg.appendChild(this._c("g"));
 
-      /*
-       * Create token list
-       */
+      // 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";
@@ -433,38 +459,34 @@
         this._tokenElements.push(tspan);
 
         // Add whitespace!
-        //var ws = text.appendChild(this._c("tspan"));
-        //ws.appendChild(document.createTextNode(" "));
-        // ws.setAttribute("class", "rel-ws");
         tspan.setAttribute("dx", this.tokenSep);
       };
 
+      // Get some global position data that may change on resize
       var globalBoundingBox = g.getBoundingClientRect();
       this.offsetLeft = globalBoundingBox.left;
 
+      // The group of arcs
       var arcs = g.appendChild(this._c("g"));
       this._arcsElement = arcs;
-
       arcs.classList.add("arcs");
 
       // Sort arcs if not sorted yet
-      if (this._sortedArcs === undefined) {
+      if (this._sortedArcs === undefined)
         this._sortArcs();
-      };
 
+      // 1. Draw all anchors
       var i;
-
-      // Draw all anchors
       for (i in this._sortedAnchors) {
         this._drawAnchor(this._sortedAnchors[i]);
       };
 
-
-      // draw all arcs
+      // 2. Draw all arcs
       for (i in this._sortedArcs) {
         this._drawArc(this._sortedArcs[i]);
       };
 
+      // Resize the svg with some reasonable margins
       var width = text.getBoundingClientRect().width;
       svg.setAttribute("width", width + 20);
       svg.setAttribute("height", height + 20);
@@ -472,6 +494,7 @@
     }
   };
 
+  // Sort relations regarding their span
   function lengthSort (list, inclusive) {
 
     /*
@@ -488,7 +511,6 @@
 
       // Check the stack order
       var overlaps = 0;
-
       for (var j = (stack.length - 1); j >= 0; j--) {
         var check = stack[j];
 
@@ -522,7 +544,8 @@
 
       // Although it is already sorted,
       // the new item has to be put at the correct place
-      // TODO: Use something like splice() instead
+      // TODO:
+      //   Use something like splice() instead
       stack.sort(function (a,b) {
         b.overlaps - a.overlaps
       });