blob: 1451c1e5e068f048951292a97b99eba345eb01a5 [file] [log] [blame]
Akron6f1302b2017-09-13 12:46:02 +02001/**
2 * Parse a relational tree and visualize using arcs.
3 *
4 * @author Nils Diewald
5 */
6
Akron671fdb92017-09-12 18:09:46 +02007define([], function () {
Akronf5dc5102017-05-16 20:32:57 +02008 "use strict";
9
10 var svgNS = "http://www.w3.org/2000/svg";
Akron671fdb92017-09-12 18:09:46 +020011 var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
Akronf5dc5102017-05-16 20:32:57 +020012
13 return {
14 create : function (snippet) {
Akron6f1302b2017-09-13 12:46:02 +020015 return Object.create(this)._init(snippet);
Akronf5dc5102017-05-16 20:32:57 +020016 },
Akron6f1302b2017-09-13 12:46:02 +020017
18 // Initialize the state of the object
Akronf5dc5102017-05-16 20:32:57 +020019 _init : function (snippet) {
Akron671fdb92017-09-12 18:09:46 +020020
Akron6f1302b2017-09-13 12:46:02 +020021 // Predefine some values
Akron71c87de2017-09-15 14:24:21 +020022 this._tokens = [];
23 this._arcs = [];
Akron6f1302b2017-09-13 12:46:02 +020024 this._tokenElements = [];
25 this._y = 0;
Akron671fdb92017-09-12 18:09:46 +020026
Akron6f1302b2017-09-13 12:46:02 +020027 // Some configurations
28 this.maxArc = 200; // maximum height of the bezier control point
Akronfee0b622017-09-13 14:46:43 +020029 this.overlapDiff = 40; // Difference on overlaps and minimum height for self-refernces
Akron6f1302b2017-09-13 12:46:02 +020030 this.arcDiff = 15;
31 this.anchorDiff = 8;
32 this.anchorStart = 15;
33 this.tokenSep = 30;
34 this.xPadding = 10;
35 this.yPadding = 5;
Akron671fdb92017-09-12 18:09:46 +020036
Akron6f1302b2017-09-13 12:46:02 +020037 // No snippet to process
38 if (snippet == undefined || snippet == null)
39 return this;
Akron671fdb92017-09-12 18:09:46 +020040
Akron6f1302b2017-09-13 12:46:02 +020041 // Parse the snippet
42 var html = document.createElement("div");
43 html.innerHTML = snippet;
Akron671fdb92017-09-12 18:09:46 +020044
Akron6f1302b2017-09-13 12:46:02 +020045 // Establish temporary parsing memory
46 this.temp = {
47 target : {}, // Remember the map id => pos
48 edges : [], // Remember edge definitions
49 pos : 0 // Keep track of the current token position
Akron671fdb92017-09-12 18:09:46 +020050 };
51
Akron6f1302b2017-09-13 12:46:02 +020052 // Start parsing from root
53 this._parse(0, html.childNodes, undefined);
54
55 // Establish edge list
56 var targetMap = this.temp['target'];
57 var edges = this.temp['edges'];
58
59 // Iterate over edge lists
60 // TODO:
61 // Support spans for anchors!
62 for (var i in edges) {
63 var edge = edges[i];
64
65 // Check the target identifier
66 var targetID = edge.targetID;
67 var target = targetMap[targetID];
68
69 if (target != undefined) {
70
Akron2c1d9ff2017-09-15 13:57:33 +020071 // Check if the source is a span anchor
72 var start = edge.srcStart;
73 if (start !== edge.srcEnd) {
74 start = [start, edge.srcEnd];
75 };
76
77
Akron6f1302b2017-09-13 12:46:02 +020078 // Add relation
Akron2c1d9ff2017-09-15 13:57:33 +020079 var relation = {
80 start : start,
Akron6f1302b2017-09-13 12:46:02 +020081 end : target,
82 direction : 'uni',
83 label : edge.label
Akron2c1d9ff2017-09-15 13:57:33 +020084 };
85 // console.log(relation);
86 this.addRel(relation);
Akron6f1302b2017-09-13 12:46:02 +020087 };
88 };
89
90 // Reset parsing memory
91 this.temp = {};
92
Akronf5dc5102017-05-16 20:32:57 +020093 return this;
94 },
95
Akron6f1302b2017-09-13 12:46:02 +020096 // Parse a node of the tree snippet
Akron671fdb92017-09-12 18:09:46 +020097 _parse : function (parent, children, mark) {
Akron6f1302b2017-09-13 12:46:02 +020098
99 // Iterate over all child nodes
Akron671fdb92017-09-12 18:09:46 +0200100 for (var i in children) {
101 var c = children[i];
102
103 // Element node
104 if (c.nodeType == 1) {
105
Akron2c1d9ff2017-09-15 13:57:33 +0200106 var xmlid, target;
107
Akron671fdb92017-09-12 18:09:46 +0200108 // Node is an identifier
109 if (c.hasAttribute('xml:id')) {
110
111 // Remember that pos has this identifier
Akron2c1d9ff2017-09-15 13:57:33 +0200112 xmlid = c.getAttribute('xml:id');
113 this.temp['target'][xmlid] = [this.temp['pos'], this.temp['pos']];
Akron671fdb92017-09-12 18:09:46 +0200114 }
115
116 // Node is a relation
117 else if (c.hasAttribute('xlink:href')) {
Akron6f1302b2017-09-13 12:46:02 +0200118 var label;
Akron671fdb92017-09-12 18:09:46 +0200119
Akron6f1302b2017-09-13 12:46:02 +0200120 // Get target id
Akron2c1d9ff2017-09-15 13:57:33 +0200121 target = c.getAttribute('xlink:href').replace(/^#/, "");
Akron671fdb92017-09-12 18:09:46 +0200122
123 if (c.hasAttribute('xlink:title')) {
124 label = this._clean(c.getAttribute('xlink:title'));
125 };
126
127 // Remember the defined edge
Akron2c1d9ff2017-09-15 13:57:33 +0200128 var edge = {
Akron6f1302b2017-09-13 12:46:02 +0200129 label : label,
Akron2c1d9ff2017-09-15 13:57:33 +0200130 srcStart : this.temp['pos'],
Akron671fdb92017-09-12 18:09:46 +0200131 targetID : target
Akron2c1d9ff2017-09-15 13:57:33 +0200132 };
133 this.temp['edges'].push(edge);
Akron671fdb92017-09-12 18:09:46 +0200134 };
135
Akron6f1302b2017-09-13 12:46:02 +0200136 // Go on with child nodes
Akron671fdb92017-09-12 18:09:46 +0200137 if (c.hasChildNodes()) {
138 this._parse(0, c.childNodes, mark);
139 };
Akron2c1d9ff2017-09-15 13:57:33 +0200140
141 if (xmlid !== undefined) {
142 this.temp['target'][xmlid][1] = this.temp['pos'] -1;
143
144 /*
145 console.log('Target ' + xmlid + ' spans from ' +
146 this.temp['target'][xmlid][0] +
147 ' to ' +
148 this.temp['target'][xmlid][1]
149 );
150 */
151 xmlid = undefined;
152 }
153 else if (target !== undefined) {
154 edge["srcEnd"] = this.temp['pos'] -1;
155
156 /*
157 console.log('Source spans from ' +
158 edge["srcStart"] +
159 ' to ' +
160 edge["srcEnd"]
161 );
162 */
163 target = undefined;
164 };
Akron671fdb92017-09-12 18:09:46 +0200165 }
166
167 // Text node
168 else if (c.nodeType == 3) {
169
170 // Check, if there is a non-whitespace token
171 if (c.nodeValue !== undefined) {
172 var str = c.nodeValue.trim();
173 if (str !== undefined && str.length > 0) {
174
175 // Add token to token list
176 this.addToken(str);
177
178 // Move token position
179 this.temp['pos']++;
180 };
181 };
182 }
183 };
184 },
185
Akron6f1302b2017-09-13 12:46:02 +0200186
Akron671fdb92017-09-12 18:09:46 +0200187 // Remove foundry and layer for labels
188 _clean : function (title) {
189 return title.replace(_TermRE, "$3");
190 },
191
Akron6f1302b2017-09-13 12:46:02 +0200192
193 // Return the number of leaf nodes
194 // (not necessarily part of a relation).
195 // Consecutive nodes that are not part of any
196 // relation are summarized in one node.
Akron671fdb92017-09-12 18:09:46 +0200197 size : function () {
198 return this._tokens.length;
199 },
Akron15175132017-09-07 18:12:55 +0200200
Akron6f1302b2017-09-13 12:46:02 +0200201
Akronf5dc5102017-05-16 20:32:57 +0200202 // This is a shorthand for SVG element creation
203 _c : function (tag) {
204 return document.createElementNS(svgNS, tag);
205 },
206
Akron15175132017-09-07 18:12:55 +0200207
Akronf5dc5102017-05-16 20:32:57 +0200208 // Returns the center point of the requesting token
209 _tokenPoint : function (node) {
210 var box = node.getBoundingClientRect();
211 return box.x + (box.width / 2);
212 },
213
Akron15175132017-09-07 18:12:55 +0200214
215 // Draws an anchor
Akrond67d45b2017-05-18 21:47:38 +0200216 _drawAnchor : function (anchor) {
Akron15175132017-09-07 18:12:55 +0200217
218 // Calculate the span of the first and last token, the anchor spans
Akron3a4a08e2017-05-23 22:34:18 +0200219 var firstBox = this._tokenElements[anchor.first].getBoundingClientRect();
Akron6f1302b2017-09-13 12:46:02 +0200220 var lastBox = this._tokenElements[anchor.last].getBoundingClientRect();
Akrond67d45b2017-05-18 21:47:38 +0200221
Akron15175132017-09-07 18:12:55 +0200222 var startPos = firstBox.left - this.offsetLeft;
Akron6f1302b2017-09-13 12:46:02 +0200223 var endPos = lastBox.right - this.offsetLeft;
Akron15175132017-09-07 18:12:55 +0200224
Akron3a4a08e2017-05-23 22:34:18 +0200225 var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
226
Akrond67d45b2017-05-18 21:47:38 +0200227 var l = this._c('path');
Akron65d31082017-09-08 16:23:40 +0200228 this._arcsElement.appendChild(l);
Akron3a4a08e2017-05-23 22:34:18 +0200229 l.setAttribute("d", "M " + startPos + "," + y + " L " + endPos + "," + y);
Akrond67d45b2017-05-18 21:47:38 +0200230 l.setAttribute("class", "anchor");
231 anchor.element = l;
232 anchor.y = y;
233 return l;
234 },
235
Akron6f1302b2017-09-13 12:46:02 +0200236
237 // Create an arc with an optional label
Akronf5dc5102017-05-16 20:32:57 +0200238 // Potentially needs a height parameter for stacks
Akron63ae00b2017-05-16 22:03:36 +0200239 _drawArc : function (arc) {
Akronf5dc5102017-05-16 20:32:57 +0200240
Akrond67d45b2017-05-18 21:47:38 +0200241 var startPos, endPos;
Akronfee0b622017-09-13 14:46:43 +0200242 var startY = this._y;
243 var endY = this._y;
Akronf5dc5102017-05-16 20:32:57 +0200244
Akrond67d45b2017-05-18 21:47:38 +0200245 if (arc.startAnchor !== undefined) {
Akron3a4a08e2017-05-23 22:34:18 +0200246 startPos = this._tokenPoint(arc.startAnchor.element);
Akrond67d45b2017-05-18 21:47:38 +0200247 startY = arc.startAnchor.y;
248 }
249 else {
250 startPos = this._tokenPoint(this._tokenElements[arc.first]);
251 };
252
253 if (arc.endAnchor !== undefined) {
254 endPos = this._tokenPoint(arc.endAnchor.element)
255 endY = arc.endAnchor.y;
256 }
257 else {
258 endPos = this._tokenPoint(this._tokenElements[arc.last]);
259 };
260
Akron15175132017-09-07 18:12:55 +0200261 startPos -= this.offsetLeft;
262 endPos -= this.offsetLeft;
263
Akronfee0b622017-09-13 14:46:43 +0200264 // Special treatment for self-references
265 var overlaps = arc.overlaps;
266 if (startPos == endPos) {
267 startPos -= this.overlapDiff / 3;
Akronc9f23022017-09-13 15:43:52 +0200268 endPos += this.overlapDiff / 3;
Akronfee0b622017-09-13 14:46:43 +0200269 overlaps += .5;
270 };
271
Akronf5dc5102017-05-16 20:32:57 +0200272 var g = this._c("g");
Akron65d31082017-09-08 16:23:40 +0200273 g.setAttribute("class", "arc");
Akronf5dc5102017-05-16 20:32:57 +0200274 var p = g.appendChild(this._c("path"));
Akron15175132017-09-07 18:12:55 +0200275 p.setAttribute('class', 'edge');
Akron65d31082017-09-08 16:23:40 +0200276
277 // Attach the new arc before drawing, so computed values are available
278 this._arcsElement.appendChild(g);
Akronf5dc5102017-05-16 20:32:57 +0200279
280 // Create arc
281 var middle = Math.abs(endPos - startPos) / 2;
282
Akronfee0b622017-09-13 14:46:43 +0200283 // TODO:
284 // take the number of tokens into account!
285 var cHeight = this.arcDiff + (overlaps * this.overlapDiff) + (middle / 2);
Akronc5b5f742017-05-23 16:04:35 +0200286
287 // Respect the maximum height
288 cHeight = cHeight < this.maxArc ? cHeight : this.maxArc;
Akronf5dc5102017-05-16 20:32:57 +0200289
290 var x = Math.min(startPos, endPos);
Akronc5b5f742017-05-23 16:04:35 +0200291
Akron3a4a08e2017-05-23 22:34:18 +0200292 //var controlY = (startY + endY - cHeight);
293 var controlY = (endY - cHeight);
Akronf5dc5102017-05-16 20:32:57 +0200294
Akron3a4a08e2017-05-23 22:34:18 +0200295 var arcE = "M "+ startPos + "," + startY +
296 " C " + startPos + "," + controlY +
297 " " + endPos + "," + controlY +
298 " " + endPos + "," + endY;
Akrond67d45b2017-05-18 21:47:38 +0200299
Akron63ae00b2017-05-16 22:03:36 +0200300 p.setAttribute("d", arcE);
Akronf5dc5102017-05-16 20:32:57 +0200301
Akron3a4a08e2017-05-23 22:34:18 +0200302 if (arc.direction !== undefined) {
303 p.setAttribute("marker-end", "url(#arr)");
304 if (arc.direction === 'bi') {
305 p.setAttribute("marker-start", "url(#arr)");
306 };
307 };
308
Akron6f1302b2017-09-13 12:46:02 +0200309 if (arc.label === undefined)
310 return g;
311
Akronc5b5f742017-05-23 16:04:35 +0200312 /*
313 * Calculate the top point of the arc for labeling using
314 * de Casteljau's algorithm, see e.g.
315 * http://blog.sklambert.com/finding-the-control-points-of-a-bezier-curve/
316 * of course simplified to symmetric arcs ...
317 */
318 // Interpolate one side of the control polygon
Akronc5b5f742017-05-23 16:04:35 +0200319 var middleY = (((startY + controlY) / 2) + controlY) / 2;
320
Akron6f1302b2017-09-13 12:46:02 +0200321 // Create a boxed label
Akronc9f23022017-09-13 15:43:52 +0200322 g = this._c("g");
323 g.setAttribute("class", "label");
324 this._labelsElement.appendChild(g);
325
326 var that = this;
327 g.addEventListener('mouseenter', function () {
328 that._labelsElement.appendChild(this);
329 });
330
Akron6f1302b2017-09-13 12:46:02 +0200331 var labelE = g.appendChild(this._c("text"));
332 labelE.setAttribute("x", x + middle);
333 labelE.setAttribute("y", middleY + 3);
334 labelE.setAttribute("text-anchor", "middle");
335 var textNode = document.createTextNode(arc.label);
336 labelE.appendChild(textNode);
Akronc5b5f742017-05-23 16:04:35 +0200337
Akron6f1302b2017-09-13 12:46:02 +0200338 var labelBox = labelE.getBBox();
339 var textWidth = labelBox.width; // labelE.getComputedTextLength();
340 var textHeight = labelBox.height; // labelE.getComputedTextLength();
Akron1dc87902017-05-29 16:04:56 +0200341
Akron6f1302b2017-09-13 12:46:02 +0200342 // Add box with padding to left and right
343 var labelR = g.insertBefore(this._c("rect"), labelE);
344 var boxWidth = textWidth + 2 * this.xPadding;
345 labelR.setAttribute("x", x + middle - (boxWidth / 2));
346 labelR.setAttribute("ry", 5);
347 labelR.setAttribute("y", labelBox.y - this.yPadding);
348 labelR.setAttribute("width", boxWidth);
349 labelR.setAttribute("height", textHeight + 2 * this.yPadding);
Akronf5dc5102017-05-16 20:32:57 +0200350 },
351
Akron6f1302b2017-09-13 12:46:02 +0200352 // Get the svg element
Akronf5dc5102017-05-16 20:32:57 +0200353 element : function () {
354 if (this._element !== undefined)
355 return this._element;
356
357 // Create svg
358 var svg = this._c("svg");
Akron3a4a08e2017-05-23 22:34:18 +0200359
360 window.addEventListener("resize", function () {
Akron6f1302b2017-09-13 12:46:02 +0200361 // TODO:
362 // Only if text-size changed!
363 // TODO:
364 // This is currently untested
Akron3a4a08e2017-05-23 22:34:18 +0200365 this.show();
366 }.bind(this));
Akron6f1302b2017-09-13 12:46:02 +0200367
368 // Define marker arrows
Akron3a4a08e2017-05-23 22:34:18 +0200369 var defs = svg.appendChild(this._c("defs"));
370 var marker = defs.appendChild(this._c("marker"));
371 marker.setAttribute("refX", 9);
372 marker.setAttribute("id", "arr");
373 marker.setAttribute("orient", "auto-start-reverse");
374 marker.setAttribute("markerUnits","userSpaceOnUse");
Akron3a4a08e2017-05-23 22:34:18 +0200375 var arrow = this._c("path");
376 arrow.setAttribute("transform", "scale(0.8)");
377 arrow.setAttribute("d", "M 0,-5 0,5 10,0 Z");
378 marker.appendChild(arrow);
379
Akronf5dc5102017-05-16 20:32:57 +0200380 this._element = svg;
381 return this._element;
382 },
383
384 // Add a relation with a start, an end,
Akron6f1302b2017-09-13 12:46:02 +0200385 // a direction value and an optional label text
Akronc5b5f742017-05-23 16:04:35 +0200386 addRel : function (rel) {
387 this._arcs.push(rel);
388 return this;
Akronf5dc5102017-05-16 20:32:57 +0200389 },
390
Akronc5b5f742017-05-23 16:04:35 +0200391
Akron6f1302b2017-09-13 12:46:02 +0200392 // Add a token to the list (this will mostly be a word)
Akronc5b5f742017-05-23 16:04:35 +0200393 addToken : function(token) {
394 this._tokens.push(token);
395 return this;
396 },
397
Akronf5dc5102017-05-16 20:32:57 +0200398 /*
399 * All arcs need to be sorted before shown,
400 * to avoid nesting.
401 */
402 _sortArcs : function () {
403
Akrond67d45b2017-05-18 21:47:38 +0200404 // TODO:
405 // Keep in mind that the arcs may have long anchors!
406 // 1. Iterate over all arcs
407 // 2. Sort all multi
Akron71c87de2017-09-15 14:24:21 +0200408 var anchors = {};
Akrond67d45b2017-05-18 21:47:38 +0200409
Akronf5dc5102017-05-16 20:32:57 +0200410 // 1. Sort by length
411 // 2. Tag all spans with the number of overlaps before
412 // a) Iterate over all spans
413 // b) check the latest preceeding overlapping span (lpos)
414 // -> not found: tag with 0
415 // -> found: Add +1 to the level of the (lpos)
416 // c) If the new tag is smaller than the previous element,
417 // reorder
Akron63ae00b2017-05-16 22:03:36 +0200418
419 // Normalize start and end
420 var sortedArcs = this._arcs.map(function (v) {
Akrond67d45b2017-05-18 21:47:38 +0200421
422 // Check for long anchors
423 if (v.start instanceof Array) {
424 var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
425
Akron71c87de2017-09-15 14:24:21 +0200426 // Calculate signature to avoid multiple anchors
427 var anchorSig = "#" + v.start[0] + "_" + v.start[1];
428 if (v.start[0] > v.start[1]) {
429 anchorSig = "#" + v.start[1] + "_" + v.start[0];
Akrond67d45b2017-05-18 21:47:38 +0200430 };
431
Akron71c87de2017-09-15 14:24:21 +0200432 // Check if the anchor already exist
433 var anchor = anchors[anchorSig];
434 if (anchor === undefined) {
435 anchor = {
436 "first": v.start[0],
437 "last" : v.start[1],
438 "length" : v.start[1] - v.start[0]
439 };
440 anchors[anchorSig] = anchor;
441 // anchors.push(v.startAnchor);
442 };
443
444 v.startAnchor = anchor;
445
Akrond67d45b2017-05-18 21:47:38 +0200446 // Add to anchors list
Akrond67d45b2017-05-18 21:47:38 +0200447 v.start = middle;
448 };
449
450 if (v.end instanceof Array) {
451 var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
Akron71c87de2017-09-15 14:24:21 +0200452
453 // Calculate signature to avoid multiple anchors
454 var anchorSig = "#" + v.end[0] + "_" + v.end[1];
455 if (v.end[0] > v.end[1]) {
456 anchorSig = "#" + v.end[1] + "_" + v.end[0];
Akrond67d45b2017-05-18 21:47:38 +0200457 };
458
Akron71c87de2017-09-15 14:24:21 +0200459 // Check if the anchor already exist
460 var anchor = anchors[anchorSig];
461 if (anchor === undefined) {
462 anchor = {
463 "first": v.end[0],
464 "last" : v.end[1],
465 "length" : v.end[1] - v.end[0]
466 };
467 anchors[anchorSig] = anchor;
468 // anchors.push(v.startAnchor);
469 };
470
471 v.endAnchor = anchor;
472
Akrond67d45b2017-05-18 21:47:38 +0200473 // Add to anchors list
Akron71c87de2017-09-15 14:24:21 +0200474 // anchors.push(v.endAnchor);
Akrond67d45b2017-05-18 21:47:38 +0200475 v.end = middle;
476 };
477
Akron2c1d9ff2017-09-15 13:57:33 +0200478 v.first = v.start;
479 v.last = v.end;
480
Akrond67d45b2017-05-18 21:47:38 +0200481 // calculate the arch length
Akron63ae00b2017-05-16 22:03:36 +0200482 if (v.start < v.end) {
Akron63ae00b2017-05-16 22:03:36 +0200483 v.length = v.end - v.start;
484 }
485 else {
Akron2c1d9ff2017-09-15 13:57:33 +0200486 // v.first = v.end;
487 // v.last = v.start;
Akron63ae00b2017-05-16 22:03:36 +0200488 v.length = v.start - v.end;
489 };
Akron2c1d9ff2017-09-15 13:57:33 +0200490
Akron63ae00b2017-05-16 22:03:36 +0200491 return v;
492 });
493
494 // Sort based on length
495 sortedArcs.sort(function (a, b) {
496 if (a.length < b.length)
497 return -1;
498 else
499 return 1;
500 });
501
Akron2c1d9ff2017-09-15 13:57:33 +0200502 // Add sorted arcs and anchors
Akron3a4a08e2017-05-23 22:34:18 +0200503 this._sortedArcs = lengthSort(sortedArcs, false);
Akron71c87de2017-09-15 14:24:21 +0200504
505 // Translate map to array (there is probably a better JS method)
506 var sortedAnchors = [];
507 for (var i in anchors) {
508 sortedAnchors.push(anchors[i]);
509 };
510 this._sortedAnchors = lengthSort(sortedAnchors, true);
Akronf5dc5102017-05-16 20:32:57 +0200511 },
Akron6f1302b2017-09-13 12:46:02 +0200512
513
514 // Show the element
Akronf5dc5102017-05-16 20:32:57 +0200515 show : function () {
516 var svg = this._element;
Akron3a4a08e2017-05-23 22:34:18 +0200517 var height = this.maxArc;
518
Akron3a4a08e2017-05-23 22:34:18 +0200519 // Delete old group
520 if (svg.getElementsByTagName("g")[0] !== undefined) {
521 var group = svg.getElementsByTagName("g")[0];
522 svg.removeChild(group);
523 this._tokenElements = [];
524 };
525
526 var g = svg.appendChild(this._c("g"));
Akronf5dc5102017-05-16 20:32:57 +0200527
Akron6f1302b2017-09-13 12:46:02 +0200528 // Draw token list
Akron3a4a08e2017-05-23 22:34:18 +0200529 var text = g.appendChild(this._c("text"));
Akron3d204282017-09-07 18:24:18 +0200530 text.setAttribute('class', 'leaf');
Akron3a4a08e2017-05-23 22:34:18 +0200531 text.setAttribute("text-anchor", "start");
532 text.setAttribute("y", height);
Akronf5dc5102017-05-16 20:32:57 +0200533
Akron6f1302b2017-09-13 12:46:02 +0200534 // Calculate the start position
Akron3a4a08e2017-05-23 22:34:18 +0200535 this._y = height - (this.anchorStart);
536
Akron6f1302b2017-09-13 12:46:02 +0200537 // Introduce some prepending whitespace (yeah - I know ...)
Akron3a4a08e2017-05-23 22:34:18 +0200538 var ws = text.appendChild(this._c("tspan"));
539 ws.appendChild(document.createTextNode('\u00A0'));
540 ws.style.textAnchor = "start";
541
Akronf5dc5102017-05-16 20:32:57 +0200542 var lastRight = 0;
543 for (var node_i in this._tokens) {
544 // Append svg
545 var tspan = text.appendChild(this._c("tspan"));
546 tspan.appendChild(document.createTextNode(this._tokens[node_i]));
Akron3a4a08e2017-05-23 22:34:18 +0200547 tspan.setAttribute("text-anchor", "middle");
548
Akronf5dc5102017-05-16 20:32:57 +0200549 this._tokenElements.push(tspan);
550
551 // Add whitespace!
Akron3a4a08e2017-05-23 22:34:18 +0200552 tspan.setAttribute("dx", this.tokenSep);
Akronf5dc5102017-05-16 20:32:57 +0200553 };
554
Akron6f1302b2017-09-13 12:46:02 +0200555 // Get some global position data that may change on resize
Akron15175132017-09-07 18:12:55 +0200556 var globalBoundingBox = g.getBoundingClientRect();
557 this.offsetLeft = globalBoundingBox.left;
558
Akron6f1302b2017-09-13 12:46:02 +0200559 // The group of arcs
Akron3a4a08e2017-05-23 22:34:18 +0200560 var arcs = g.appendChild(this._c("g"));
Akron65d31082017-09-08 16:23:40 +0200561 this._arcsElement = arcs;
Akron3a4a08e2017-05-23 22:34:18 +0200562 arcs.classList.add("arcs");
Akron15175132017-09-07 18:12:55 +0200563
Akronc9f23022017-09-13 15:43:52 +0200564 var labels = g.appendChild(this._c("g"));
565 this._labelsElement = labels;
566 labels.classList.add("labels");
567
Akron15175132017-09-07 18:12:55 +0200568 // Sort arcs if not sorted yet
Akron6f1302b2017-09-13 12:46:02 +0200569 if (this._sortedArcs === undefined)
Akron3a4a08e2017-05-23 22:34:18 +0200570 this._sortArcs();
Akrond67d45b2017-05-18 21:47:38 +0200571
Akron6f1302b2017-09-13 12:46:02 +0200572 // 1. Draw all anchors
Akrond67d45b2017-05-18 21:47:38 +0200573 var i;
574 for (i in this._sortedAnchors) {
Akron65d31082017-09-08 16:23:40 +0200575 this._drawAnchor(this._sortedAnchors[i]);
Akrond67d45b2017-05-18 21:47:38 +0200576 };
Akron15175132017-09-07 18:12:55 +0200577
Akron6f1302b2017-09-13 12:46:02 +0200578 // 2. Draw all arcs
Akrond67d45b2017-05-18 21:47:38 +0200579 for (i in this._sortedArcs) {
Akron65d31082017-09-08 16:23:40 +0200580 this._drawArc(this._sortedArcs[i]);
Akronf5dc5102017-05-16 20:32:57 +0200581 };
Akron3a4a08e2017-05-23 22:34:18 +0200582
Akron6f1302b2017-09-13 12:46:02 +0200583 // Resize the svg with some reasonable margins
Akron3a4a08e2017-05-23 22:34:18 +0200584 var width = text.getBoundingClientRect().width;
Akronf3d7d8e2017-05-23 22:52:54 +0200585 svg.setAttribute("width", width + 20);
586 svg.setAttribute("height", height + 20);
Akron3a4a08e2017-05-23 22:34:18 +0200587 svg.setAttribute("class", "relTree");
Akronf5dc5102017-05-16 20:32:57 +0200588 }
Akrond67d45b2017-05-18 21:47:38 +0200589 };
590
Akron6f1302b2017-09-13 12:46:02 +0200591 // Sort relations regarding their span
Akrond67d45b2017-05-18 21:47:38 +0200592 function lengthSort (list, inclusive) {
593
594 /*
595 * The "inclusive" flag allows to
596 * modify the behaviour for inclusivity check,
597 * e.g. if identical start or endpoints mean overlap or not.
598 */
599
600 var stack = [];
601
602 // Iterate over all definitions
603 for (var i = 0; i < list.length; i++) {
604 var current = list[i];
605
606 // Check the stack order
607 var overlaps = 0;
Akrond67d45b2017-05-18 21:47:38 +0200608 for (var j = (stack.length - 1); j >= 0; j--) {
609 var check = stack[j];
610
611 // (a..(b..b)..a)
612 if (current.first <= check.first && current.last >= check.last) {
613 overlaps = check.overlaps + 1;
614 break;
615 }
616
617 // (a..(b..a)..b)
618 else if (current.first <= check.first && current.last >= check.first) {
619
620 if (inclusive || (current.first != check.first && current.last != check.first)) {
621 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
622 };
623 }
624
625 // (b..(a..b)..a)
626 else if (current.first <= check.last && current.last >= check.last) {
627
628 if (inclusive || (current.first != check.last && current.last != check.last)) {
629 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
630 };
631 };
632 };
633
634 // Set overlaps
635 current.overlaps = overlaps;
636
637 stack.push(current);
638
639 // Although it is already sorted,
640 // the new item has to be put at the correct place
Akron6f1302b2017-09-13 12:46:02 +0200641 // TODO:
642 // Use something like splice() instead
Akrond67d45b2017-05-18 21:47:38 +0200643 stack.sort(function (a,b) {
644 b.overlaps - a.overlaps
645 });
646 };
647
648 return stack;
649 };
Akronf5dc5102017-05-16 20:32:57 +0200650});