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
});