blob: 02d6c8bf8d6e9d12653cfb7f8c36ada6960c81d2 [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 = [];
Akron2c1d9ff2017-09-15 13:57:33 +020023 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
408 var anchors = [];
409
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
426 v.startAnchor = {
Akron3a4a08e2017-05-23 22:34:18 +0200427 "first": v.start[0],
428 "last" : v.start[1],
Akrond67d45b2017-05-18 21:47:38 +0200429 "length" : v.start[1] - v.start[0]
430 };
431
432 // Add to anchors list
433 anchors.push(v.startAnchor);
434 v.start = middle;
435 };
436
437 if (v.end instanceof Array) {
438 var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
439 v.endAnchor = {
Akron3a4a08e2017-05-23 22:34:18 +0200440 "first": v.end[0],
441 "last" : v.end[1],
Akrond67d45b2017-05-18 21:47:38 +0200442 "length" : v.end[1] - v.end[0]
443 };
444
445 // Add to anchors list
446 anchors.push(v.endAnchor);
447 v.end = middle;
448 };
449
Akron2c1d9ff2017-09-15 13:57:33 +0200450 v.first = v.start;
451 v.last = v.end;
452
Akrond67d45b2017-05-18 21:47:38 +0200453 // calculate the arch length
Akron63ae00b2017-05-16 22:03:36 +0200454 if (v.start < v.end) {
Akron63ae00b2017-05-16 22:03:36 +0200455 v.length = v.end - v.start;
456 }
457 else {
Akron2c1d9ff2017-09-15 13:57:33 +0200458 // v.first = v.end;
459 // v.last = v.start;
Akron63ae00b2017-05-16 22:03:36 +0200460 v.length = v.start - v.end;
461 };
Akron2c1d9ff2017-09-15 13:57:33 +0200462
463 if (v.label === "OBJA") {
464 console.log(v);
465 };
466
467 // console.log(v);
Akron63ae00b2017-05-16 22:03:36 +0200468 return v;
469 });
470
471 // Sort based on length
472 sortedArcs.sort(function (a, b) {
473 if (a.length < b.length)
474 return -1;
475 else
476 return 1;
477 });
478
Akron2c1d9ff2017-09-15 13:57:33 +0200479 // Add sorted arcs and anchors
Akron3a4a08e2017-05-23 22:34:18 +0200480 this._sortedArcs = lengthSort(sortedArcs, false);
Akrond67d45b2017-05-18 21:47:38 +0200481 this._sortedAnchors = lengthSort(anchors, true);
Akronf5dc5102017-05-16 20:32:57 +0200482 },
Akron6f1302b2017-09-13 12:46:02 +0200483
484
485 // Show the element
Akronf5dc5102017-05-16 20:32:57 +0200486 show : function () {
487 var svg = this._element;
Akron3a4a08e2017-05-23 22:34:18 +0200488 var height = this.maxArc;
489
Akron3a4a08e2017-05-23 22:34:18 +0200490 // Delete old group
491 if (svg.getElementsByTagName("g")[0] !== undefined) {
492 var group = svg.getElementsByTagName("g")[0];
493 svg.removeChild(group);
494 this._tokenElements = [];
495 };
496
497 var g = svg.appendChild(this._c("g"));
Akronf5dc5102017-05-16 20:32:57 +0200498
Akron6f1302b2017-09-13 12:46:02 +0200499 // Draw token list
Akron3a4a08e2017-05-23 22:34:18 +0200500 var text = g.appendChild(this._c("text"));
Akron3d204282017-09-07 18:24:18 +0200501 text.setAttribute('class', 'leaf');
Akron3a4a08e2017-05-23 22:34:18 +0200502 text.setAttribute("text-anchor", "start");
503 text.setAttribute("y", height);
Akronf5dc5102017-05-16 20:32:57 +0200504
Akron6f1302b2017-09-13 12:46:02 +0200505 // Calculate the start position
Akron3a4a08e2017-05-23 22:34:18 +0200506 this._y = height - (this.anchorStart);
507
Akron6f1302b2017-09-13 12:46:02 +0200508 // Introduce some prepending whitespace (yeah - I know ...)
Akron3a4a08e2017-05-23 22:34:18 +0200509 var ws = text.appendChild(this._c("tspan"));
510 ws.appendChild(document.createTextNode('\u00A0'));
511 ws.style.textAnchor = "start";
512
Akronf5dc5102017-05-16 20:32:57 +0200513 var lastRight = 0;
514 for (var node_i in this._tokens) {
515 // Append svg
516 var tspan = text.appendChild(this._c("tspan"));
517 tspan.appendChild(document.createTextNode(this._tokens[node_i]));
Akron3a4a08e2017-05-23 22:34:18 +0200518 tspan.setAttribute("text-anchor", "middle");
519
Akronf5dc5102017-05-16 20:32:57 +0200520 this._tokenElements.push(tspan);
521
522 // Add whitespace!
Akron3a4a08e2017-05-23 22:34:18 +0200523 tspan.setAttribute("dx", this.tokenSep);
Akronf5dc5102017-05-16 20:32:57 +0200524 };
525
Akron6f1302b2017-09-13 12:46:02 +0200526 // Get some global position data that may change on resize
Akron15175132017-09-07 18:12:55 +0200527 var globalBoundingBox = g.getBoundingClientRect();
528 this.offsetLeft = globalBoundingBox.left;
529
Akron6f1302b2017-09-13 12:46:02 +0200530 // The group of arcs
Akron3a4a08e2017-05-23 22:34:18 +0200531 var arcs = g.appendChild(this._c("g"));
Akron65d31082017-09-08 16:23:40 +0200532 this._arcsElement = arcs;
Akron3a4a08e2017-05-23 22:34:18 +0200533 arcs.classList.add("arcs");
Akron15175132017-09-07 18:12:55 +0200534
Akronc9f23022017-09-13 15:43:52 +0200535 var labels = g.appendChild(this._c("g"));
536 this._labelsElement = labels;
537 labels.classList.add("labels");
538
Akron15175132017-09-07 18:12:55 +0200539 // Sort arcs if not sorted yet
Akron6f1302b2017-09-13 12:46:02 +0200540 if (this._sortedArcs === undefined)
Akron3a4a08e2017-05-23 22:34:18 +0200541 this._sortArcs();
Akrond67d45b2017-05-18 21:47:38 +0200542
Akron6f1302b2017-09-13 12:46:02 +0200543 // 1. Draw all anchors
Akrond67d45b2017-05-18 21:47:38 +0200544 var i;
545 for (i in this._sortedAnchors) {
Akron65d31082017-09-08 16:23:40 +0200546 this._drawAnchor(this._sortedAnchors[i]);
Akrond67d45b2017-05-18 21:47:38 +0200547 };
Akron15175132017-09-07 18:12:55 +0200548
Akron6f1302b2017-09-13 12:46:02 +0200549 // 2. Draw all arcs
Akrond67d45b2017-05-18 21:47:38 +0200550 for (i in this._sortedArcs) {
Akron65d31082017-09-08 16:23:40 +0200551 this._drawArc(this._sortedArcs[i]);
Akronf5dc5102017-05-16 20:32:57 +0200552 };
Akron3a4a08e2017-05-23 22:34:18 +0200553
Akron6f1302b2017-09-13 12:46:02 +0200554 // Resize the svg with some reasonable margins
Akron3a4a08e2017-05-23 22:34:18 +0200555 var width = text.getBoundingClientRect().width;
Akronf3d7d8e2017-05-23 22:52:54 +0200556 svg.setAttribute("width", width + 20);
557 svg.setAttribute("height", height + 20);
Akron3a4a08e2017-05-23 22:34:18 +0200558 svg.setAttribute("class", "relTree");
Akronf5dc5102017-05-16 20:32:57 +0200559 }
Akrond67d45b2017-05-18 21:47:38 +0200560 };
561
Akron6f1302b2017-09-13 12:46:02 +0200562 // Sort relations regarding their span
Akrond67d45b2017-05-18 21:47:38 +0200563 function lengthSort (list, inclusive) {
564
565 /*
566 * The "inclusive" flag allows to
567 * modify the behaviour for inclusivity check,
568 * e.g. if identical start or endpoints mean overlap or not.
569 */
570
571 var stack = [];
572
573 // Iterate over all definitions
574 for (var i = 0; i < list.length; i++) {
575 var current = list[i];
576
577 // Check the stack order
578 var overlaps = 0;
Akrond67d45b2017-05-18 21:47:38 +0200579 for (var j = (stack.length - 1); j >= 0; j--) {
580 var check = stack[j];
581
582 // (a..(b..b)..a)
583 if (current.first <= check.first && current.last >= check.last) {
584 overlaps = check.overlaps + 1;
585 break;
586 }
587
588 // (a..(b..a)..b)
589 else if (current.first <= check.first && current.last >= check.first) {
590
591 if (inclusive || (current.first != check.first && current.last != check.first)) {
592 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
593 };
594 }
595
596 // (b..(a..b)..a)
597 else if (current.first <= check.last && current.last >= check.last) {
598
599 if (inclusive || (current.first != check.last && current.last != check.last)) {
600 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
601 };
602 };
603 };
604
605 // Set overlaps
606 current.overlaps = overlaps;
607
608 stack.push(current);
609
610 // Although it is already sorted,
611 // the new item has to be put at the correct place
Akron6f1302b2017-09-13 12:46:02 +0200612 // TODO:
613 // Use something like splice() instead
Akrond67d45b2017-05-18 21:47:38 +0200614 stack.sort(function (a,b) {
615 b.overlaps - a.overlaps
616 });
617 };
618
619 return stack;
620 };
Akronf5dc5102017-05-16 20:32:57 +0200621});