Added span anchors to relation visualization
Change-Id: Id106c4659869a7162e1c0b3f1838e69fad688886
diff --git a/dev/js/src/match/info.js b/dev/js/src/match/info.js
index 7a659ba..c2154a5 100644
--- a/dev/js/src/match/info.js
+++ b/dev/js/src/match/info.js
@@ -57,17 +57,17 @@
var elem = this._match.element();
if (this.opened == true) {
- elem.removeChild(
- this.element()
- );
- this.opened = false;
+ elem.removeChild(
+ this.element()
+ );
+ this.opened = false;
}
else {
- // Append element to match
+ // Append element to match
elem.appendChild(
- this.element()
- );
- this.opened = true;
+ this.element()
+ );
+ this.opened = true;
};
return this.opened;
@@ -83,48 +83,48 @@
// Get all tokens
if (tokens === undefined) {
- focus = this._match.getTokens();
+ focus = this._match.getTokens();
}
// Get only some tokens
else {
-
- // Push newly to focus array
- for (var i = 0; i < tokens.length; i++) {
- var term = tokens[i];
- try {
- // Create info layer objects
- var layer = infoLayerClass.create(term);
- layer.type = "tokens";
- focus.push(layer);
- }
- catch (e) {
- continue;
- };
- };
+
+ // Push newly to focus array
+ for (var i = 0; i < tokens.length; i++) {
+ var term = tokens[i];
+ try {
+ // Create info layer objects
+ var layer = infoLayerClass.create(term);
+ layer.type = "tokens";
+ focus.push(layer);
+ }
+ catch (e) {
+ continue;
+ };
+ };
};
// No tokens chosen
if (focus.length == 0)
- cb(null);
+ cb(null);
// Get info (may be cached)
KorAP.API.getMatchInfo(
- this._match,
- { 'spans' : false, 'layer' : focus },
-
- // Callback for retrieval
- function (matchResponse) {
+ this._match,
+ { 'spans' : false, 'layer' : focus },
+
+ // Callback for retrieval
+ function (matchResponse) {
if (matchResponse === undefined)
cb(null);
- // Get snippet from match info
- if (matchResponse["snippet"] !== undefined) {
- this._table = matchTableClass.create(matchResponse["snippet"]);
- cb(this._table);
- };
- }.bind(this)
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ this._table = matchTableClass.create(matchResponse["snippet"]);
+ cb(this._table);
+ };
+ }.bind(this)
);
/*
@@ -142,22 +142,22 @@
// TODO: Support and cache multiple trees
KorAP.API.getMatchInfo(
- this._match, {
- 'spans' : true,
- 'foundry' : foundry,
- 'layer' : layer
- },
- function (matchResponse) {
- // Get snippet from match info
- if (matchResponse["snippet"] !== undefined) {
- // Todo: This should be cached somehow
+ this._match, {
+ 'spans' : true,
+ 'foundry' : foundry,
+ 'layer' : layer
+ },
+ function (matchResponse) {
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ // Todo: This should be cached somehow
- cb(matchTreeClass.create(matchResponse["snippet"]));
- }
- else {
- cb(null);
- };
- }.bind(this)
+ cb(matchTreeClass.create(matchResponse["snippet"]));
+ }
+ else {
+ cb(null);
+ };
+ }.bind(this)
);
},
@@ -188,10 +188,10 @@
h6.appendChild(document.createElement('span'))
.appendChild(document.createTextNode(foundry));
h6.appendChild(document.createElement('span'))
- .appendChild(document.createTextNode(layer));
-
+ .appendChild(document.createTextNode(layer));
+
var tree = matchtree.appendChild(
- document.createElement('div')
+ document.createElement('div')
);
this._element.insertBefore(matchtree, this._element.lastChild);
@@ -202,10 +202,10 @@
close.className = 'close';
close.appendChild(document.createElement('span'));
close.addEventListener(
- 'click', function (e) {
- matchtree.parentNode.removeChild(matchtree);
- e.halt();
- }
+ 'click', function (e) {
+ matchtree.parentNode.removeChild(matchtree);
+ e.halt();
+ }
);
tree.classList.add('loading');
@@ -213,24 +213,24 @@
// Get tree data async
this.getTree(foundry, layer, function (treeObj) {
- tree.classList.remove('loading');
+ tree.classList.remove('loading');
- // Something went wrong - probably log!!!
+ // Something went wrong - probably log!!!
- if (treeObj === null) {
- tree.appendChild(document.createTextNode('No data available.'));
- }
- else {
- tree.appendChild(treeObj.element());
- // Reposition the view to the center
- // (This may in a future release be a reposition
- // to move the root into the center or the actual
- // match)
+ if (treeObj === null) {
+ tree.appendChild(document.createTextNode('No data available.'));
+ }
+ else {
+ tree.appendChild(treeObj.element());
+ // Reposition the view to the center
+ // (This may in a future release be a reposition
+ // to move the root into the center or the actual
+ // match)
var dl = document.createElement('li');
dl.className = 'download';
dl.addEventListener(
- 'click', function (e) {
+ 'click', function (e) {
var a = document.createElement('a');
a.setAttribute('href-lang', 'image/svg+xml');
@@ -243,14 +243,14 @@
document.body.removeChild(a)
e.halt();
- }
+ }
);
actions.appendChild(dl);
treeObj.center();
- };
-
- if (cb !== undefined)
- cb(treeObj);
+ };
+
+ if (cb !== undefined)
+ cb(treeObj);
});
},
@@ -260,7 +260,7 @@
element : function () {
if (this._element !== undefined)
- return this._element;
+ return this._element;
// Create info table
var info = document.createElement('div');
@@ -274,7 +274,7 @@
// Create the table asynchronous
this.getTable(undefined, function (table) {
- if (table !== null) {
+ if (table !== null) {
matchtable.appendChild(table.element());
};
matchtable.classList.remove('loading');
@@ -283,36 +283,48 @@
this._matchCreator = matchQueryCreator.create(info);
});
+ // Join spans and relations
+ var treeLayers = []
+ var spans = this._match.getSpans();
+ var rels = this._match.getRels();
+ var i;
+ for (i in spans) {
+ treeLayers.push(spans[i]);
+ };
+ for (i in rels) {
+ treeLayers.push(rels[i]);
+ };
+
// Get spans
- var spanLayers = this._match.getSpans().sort(
- function (a, b) {
- if (a.foundry < b.foundry) {
- return -1;
- }
- else if (a.foundry > b.foundry) {
- return 1;
- }
- else if (a.layer < b.layer) {
- return -1;
- }
- else if (a.layer > b.layer) {
- return 1;
- };
- return 0;
- });
+ treeLayers = treeLayers.sort(
+ function (a, b) {
+ if (a.foundry < b.foundry) {
+ return -1;
+ }
+ else if (a.foundry > b.foundry) {
+ return 1;
+ }
+ else if (a.layer < b.layer) {
+ return -1;
+ }
+ else if (a.layer > b.layer) {
+ return 1;
+ };
+ return 0;
+ });
var menuList = [];
// Show tree views
- for (var i = 0; i < spanLayers.length; i++) {
- var span = spanLayers[i];
-
- // Add foundry/layer to menu list
- menuList.push([
- span.foundry + '/' + span.layer,
- span.foundry,
- span.layer
- ]);
+ for (var i = 0; i < treeLayers.length; i++) {
+ var span = treeLayers[i];
+
+ // Add foundry/layer to menu list
+ menuList.push([
+ span.foundry + '/' + span.layer,
+ span.foundry,
+ span.layer
+ ]);
};
// Create tree menu
@@ -325,8 +337,8 @@
span.appendChild(treeElement);
span.addEventListener('click', function (e) {
- treemenu.show();
- treemenu.focus();
+ treemenu.show();
+ treemenu.focus();
});
this._element = info;
@@ -342,7 +354,7 @@
*/
treeMenu : function (list) {
if (this._treeMenu !== undefined)
- return this._treeMenu;
+ return this._treeMenu;
return this._treeMenu = matchTreeMenuClass.create(this, list);
}
diff --git a/dev/js/src/match/infolayer.js b/dev/js/src/match/infolayer.js
index 0812f71..82b9bd3 100644
--- a/dev/js/src/match/infolayer.js
+++ b/dev/js/src/match/infolayer.js
@@ -23,26 +23,26 @@
// Initialize Layer
_init : function (foundry, layer, type) {
if (foundry === undefined)
- throw new Error("Missing parameters");
+ throw new Error("Missing parameters");
if (layer === undefined) {
- if (_AvailableRE.exec(foundry)) {
- this.foundry = RegExp.$1;
- this.layer = RegExp.$2;
- this.type = RegExp.$3;
- }
- else {
- throw new Error("Missing parameters");
- };
+ if (_AvailableRE.exec(foundry)) {
+ this.foundry = RegExp.$1;
+ this.layer = RegExp.$2;
+ this.type = RegExp.$3;
+ }
+ else {
+ throw new Error("Missing parameters");
+ };
}
else {
- this.foundry = foundry;
- this.layer = layer;
- this.type = type;
+ this.foundry = foundry;
+ this.layer = layer;
+ this.type = type;
};
if (this.type === undefined)
- this.type = 'tokens';
+ this.type = 'tokens';
return this;
}
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;
+ };
});
diff --git a/dev/js/src/match/treeitem.js b/dev/js/src/match/treeitem.js
index 9a4b952..9ddb1fe 100644
--- a/dev/js/src/match/treeitem.js
+++ b/dev/js/src/match/treeitem.js
@@ -11,7 +11,7 @@
*/
create : function (params) {
return Object.create(itemClass)
- .upgradeTo(this)._init(params);
+ .upgradeTo(this)._init(params);
},
/**
@@ -20,7 +20,7 @@
*/
content : function (content) {
if (arguments.length === 1) {
- this._content = content;
+ this._content = content;
};
return this._content;
},
@@ -47,13 +47,13 @@
menu.hide();
e.halt();
if (menu.info() !== undefined)
- menu.info().addTree(this._foundry, this._layer);
+ menu.info().addTree(this._foundry, this._layer);
},
// Initialize tree menu item.
_init : function (params) {
if (params[0] === undefined)
- throw new Error("Missing parameters");
+ throw new Error("Missing parameters");
this._name = params[0];
this._foundry = params[1];
diff --git a/dev/js/src/match/treemenu.js b/dev/js/src/match/treemenu.js
index 5af4619..2ca0731 100644
--- a/dev/js/src/match/treemenu.js
+++ b/dev/js/src/match/treemenu.js
@@ -17,14 +17,14 @@
*/
create : function (info, params) {
var obj = Object.create(menuClass)
- .upgradeTo(this)
- ._init(params, {itemClass : itemClass});
+ .upgradeTo(this)
+ ._init(params, {itemClass : itemClass});
obj.limit(6);
obj._info = info;
// This is only domspecific
obj.element().addEventListener('blur', function (e) {
- this.menu.hide();
+ this.menu.hide();
});
return obj;