blob: afcd1879981c9123fc429879984cdf56f3d7866f [file] [log] [blame]
Akron3ebfd4e2017-11-13 17:56:49 +01001/**
2 * Parse a relational tree and visualize using arcs.
3 *
4 * @author Nils Diewald
5 */
6
7define([], function () {
8 "use strict";
9
Akron0b489ad2018-02-02 16:49:32 +010010 const svgNS = "http://www.w3.org/2000/svg";
11 const _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
12 const d = document;
Akron3ebfd4e2017-11-13 17:56:49 +010013
14 return {
15 create : function (snippet) {
16 return Object.create(this)._init(snippet);
17 },
18
19 // Initialize the state of the object
20 _init : function (snippet) {
21
22 // Predefine some values
23 this._tokens = [];
24 this._arcs = [];
25 this._tokenElements = [];
26 this._y = 0;
Akron430332b2017-11-20 15:36:51 +010027 this._currentInFocus = undefined;
Akron3ebfd4e2017-11-13 17:56:49 +010028
29 // Some configurations
30 this.maxArc = 200; // maximum height of the bezier control point
31 this.overlapDiff = 40; // Difference on overlaps and minimum height for self-refernces
32 this.arcDiff = 15;
33 this.anchorDiff = 8;
34 this.anchorStart = 15;
35 this.tokenSep = 30;
36 this.xPadding = 10;
37 this.yPadding = 5;
38
39 // No snippet to process
40 if (snippet == undefined || snippet == null)
41 return this;
42
43 // Parse the snippet
Akron0b489ad2018-02-02 16:49:32 +010044 var html = d.createElement("div");
Akron3ebfd4e2017-11-13 17:56:49 +010045 html.innerHTML = snippet;
46
47 // Establish temporary parsing memory
48 this.temp = {
49 target : {}, // Remember the map id => pos
50 edges : [], // Remember edge definitions
51 pos : 0 // Keep track of the current token position
52 };
53
54 // Start parsing from root
55 this._parse(0, html.childNodes, undefined);
56
57 // Establish edge list
58 var targetMap = this.temp['target'];
59 var edges = this.temp['edges'];
60
61 // Iterate over edge lists
62 // TODO:
63 // Support spans for anchors!
Akron678c26f2020-10-09 08:52:50 +020064 edges.forEach(function(edge) {
Akron3ebfd4e2017-11-13 17:56:49 +010065
66 // Check the target identifier
67 var targetID = edge.targetID;
68 var target = targetMap[targetID];
69
70 if (target != undefined) {
71
72 // Check if the source is a span anchor
73 /*
74 var start = edge.srcStart;
75 if (start !== edge.srcEnd) {
76 start = [start, edge.srcEnd];
77 };
78 */
79
80 // Add relation
81 var relation = {
82 start : [edge.srcStart, edge.srcEnd],
83 end : target,
84 direction : 'uni',
85 label : edge.label
86 };
87 // console.log(relation);
88 this.addRel(relation);
89 };
Akron678c26f2020-10-09 08:52:50 +020090 }, this);
Akron3ebfd4e2017-11-13 17:56:49 +010091
92 // Reset parsing memory
93 this.temp = {};
94
95 return this;
96 },
97
98 // Parse a node of the tree snippet
99 _parse : function (parent, children, mark) {
100
101 // Iterate over all child nodes
Akronb50964a2020-10-12 11:44:37 +0200102 Array.from(children).forEach(function(c) {
Akron3ebfd4e2017-11-13 17:56:49 +0100103
104 // Element node
105 if (c.nodeType == 1) {
106
Akron63581052018-01-31 17:50:59 +0100107 var xmlid, target, start, end;
Akron3ebfd4e2017-11-13 17:56:49 +0100108
109 // Node is an identifier
110 if (c.hasAttribute('xml:id')) {
111
112 // Remember that pos has this identifier
113 xmlid = c.getAttribute('xml:id');
Akron63581052018-01-31 17:50:59 +0100114 // this.temp['target'][xmlid] =
115 start = this.temp['pos'];
116 end = this.temp['pos'];
Akron3ebfd4e2017-11-13 17:56:49 +0100117 }
118
Akron63581052018-01-31 17:50:59 +0100119 // Node is a link
120 // Stricter: && c.hasAttribute('xlink:show')
Akron3ebfd4e2017-11-13 17:56:49 +0100121 else if (c.hasAttribute('xlink:href')) {
Akron3ebfd4e2017-11-13 17:56:49 +0100122
Akron63581052018-01-31 17:50:59 +0100123 // Node is a continuation
124 if (c.getAttribute('xlink:show') == "other" &&
125 c.hasAttribute('data-action') &&
126 c.getAttribute('data-action') == "join"
127 ) {
128 xmlid = c.getAttribute('xlink:href').replace(/^#/, "");
129 start = this.temp['pos'];
130 end = this.temp['pos'];
Akron3ebfd4e2017-11-13 17:56:49 +0100131
Akron63581052018-01-31 17:50:59 +0100132 // this.temp['target'][xmlid][1] = this.temp['pos'] -1;
133 // console.log("Here");
134 }
135
136 // Node is a relation
137 // Stricter: c.getAttribute('xlink:show') == "none"
138 else {
139 var label;
140
141 // Get target id
142 target = c.getAttribute('xlink:href').replace(/^#/, "");
143
144 if (c.hasAttribute('xlink:title')) {
145 label = this._clean(c.getAttribute('xlink:title'));
146 };
147
148 // Remember the defined edge
149 var edge = {
150 label : label,
151 srcStart : this.temp['pos'],
152 targetID : target
153 };
154
155 // TEMP: Hot-fix for root relations
156 if (!label.match(/--$/) && !label.match(/ROOT$/)) {
157 this.temp['edges'].push(edge);
158 };
159
Akron3ebfd4e2017-11-13 17:56:49 +0100160 };
Akron3ebfd4e2017-11-13 17:56:49 +0100161 };
162
163 // Go on with child nodes
164 if (c.hasChildNodes()) {
165 this._parse(0, c.childNodes, mark);
166 };
167
Akron63581052018-01-31 17:50:59 +0100168
169 // The element is a defined anchor
Akron3ebfd4e2017-11-13 17:56:49 +0100170 if (xmlid !== undefined) {
Akron63581052018-01-31 17:50:59 +0100171
172 // this.temp['target'][xmlid][1] = this.temp['pos'] -1;
173
174 // Element already defined
175 if (this.temp['target'][xmlid] !== undefined) {
176 var newtarget = this.temp['target'][xmlid];
177 end = this.temp['pos'] - 1;
178 newtarget[0] = start < newtarget[0] ? start : newtarget[0];
179 newtarget[1] = end > newtarget[1] ? end : newtarget[1];
180 }
181
182 // Element not yet defined
183 else {
184 end = this.temp['pos'] - 1;
185 this.temp['target'][xmlid] = [start, end];
186 };
Akron3ebfd4e2017-11-13 17:56:49 +0100187
188 /*
189 console.log('Target ' + xmlid + ' spans from ' +
190 this.temp['target'][xmlid][0] +
191 ' to ' +
192 this.temp['target'][xmlid][1]
193 );
194 */
195 xmlid = undefined;
196 }
Akron63581052018-01-31 17:50:59 +0100197
198 // Current element describes an arc
Akron3ebfd4e2017-11-13 17:56:49 +0100199 else if (target !== undefined) {
Akron63581052018-01-31 17:50:59 +0100200
201 // TODO: This does not work yet!
Akron3ebfd4e2017-11-13 17:56:49 +0100202 edge["srcEnd"] = this.temp['pos'] -1;
Akron63581052018-01-31 17:50:59 +0100203 // console.log('Here');
Akron3ebfd4e2017-11-13 17:56:49 +0100204
205 /*
206 console.log('Source spans from ' +
207 edge["srcStart"] +
208 ' to ' +
209 edge["srcEnd"]
210 );
211 */
212 target = undefined;
213 };
214 }
215
216 // Text node
217 else if (c.nodeType == 3) {
218
219 // Check, if there is a non-whitespace token
220 if (c.nodeValue !== undefined) {
221 var str = c.nodeValue.trim();
222 if (str !== undefined && str.length > 0) {
223
224 // Add token to token list
225 this.addToken(str);
226
227 // Move token position
228 this.temp['pos']++;
229 };
230 };
Akron678c26f2020-10-09 08:52:50 +0200231 };
232 }, this);
Akron63581052018-01-31 17:50:59 +0100233
234 // Todo: define edges here!
Akron3ebfd4e2017-11-13 17:56:49 +0100235 },
236
237
238 // Remove foundry and layer for labels
239 _clean : function (title) {
240 return title.replace(_TermRE, "$3");
241 },
242
243
244 // Return the number of leaf nodes
245 // (not necessarily part of a relation).
246 // Consecutive nodes that are not part of any
247 // relation are summarized in one node.
248 size : function () {
249 return this._tokens.length;
250 },
251
252
253 // This is a shorthand for SVG element creation
254 _c : function (tag) {
Akron0b489ad2018-02-02 16:49:32 +0100255 return d.createElementNS(svgNS, tag);
Akron3ebfd4e2017-11-13 17:56:49 +0100256 },
257
258 // Get bounding box - with workaround for text nodes
259 _rect : function (node) {
260 if (node.tagName == "tspan" && !navigator.userAgent.match(/Edge/)) {
Akron0b489ad2018-02-02 16:49:32 +0100261 var range = d.createRange();
Akron3ebfd4e2017-11-13 17:56:49 +0100262 range.selectNode(node);
263 var rect = range.getBoundingClientRect();
264 range.detach();
265 return rect;
266 };
267 return node.getBoundingClientRect();
268 },
269
270 // Returns the center point of the requesting token
271 _tokenPoint : function (node) {
272 var box = this._rect(node);
273 return box.left + (box.width / 2);
274 },
275
276
277 // Draws an anchor
278 _drawAnchor : function (anchor) {
279
280 // Calculate the span of the first and last token, the anchor spans
281 var firstBox = this._rect(this._tokenElements[anchor.first]);
282 var lastBox = this._rect(this._tokenElements[anchor.last]);
283
284 var startPos = firstBox.left - this.offsetLeft;
285 var endPos = lastBox.right - this.offsetLeft;
286
287 var y = this._y + (anchor.overlaps * this.anchorDiff) - this.anchorStart;
288
289 var l = this._c('path');
290 this._arcsElement.appendChild(l);
291 var pathStr = "M " + startPos + "," + y + " L " + endPos + "," + y;
292 l.setAttribute("d", pathStr);
293 l.setAttribute("class", "anchor");
294 anchor.element = l;
295 anchor.y = y;
296 return l;
297 },
298
299
300 // Create an arc with an optional label
301 // Potentially needs a height parameter for stacks
302 _drawArc : function (arc) {
303
304 var startPos, endPos;
305 var startY = this._y;
306 var endY = this._y;
307
308 if (arc.startAnchor !== undefined) {
309 startPos = this._tokenPoint(arc.startAnchor.element);
310 startY = arc.startAnchor.y;
311 }
312 else {
313 startPos = this._tokenPoint(this._tokenElements[arc.first]);
314 };
315
316 if (arc.endAnchor !== undefined) {
317 endPos = this._tokenPoint(arc.endAnchor.element)
318 endY = arc.endAnchor.y;
319 }
320 else {
321 endPos = this._tokenPoint(this._tokenElements[arc.last]);
322 };
323
Akron3ebfd4e2017-11-13 17:56:49 +0100324 startPos -= this.offsetLeft;
325 endPos -= this.offsetLeft;
326
327 // Special treatment for self-references
328 var overlaps = arc.overlaps;
329 if (startPos == endPos) {
330 startPos -= this.overlapDiff / 3;
331 endPos += this.overlapDiff / 3;
332 overlaps += .5;
333 };
334
335 var g = this._c("g");
336 g.setAttribute("class", "arc");
337 var p = g.appendChild(this._c("path"));
338 p.setAttribute('class', 'edge');
339
340 // Attach the new arc before drawing, so computed values are available
341 this._arcsElement.appendChild(g);
342
343 // Create arc
344 var middle = Math.abs(endPos - startPos) / 2;
345
346 // TODO:
347 // take the number of tokens into account!
348 var cHeight = this.arcDiff + (overlaps * this.overlapDiff) + (middle / 2);
349
350 // Respect the maximum height
351 cHeight = cHeight < this.maxArc ? cHeight : this.maxArc;
352
353 var x = Math.min(startPos, endPos);
354
355 //var controlY = (startY + endY - cHeight);
356 var controlY = (endY - cHeight);
357
358 var arcE = "M "+ startPos + "," + startY +
359 " C " + startPos + "," + controlY +
360 " " + endPos + "," + controlY +
361 " " + endPos + "," + endY;
362
363 p.setAttribute("d", arcE);
364
365 if (arc.direction !== undefined) {
366 p.setAttribute("marker-end", "url(#arr)");
367 if (arc.direction === 'bi') {
368 p.setAttribute("marker-start", "url(#arr)");
369 };
370 };
371
372 if (arc.label === undefined)
373 return g;
374
375 /*
376 * Calculate the top point of the arc for labeling using
377 * de Casteljau's algorithm, see e.g.
378 * http://blog.sklambert.com/finding-the-control-points-of-a-bezier-curve/
379 * of course simplified to symmetric arcs ...
380 */
381 // Interpolate one side of the control polygon
382 var middleY = (((startY + controlY) / 2) + controlY) / 2;
383
384 // Create a boxed label
Akron430332b2017-11-20 15:36:51 +0100385 var label = this._c("g");
386 label.setAttribute("class", "label");
387 this._labelsElement.appendChild(label);
388
389 // Set arc reference
390 label.arcRef = g;
Akron3ebfd4e2017-11-13 17:56:49 +0100391
392 var that = this;
Akron430332b2017-11-20 15:36:51 +0100393 label.addEventListener('mouseenter', function () {
394 that.inFocus(this);
Akron3ebfd4e2017-11-13 17:56:49 +0100395 });
396
Akron430332b2017-11-20 15:36:51 +0100397 var labelE = label.appendChild(this._c("text"));
Akron3ebfd4e2017-11-13 17:56:49 +0100398 labelE.setAttribute("x", x + middle);
399 labelE.setAttribute("y", middleY + 3);
400 labelE.setAttribute("text-anchor", "middle");
Akron0b489ad2018-02-02 16:49:32 +0100401 var textNode = d.createTextNode(arc.label);
Akron3ebfd4e2017-11-13 17:56:49 +0100402 labelE.appendChild(textNode);
403
404 var labelBox = labelE.getBBox();
Akronbfe912c2018-07-17 19:30:52 +0200405
406 if (!labelBox)
407 console.log("----");
408
Akron3ebfd4e2017-11-13 17:56:49 +0100409 var textWidth = labelBox.width; // labelE.getComputedTextLength();
410 var textHeight = labelBox.height; // labelE.getComputedTextLength();
411
412 // Add box with padding to left and right
Akron430332b2017-11-20 15:36:51 +0100413 var labelR = label.insertBefore(this._c("rect"), labelE);
Akron3ebfd4e2017-11-13 17:56:49 +0100414 var boxWidth = textWidth + 2 * this.xPadding;
415 labelR.setAttribute("x", x + middle - (boxWidth / 2));
416 labelR.setAttribute("ry", 5);
417 labelR.setAttribute("y", labelBox.y - this.yPadding);
418 labelR.setAttribute("width", boxWidth);
419 labelR.setAttribute("height", textHeight + 2 * this.yPadding);
420 },
421
422 // Get the svg element
423 element : function () {
424 if (this._element !== undefined)
425 return this._element;
426
427 // Create svg
428 var svg = this._c("svg");
429
430 window.addEventListener("resize", function () {
431 // TODO:
432 // Only if text-size changed!
433 // TODO:
434 // This is currently untested
435 this.show();
436 }.bind(this));
437
438 // Define marker arrows
439 var defs = svg.appendChild(this._c("defs"));
440 var marker = defs.appendChild(this._c("marker"));
441 marker.setAttribute("refX", 9);
442 marker.setAttribute("id", "arr");
443 marker.setAttribute("orient", "auto-start-reverse");
444 marker.setAttribute("markerUnits","userSpaceOnUse");
445 var arrow = this._c("path");
446 arrow.setAttribute("transform", "scale(0.8)");
447 arrow.setAttribute("d", "M 0,-5 0,5 10,0 Z");
448 marker.appendChild(arrow);
449
450 this._element = svg;
451 return this._element;
452 },
453
454 // Add a relation with a start, an end,
455 // a direction value and an optional label text
456 addRel : function (rel) {
457 this._arcs.push(rel);
458 return this;
459 },
460
461
462 // Add a token to the list (this will mostly be a word)
463 addToken : function(token) {
464 this._tokens.push(token);
465 return this;
466 },
Akron430332b2017-11-20 15:36:51 +0100467
468
469 // Move label and arc in focus
470 inFocus : function (element) {
471 var cif;
472
473 if (this._currentInFocus) {
474
475 // Already in focus
476 if (this._currentInFocus === element)
477 return;
478
479 cif = this._currentInFocus;
480 cif.classList.remove('infocus');
481 cif.arcRef.classList.remove('infocus');
482 };
483
484 cif = this._currentInFocus = element;
485 this._labelsElement.appendChild(cif);
486 this._arcsElement.appendChild(cif.arcRef);
487 cif.classList.add('infocus');
488 cif.arcRef.classList.add('infocus');
489 },
Akron3ebfd4e2017-11-13 17:56:49 +0100490
491 /*
492 * All arcs need to be sorted before shown,
493 * to avoid nesting.
494 */
495 _sortArcs : function () {
496
497 // TODO:
498 // Keep in mind that the arcs may have long anchors!
499 // 1. Iterate over all arcs
500 // 2. Sort all multi
501 var anchors = {};
502
503 // 1. Sort by length
504 // 2. Tag all spans with the number of overlaps before
505 // a) Iterate over all spans
506 // b) check the latest preceeding overlapping span (lpos)
507 // -> not found: tag with 0
508 // -> found: Add +1 to the level of the (lpos)
509 // c) If the new tag is smaller than the previous element,
510 // reorder
511
512 // Normalize start and end
513 var sortedArcs = this._arcs.map(function (v) {
514
515 // Check for long anchors
516 if (v.start instanceof Array) {
517
518 if (v.start[0] == v.start[1]) {
519 v.start = v.start[0];
520 }
521
522 else {
523
524 var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
525
526 // Calculate signature to avoid multiple anchors
527 var anchorSig = "#" + v.start[0] + "_" + v.start[1];
528 if (v.start[0] > v.start[1]) {
529 anchorSig = "#" + v.start[1] + "_" + v.start[0];
530 };
531
532 // Check if the anchor already exist
533 var anchor = anchors[anchorSig];
534 if (anchor === undefined) {
535 anchor = {
536 "first": v.start[0],
537 "last" : v.start[1],
538 "length" : v.start[1] - v.start[0]
539 };
540 anchors[anchorSig] = anchor;
541 // anchors.push(v.startAnchor);
542 };
543
544 v.startAnchor = anchor;
545
546 // Add to anchors list
547 v.start = middle;
548 };
549 };
550
551 if (v.end instanceof Array) {
552
553 if (v.end[0] == v.end[1]) {
554 v.end = v.end[0];
555 }
556
557 else {
558
559 var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
560
561 // Calculate signature to avoid multiple anchors
562 var anchorSig = "#" + v.end[0] + "_" + v.end[1];
563 if (v.end[0] > v.end[1]) {
564 anchorSig = "#" + v.end[1] + "_" + v.end[0];
565 };
566
567 // Check if the anchor already exist
568 var anchor = anchors[anchorSig];
569 if (anchor === undefined) {
570 anchor = {
571 "first": v.end[0],
572 "last" : v.end[1],
573 "length" : v.end[1] - v.end[0]
574 };
575 anchors[anchorSig] = anchor;
576 // anchors.push(v.startAnchor);
577 };
578
579 v.endAnchor = anchor;
580
581 // Add to anchors list
582 // anchors.push(v.endAnchor);
583 v.end = middle;
584 };
585 };
586
587 v.first = v.start;
588 v.last = v.end;
589
590 // calculate the arch length
591 if (v.start < v.end) {
592 v.length = v.end - v.start;
593 }
594 else {
595 // v.first = v.end;
596 // v.last = v.start;
597 v.length = v.start - v.end;
598 };
599
600 return v;
601 });
602
603 // Sort based on length
604 sortedArcs.sort(function (a, b) {
605 if (a.length < b.length)
606 return -1;
607 else
608 return 1;
609 });
610
611 // Add sorted arcs and anchors
612 this._sortedArcs = lengthSort(sortedArcs, false);
Akron0a785d42020-10-12 17:21:46 +0200613 this._sortedAnchors = lengthSort(Object.values(anchors), true);
Akron3ebfd4e2017-11-13 17:56:49 +0100614 },
615
616 /**
617 * Center the viewport of the canvas
618 * TODO:
619 * This is identical to tree
620 */
621 center : function () {
622 if (this._element === undefined)
623 return;
624
625 var treeDiv = this._element.parentNode;
626
627 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
628 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
629 // Reposition:
630 if (cWidth > treeWidth) {
631 var scrollValue = (cWidth - treeWidth) / 2;
632 treeDiv.scrollLeft = scrollValue;
633 };
634 },
635
636
637 // Show the element
638 show : function () {
639 var svg = this._element;
640 var height = this.maxArc;
641
642 // Delete old group
643 if (svg.getElementsByTagName("g")[0] !== undefined) {
644 var group = svg.getElementsByTagName("g")[0];
645 svg.removeChild(group);
646 this._tokenElements = [];
647 };
648
649 var g = svg.appendChild(this._c("g"));
650
651 // Draw token list
652 var text = g.appendChild(this._c("text"));
653 text.setAttribute('class', 'leaf');
654 text.setAttribute("text-anchor", "start");
655 text.setAttribute("y", height);
656
657 // Calculate the start position
658 this._y = height - (this.anchorStart);
659
660 // Introduce some prepending whitespace (yeah - I know ...)
661 var ws = text.appendChild(this._c("tspan"));
Akron0b489ad2018-02-02 16:49:32 +0100662 ws.appendChild(d.createTextNode('\u00A0'));
Akron3ebfd4e2017-11-13 17:56:49 +0100663 ws.style.textAnchor = "start";
664
665 var lastRight = 0;
Akron678c26f2020-10-09 08:52:50 +0200666 this._tokens.forEach(function(node_i) {
Akron3ebfd4e2017-11-13 17:56:49 +0100667 // Append svg
668 // var x = text.appendChild(this._c("text"));
669 var tspan = text.appendChild(this._c("tspan"));
Akron678c26f2020-10-09 08:52:50 +0200670 tspan.appendChild(d.createTextNode(node_i));
Akron3ebfd4e2017-11-13 17:56:49 +0100671 tspan.setAttribute("text-anchor", "middle");
672
673 this._tokenElements.push(tspan);
674
675 // Add whitespace!
676 tspan.setAttribute("dx", this.tokenSep);
Akron678c26f2020-10-09 08:52:50 +0200677 }, this);
Akron3ebfd4e2017-11-13 17:56:49 +0100678
679 // Get some global position data that may change on resize
680 var globalBoundingBox = this._rect(g);
681 this.offsetLeft = globalBoundingBox.left;
682
683 // The group of arcs
684 var arcs = g.appendChild(this._c("g"));
685 this._arcsElement = arcs;
686 arcs.classList.add("arcs");
687
688 var labels = g.appendChild(this._c("g"));
689 this._labelsElement = labels;
690 labels.classList.add("labels");
691
692 // Sort arcs if not sorted yet
693 if (this._sortedArcs === undefined)
694 this._sortArcs();
695
696 // 1. Draw all anchors
Akron678c26f2020-10-09 08:52:50 +0200697 this._sortedAnchors.forEach(
698 i => this._drawAnchor(i)
699 );
Akron3ebfd4e2017-11-13 17:56:49 +0100700
701 // 2. Draw all arcs
Akron678c26f2020-10-09 08:52:50 +0200702 this._sortedArcs.forEach(
703 i => this._drawArc(i)
704 );
Akron3ebfd4e2017-11-13 17:56:49 +0100705
706 // Resize the svg with some reasonable margins
707 var width = this._rect(text).width;
708 svg.setAttribute("width", width + 20);
709 svg.setAttribute("height", height + 20);
710 svg.setAttribute("class", "relTree");
711 }
712 };
713
714 // Sort relations regarding their span
715 function lengthSort (list, inclusive) {
716
717 /*
718 * The "inclusive" flag allows to
719 * modify the behaviour for inclusivity check,
720 * e.g. if identical start or endpoints mean overlap or not.
721 */
722
723 var stack = [];
724
725 // Iterate over all definitions
Akronb50964a2020-10-12 11:44:37 +0200726 list.forEach(function(current) {
Akron3ebfd4e2017-11-13 17:56:49 +0100727
728 // Check the stack order
729 var overlaps = 0;
730 for (var j = (stack.length - 1); j >= 0; j--) {
731 var check = stack[j];
732
733 // (a..(b..b)..a)
734 if (current.first <= check.first && current.last >= check.last) {
735 overlaps = check.overlaps + 1;
736 break;
737 }
738
739 // (a..(b..a)..b)
740 else if (current.first <= check.first && current.last >= check.first) {
741
742 if (inclusive || (current.first != check.first && current.last != check.first)) {
743 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
744 };
745 }
746
747 // (b..(a..b)..a)
748 else if (current.first <= check.last && current.last >= check.last) {
749
750 if (inclusive || (current.first != check.last && current.last != check.last)) {
751 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
752 };
753 };
754 };
755
756 // Set overlaps
757 current.overlaps = overlaps;
758
759 stack.push(current);
760
761 // Although it is already sorted,
762 // the new item has to be put at the correct place
763 // TODO:
764 // Use something like splice() instead
765 stack.sort(function (a,b) {
766 b.overlaps - a.overlaps
767 });
Akronb50964a2020-10-12 11:44:37 +0200768 });
Akron3ebfd4e2017-11-13 17:56:49 +0100769
770 return stack;
771 };
772});