blob: 34efe04ec5d73a2bd0f57ef19eea728b9a335c8c [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
22 this._tokens = [];
23 this._arcs = []
24 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
71 // Add relation
72 this.addRel({
73 start : edge.src,
74 end : target,
75 direction : 'uni',
76 label : edge.label
77 });
78 };
79 };
80
81 // Reset parsing memory
82 this.temp = {};
83
Akronf5dc5102017-05-16 20:32:57 +020084 return this;
85 },
86
Akron6f1302b2017-09-13 12:46:02 +020087 // Parse a node of the tree snippet
Akron671fdb92017-09-12 18:09:46 +020088 _parse : function (parent, children, mark) {
Akron6f1302b2017-09-13 12:46:02 +020089
90 // Iterate over all child nodes
Akron671fdb92017-09-12 18:09:46 +020091 for (var i in children) {
92 var c = children[i];
93
94 // Element node
95 if (c.nodeType == 1) {
96
97 // Node is an identifier
98 if (c.hasAttribute('xml:id')) {
99
100 // Remember that pos has this identifier
Akron6f1302b2017-09-13 12:46:02 +0200101 // TODO:
102 // Target may be a span!
Akron671fdb92017-09-12 18:09:46 +0200103 this.temp['target'][c.getAttribute('xml:id')] = this.temp['pos'];
104 }
105
106 // Node is a relation
107 else if (c.hasAttribute('xlink:href')) {
Akron6f1302b2017-09-13 12:46:02 +0200108 var label;
Akron671fdb92017-09-12 18:09:46 +0200109
Akron6f1302b2017-09-13 12:46:02 +0200110 // Get target id
111 var target = c.getAttribute('xlink:href').replace(/^#/, "");
Akron671fdb92017-09-12 18:09:46 +0200112
113 if (c.hasAttribute('xlink:title')) {
114 label = this._clean(c.getAttribute('xlink:title'));
115 };
116
117 // Remember the defined edge
Akron6f1302b2017-09-13 12:46:02 +0200118 // TODO:
119 // src may be a span!
Akron671fdb92017-09-12 18:09:46 +0200120 this.temp['edges'].push({
Akron6f1302b2017-09-13 12:46:02 +0200121 label : label,
122 src : this.temp['pos'],
Akron671fdb92017-09-12 18:09:46 +0200123 targetID : target
124 });
125 };
126
Akron6f1302b2017-09-13 12:46:02 +0200127 // Go on with child nodes
Akron671fdb92017-09-12 18:09:46 +0200128 if (c.hasChildNodes()) {
129 this._parse(0, c.childNodes, mark);
130 };
131 }
132
133 // Text node
134 else if (c.nodeType == 3) {
135
136 // Check, if there is a non-whitespace token
137 if (c.nodeValue !== undefined) {
138 var str = c.nodeValue.trim();
139 if (str !== undefined && str.length > 0) {
140
141 // Add token to token list
142 this.addToken(str);
143
144 // Move token position
145 this.temp['pos']++;
146 };
147 };
148 }
149 };
150 },
151
Akron6f1302b2017-09-13 12:46:02 +0200152
Akron671fdb92017-09-12 18:09:46 +0200153 // Remove foundry and layer for labels
154 _clean : function (title) {
155 return title.replace(_TermRE, "$3");
156 },
157
Akron6f1302b2017-09-13 12:46:02 +0200158
159 // Return the number of leaf nodes
160 // (not necessarily part of a relation).
161 // Consecutive nodes that are not part of any
162 // relation are summarized in one node.
Akron671fdb92017-09-12 18:09:46 +0200163 size : function () {
164 return this._tokens.length;
165 },
Akron15175132017-09-07 18:12:55 +0200166
Akron6f1302b2017-09-13 12:46:02 +0200167
Akronf5dc5102017-05-16 20:32:57 +0200168 // This is a shorthand for SVG element creation
169 _c : function (tag) {
170 return document.createElementNS(svgNS, tag);
171 },
172
Akron15175132017-09-07 18:12:55 +0200173
Akronf5dc5102017-05-16 20:32:57 +0200174 // Returns the center point of the requesting token
175 _tokenPoint : function (node) {
176 var box = node.getBoundingClientRect();
177 return box.x + (box.width / 2);
178 },
179
Akron15175132017-09-07 18:12:55 +0200180
181 // Draws an anchor
Akrond67d45b2017-05-18 21:47:38 +0200182 _drawAnchor : function (anchor) {
Akron15175132017-09-07 18:12:55 +0200183
184 // Calculate the span of the first and last token, the anchor spans
Akron3a4a08e2017-05-23 22:34:18 +0200185 var firstBox = this._tokenElements[anchor.first].getBoundingClientRect();
Akron6f1302b2017-09-13 12:46:02 +0200186 var lastBox = this._tokenElements[anchor.last].getBoundingClientRect();
Akrond67d45b2017-05-18 21:47:38 +0200187
Akron15175132017-09-07 18:12:55 +0200188 var startPos = firstBox.left - this.offsetLeft;
Akron6f1302b2017-09-13 12:46:02 +0200189 var endPos = lastBox.right - this.offsetLeft;
Akron15175132017-09-07 18:12:55 +0200190
Akron3a4a08e2017-05-23 22:34:18 +0200191 var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
192
Akrond67d45b2017-05-18 21:47:38 +0200193 var l = this._c('path');
Akron65d31082017-09-08 16:23:40 +0200194 this._arcsElement.appendChild(l);
Akron3a4a08e2017-05-23 22:34:18 +0200195 l.setAttribute("d", "M " + startPos + "," + y + " L " + endPos + "," + y);
Akrond67d45b2017-05-18 21:47:38 +0200196 l.setAttribute("class", "anchor");
197 anchor.element = l;
198 anchor.y = y;
199 return l;
200 },
201
Akron6f1302b2017-09-13 12:46:02 +0200202
203 // Create an arc with an optional label
Akronf5dc5102017-05-16 20:32:57 +0200204 // Potentially needs a height parameter for stacks
Akron63ae00b2017-05-16 22:03:36 +0200205 _drawArc : function (arc) {
Akronf5dc5102017-05-16 20:32:57 +0200206
Akrond67d45b2017-05-18 21:47:38 +0200207 var startPos, endPos;
Akronfee0b622017-09-13 14:46:43 +0200208 var startY = this._y;
209 var endY = this._y;
Akronf5dc5102017-05-16 20:32:57 +0200210
Akrond67d45b2017-05-18 21:47:38 +0200211 if (arc.startAnchor !== undefined) {
Akron3a4a08e2017-05-23 22:34:18 +0200212 startPos = this._tokenPoint(arc.startAnchor.element);
Akrond67d45b2017-05-18 21:47:38 +0200213 startY = arc.startAnchor.y;
214 }
215 else {
216 startPos = this._tokenPoint(this._tokenElements[arc.first]);
217 };
218
219 if (arc.endAnchor !== undefined) {
220 endPos = this._tokenPoint(arc.endAnchor.element)
221 endY = arc.endAnchor.y;
222 }
223 else {
224 endPos = this._tokenPoint(this._tokenElements[arc.last]);
225 };
226
Akron15175132017-09-07 18:12:55 +0200227 startPos -= this.offsetLeft;
228 endPos -= this.offsetLeft;
229
Akronfee0b622017-09-13 14:46:43 +0200230 // Special treatment for self-references
231 var overlaps = arc.overlaps;
232 if (startPos == endPos) {
233 startPos -= this.overlapDiff / 3;
Akronc9f23022017-09-13 15:43:52 +0200234 endPos += this.overlapDiff / 3;
Akronfee0b622017-09-13 14:46:43 +0200235 overlaps += .5;
236 };
237
Akronf5dc5102017-05-16 20:32:57 +0200238 var g = this._c("g");
Akron65d31082017-09-08 16:23:40 +0200239 g.setAttribute("class", "arc");
Akronf5dc5102017-05-16 20:32:57 +0200240 var p = g.appendChild(this._c("path"));
Akron15175132017-09-07 18:12:55 +0200241 p.setAttribute('class', 'edge');
Akron65d31082017-09-08 16:23:40 +0200242
243 // Attach the new arc before drawing, so computed values are available
244 this._arcsElement.appendChild(g);
Akronf5dc5102017-05-16 20:32:57 +0200245
246 // Create arc
247 var middle = Math.abs(endPos - startPos) / 2;
248
Akronfee0b622017-09-13 14:46:43 +0200249 // TODO:
250 // take the number of tokens into account!
251 var cHeight = this.arcDiff + (overlaps * this.overlapDiff) + (middle / 2);
Akronc5b5f742017-05-23 16:04:35 +0200252
253 // Respect the maximum height
254 cHeight = cHeight < this.maxArc ? cHeight : this.maxArc;
Akronf5dc5102017-05-16 20:32:57 +0200255
256 var x = Math.min(startPos, endPos);
Akronc5b5f742017-05-23 16:04:35 +0200257
Akron3a4a08e2017-05-23 22:34:18 +0200258 //var controlY = (startY + endY - cHeight);
259 var controlY = (endY - cHeight);
Akronf5dc5102017-05-16 20:32:57 +0200260
Akron3a4a08e2017-05-23 22:34:18 +0200261 var arcE = "M "+ startPos + "," + startY +
262 " C " + startPos + "," + controlY +
263 " " + endPos + "," + controlY +
264 " " + endPos + "," + endY;
Akrond67d45b2017-05-18 21:47:38 +0200265
Akron63ae00b2017-05-16 22:03:36 +0200266 p.setAttribute("d", arcE);
Akronf5dc5102017-05-16 20:32:57 +0200267
Akron3a4a08e2017-05-23 22:34:18 +0200268 if (arc.direction !== undefined) {
269 p.setAttribute("marker-end", "url(#arr)");
270 if (arc.direction === 'bi') {
271 p.setAttribute("marker-start", "url(#arr)");
272 };
273 };
274
Akron6f1302b2017-09-13 12:46:02 +0200275 if (arc.label === undefined)
276 return g;
277
Akronc5b5f742017-05-23 16:04:35 +0200278 /*
279 * Calculate the top point of the arc for labeling using
280 * de Casteljau's algorithm, see e.g.
281 * http://blog.sklambert.com/finding-the-control-points-of-a-bezier-curve/
282 * of course simplified to symmetric arcs ...
283 */
284 // Interpolate one side of the control polygon
Akronc5b5f742017-05-23 16:04:35 +0200285 var middleY = (((startY + controlY) / 2) + controlY) / 2;
286
Akron6f1302b2017-09-13 12:46:02 +0200287 // Create a boxed label
Akronc9f23022017-09-13 15:43:52 +0200288 g = this._c("g");
289 g.setAttribute("class", "label");
290 this._labelsElement.appendChild(g);
291
292 var that = this;
293 g.addEventListener('mouseenter', function () {
294 that._labelsElement.appendChild(this);
295 });
296
Akron6f1302b2017-09-13 12:46:02 +0200297 var labelE = g.appendChild(this._c("text"));
298 labelE.setAttribute("x", x + middle);
299 labelE.setAttribute("y", middleY + 3);
300 labelE.setAttribute("text-anchor", "middle");
301 var textNode = document.createTextNode(arc.label);
302 labelE.appendChild(textNode);
Akronc5b5f742017-05-23 16:04:35 +0200303
Akron6f1302b2017-09-13 12:46:02 +0200304 var labelBox = labelE.getBBox();
305 var textWidth = labelBox.width; // labelE.getComputedTextLength();
306 var textHeight = labelBox.height; // labelE.getComputedTextLength();
Akron1dc87902017-05-29 16:04:56 +0200307
Akron6f1302b2017-09-13 12:46:02 +0200308 // Add box with padding to left and right
309 var labelR = g.insertBefore(this._c("rect"), labelE);
310 var boxWidth = textWidth + 2 * this.xPadding;
311 labelR.setAttribute("x", x + middle - (boxWidth / 2));
312 labelR.setAttribute("ry", 5);
313 labelR.setAttribute("y", labelBox.y - this.yPadding);
314 labelR.setAttribute("width", boxWidth);
315 labelR.setAttribute("height", textHeight + 2 * this.yPadding);
Akronf5dc5102017-05-16 20:32:57 +0200316 },
317
Akron6f1302b2017-09-13 12:46:02 +0200318 // Get the svg element
Akronf5dc5102017-05-16 20:32:57 +0200319 element : function () {
320 if (this._element !== undefined)
321 return this._element;
322
323 // Create svg
324 var svg = this._c("svg");
Akron3a4a08e2017-05-23 22:34:18 +0200325
326 window.addEventListener("resize", function () {
Akron6f1302b2017-09-13 12:46:02 +0200327 // TODO:
328 // Only if text-size changed!
329 // TODO:
330 // This is currently untested
Akron3a4a08e2017-05-23 22:34:18 +0200331 this.show();
332 }.bind(this));
Akron6f1302b2017-09-13 12:46:02 +0200333
334 // Define marker arrows
Akron3a4a08e2017-05-23 22:34:18 +0200335 var defs = svg.appendChild(this._c("defs"));
336 var marker = defs.appendChild(this._c("marker"));
337 marker.setAttribute("refX", 9);
338 marker.setAttribute("id", "arr");
339 marker.setAttribute("orient", "auto-start-reverse");
340 marker.setAttribute("markerUnits","userSpaceOnUse");
Akron3a4a08e2017-05-23 22:34:18 +0200341 var arrow = this._c("path");
342 arrow.setAttribute("transform", "scale(0.8)");
343 arrow.setAttribute("d", "M 0,-5 0,5 10,0 Z");
344 marker.appendChild(arrow);
345
Akronf5dc5102017-05-16 20:32:57 +0200346 this._element = svg;
347 return this._element;
348 },
349
350 // Add a relation with a start, an end,
Akron6f1302b2017-09-13 12:46:02 +0200351 // a direction value and an optional label text
Akronc5b5f742017-05-23 16:04:35 +0200352 addRel : function (rel) {
353 this._arcs.push(rel);
354 return this;
Akronf5dc5102017-05-16 20:32:57 +0200355 },
356
Akronc5b5f742017-05-23 16:04:35 +0200357
Akron6f1302b2017-09-13 12:46:02 +0200358 // Add a token to the list (this will mostly be a word)
Akronc5b5f742017-05-23 16:04:35 +0200359 addToken : function(token) {
360 this._tokens.push(token);
361 return this;
362 },
363
Akronf5dc5102017-05-16 20:32:57 +0200364 /*
365 * All arcs need to be sorted before shown,
366 * to avoid nesting.
367 */
368 _sortArcs : function () {
369
Akrond67d45b2017-05-18 21:47:38 +0200370 // TODO:
371 // Keep in mind that the arcs may have long anchors!
372 // 1. Iterate over all arcs
373 // 2. Sort all multi
374 var anchors = [];
375
Akronf5dc5102017-05-16 20:32:57 +0200376 // 1. Sort by length
377 // 2. Tag all spans with the number of overlaps before
378 // a) Iterate over all spans
379 // b) check the latest preceeding overlapping span (lpos)
380 // -> not found: tag with 0
381 // -> found: Add +1 to the level of the (lpos)
382 // c) If the new tag is smaller than the previous element,
383 // reorder
Akron63ae00b2017-05-16 22:03:36 +0200384
385 // Normalize start and end
386 var sortedArcs = this._arcs.map(function (v) {
Akrond67d45b2017-05-18 21:47:38 +0200387
388 // Check for long anchors
389 if (v.start instanceof Array) {
390 var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
391
392 v.startAnchor = {
Akron3a4a08e2017-05-23 22:34:18 +0200393 "first": v.start[0],
394 "last" : v.start[1],
Akrond67d45b2017-05-18 21:47:38 +0200395 "length" : v.start[1] - v.start[0]
396 };
397
398 // Add to anchors list
399 anchors.push(v.startAnchor);
400 v.start = middle;
401 };
402
403 if (v.end instanceof Array) {
404 var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
405 v.endAnchor = {
Akron3a4a08e2017-05-23 22:34:18 +0200406 "first": v.end[0],
407 "last" : v.end[1],
Akrond67d45b2017-05-18 21:47:38 +0200408 "length" : v.end[1] - v.end[0]
409 };
410
411 // Add to anchors list
412 anchors.push(v.endAnchor);
413 v.end = middle;
414 };
415
416 // calculate the arch length
Akron63ae00b2017-05-16 22:03:36 +0200417 if (v.start < v.end) {
418 v.first = v.start;
419 v.last = v.end;
420 v.length = v.end - v.start;
421 }
422 else {
423 v.first = v.end;
424 v.last = v.start;
425 v.length = v.start - v.end;
426 };
427 return v;
428 });
429
430 // Sort based on length
431 sortedArcs.sort(function (a, b) {
432 if (a.length < b.length)
433 return -1;
434 else
435 return 1;
436 });
437
Akron3a4a08e2017-05-23 22:34:18 +0200438 this._sortedArcs = lengthSort(sortedArcs, false);
Akrond67d45b2017-05-18 21:47:38 +0200439 this._sortedAnchors = lengthSort(anchors, true);
Akronf5dc5102017-05-16 20:32:57 +0200440 },
Akron6f1302b2017-09-13 12:46:02 +0200441
442
443 // Show the element
Akronf5dc5102017-05-16 20:32:57 +0200444 show : function () {
445 var svg = this._element;
Akron3a4a08e2017-05-23 22:34:18 +0200446 var height = this.maxArc;
447
Akron3a4a08e2017-05-23 22:34:18 +0200448 // Delete old group
449 if (svg.getElementsByTagName("g")[0] !== undefined) {
450 var group = svg.getElementsByTagName("g")[0];
451 svg.removeChild(group);
452 this._tokenElements = [];
453 };
454
455 var g = svg.appendChild(this._c("g"));
Akronf5dc5102017-05-16 20:32:57 +0200456
Akron6f1302b2017-09-13 12:46:02 +0200457 // Draw token list
Akron3a4a08e2017-05-23 22:34:18 +0200458 var text = g.appendChild(this._c("text"));
Akron3d204282017-09-07 18:24:18 +0200459 text.setAttribute('class', 'leaf');
Akron3a4a08e2017-05-23 22:34:18 +0200460 text.setAttribute("text-anchor", "start");
461 text.setAttribute("y", height);
Akronf5dc5102017-05-16 20:32:57 +0200462
Akron6f1302b2017-09-13 12:46:02 +0200463 // Calculate the start position
Akron3a4a08e2017-05-23 22:34:18 +0200464 this._y = height - (this.anchorStart);
465
Akron6f1302b2017-09-13 12:46:02 +0200466 // Introduce some prepending whitespace (yeah - I know ...)
Akron3a4a08e2017-05-23 22:34:18 +0200467 var ws = text.appendChild(this._c("tspan"));
468 ws.appendChild(document.createTextNode('\u00A0'));
469 ws.style.textAnchor = "start";
470
Akronf5dc5102017-05-16 20:32:57 +0200471 var lastRight = 0;
472 for (var node_i in this._tokens) {
473 // Append svg
474 var tspan = text.appendChild(this._c("tspan"));
475 tspan.appendChild(document.createTextNode(this._tokens[node_i]));
Akron3a4a08e2017-05-23 22:34:18 +0200476 tspan.setAttribute("text-anchor", "middle");
477
Akronf5dc5102017-05-16 20:32:57 +0200478 this._tokenElements.push(tspan);
479
480 // Add whitespace!
Akron3a4a08e2017-05-23 22:34:18 +0200481 tspan.setAttribute("dx", this.tokenSep);
Akronf5dc5102017-05-16 20:32:57 +0200482 };
483
Akron6f1302b2017-09-13 12:46:02 +0200484 // Get some global position data that may change on resize
Akron15175132017-09-07 18:12:55 +0200485 var globalBoundingBox = g.getBoundingClientRect();
486 this.offsetLeft = globalBoundingBox.left;
487
Akron6f1302b2017-09-13 12:46:02 +0200488 // The group of arcs
Akron3a4a08e2017-05-23 22:34:18 +0200489 var arcs = g.appendChild(this._c("g"));
Akron65d31082017-09-08 16:23:40 +0200490 this._arcsElement = arcs;
Akron3a4a08e2017-05-23 22:34:18 +0200491 arcs.classList.add("arcs");
Akron15175132017-09-07 18:12:55 +0200492
Akronc9f23022017-09-13 15:43:52 +0200493 var labels = g.appendChild(this._c("g"));
494 this._labelsElement = labels;
495 labels.classList.add("labels");
496
Akron15175132017-09-07 18:12:55 +0200497 // Sort arcs if not sorted yet
Akron6f1302b2017-09-13 12:46:02 +0200498 if (this._sortedArcs === undefined)
Akron3a4a08e2017-05-23 22:34:18 +0200499 this._sortArcs();
Akrond67d45b2017-05-18 21:47:38 +0200500
Akron6f1302b2017-09-13 12:46:02 +0200501 // 1. Draw all anchors
Akrond67d45b2017-05-18 21:47:38 +0200502 var i;
503 for (i in this._sortedAnchors) {
Akron65d31082017-09-08 16:23:40 +0200504 this._drawAnchor(this._sortedAnchors[i]);
Akrond67d45b2017-05-18 21:47:38 +0200505 };
Akron15175132017-09-07 18:12:55 +0200506
Akron6f1302b2017-09-13 12:46:02 +0200507 // 2. Draw all arcs
Akrond67d45b2017-05-18 21:47:38 +0200508 for (i in this._sortedArcs) {
Akron65d31082017-09-08 16:23:40 +0200509 this._drawArc(this._sortedArcs[i]);
Akronf5dc5102017-05-16 20:32:57 +0200510 };
Akron3a4a08e2017-05-23 22:34:18 +0200511
Akron6f1302b2017-09-13 12:46:02 +0200512 // Resize the svg with some reasonable margins
Akron3a4a08e2017-05-23 22:34:18 +0200513 var width = text.getBoundingClientRect().width;
Akronf3d7d8e2017-05-23 22:52:54 +0200514 svg.setAttribute("width", width + 20);
515 svg.setAttribute("height", height + 20);
Akron3a4a08e2017-05-23 22:34:18 +0200516 svg.setAttribute("class", "relTree");
Akronf5dc5102017-05-16 20:32:57 +0200517 }
Akrond67d45b2017-05-18 21:47:38 +0200518 };
519
Akron6f1302b2017-09-13 12:46:02 +0200520 // Sort relations regarding their span
Akrond67d45b2017-05-18 21:47:38 +0200521 function lengthSort (list, inclusive) {
522
523 /*
524 * The "inclusive" flag allows to
525 * modify the behaviour for inclusivity check,
526 * e.g. if identical start or endpoints mean overlap or not.
527 */
528
529 var stack = [];
530
531 // Iterate over all definitions
532 for (var i = 0; i < list.length; i++) {
533 var current = list[i];
534
535 // Check the stack order
536 var overlaps = 0;
Akrond67d45b2017-05-18 21:47:38 +0200537 for (var j = (stack.length - 1); j >= 0; j--) {
538 var check = stack[j];
539
540 // (a..(b..b)..a)
541 if (current.first <= check.first && current.last >= check.last) {
542 overlaps = check.overlaps + 1;
543 break;
544 }
545
546 // (a..(b..a)..b)
547 else if (current.first <= check.first && current.last >= check.first) {
548
549 if (inclusive || (current.first != check.first && current.last != check.first)) {
550 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
551 };
552 }
553
554 // (b..(a..b)..a)
555 else if (current.first <= check.last && current.last >= check.last) {
556
557 if (inclusive || (current.first != check.last && current.last != check.last)) {
558 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
559 };
560 };
561 };
562
563 // Set overlaps
564 current.overlaps = overlaps;
565
566 stack.push(current);
567
568 // Although it is already sorted,
569 // the new item has to be put at the correct place
Akron6f1302b2017-09-13 12:46:02 +0200570 // TODO:
571 // Use something like splice() instead
Akrond67d45b2017-05-18 21:47:38 +0200572 stack.sort(function (a,b) {
573 b.overlaps - a.overlaps
574 });
575 };
576
577 return stack;
578 };
Akronf5dc5102017-05-16 20:32:57 +0200579});