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