blob: b1809a044d851614b0b48f0d6cdf93df4b9d85ec [file] [log] [blame]
Akronf5dc5102017-05-16 20:32:57 +02001define(function () {
2 "use strict";
3
4 var svgNS = "http://www.w3.org/2000/svg";
5
6 return {
7 create : function (snippet) {
8 var obj = Object.create(this)._init(snippet);
Akrond67d45b2017-05-18 21:47:38 +02009 obj._tokens = ["Der", "alte", "Mann", "ging", "über", "die", "breite", "nasse", "Straße"];
Akronf5dc5102017-05-16 20:32:57 +020010 obj._tokenElements = [];
11 obj._arcs = [
Akrond67d45b2017-05-18 21:47:38 +020012
13
14 /*
15 * Start and end may be spans, i.e. arrays
16 */
Akron63ae00b2017-05-16 22:03:36 +020017 { start: 0, end: 1, label: "a" },
18 { start: 0, end: 1, label: "b" },
19 { start: 1, end: 2, label: "c" },
20 { start: 0, end: 2, label: "d" },
Akrond67d45b2017-05-18 21:47:38 +020021 { start: [2,4], end: 5, label: "e" },
22 { start: 4, end: [6,8], label: "f" },
23 { start: [5,6], end: 7, label: "g" },
Akronf5dc5102017-05-16 20:32:57 +020024 ]
25 obj.maxArc = 200; // maximum height of the bezier control point
26 return obj;
27 },
28
29 _init : function (snippet) {
30 /*
31 var html = document.createElement("div");
32 html.innerHTML = snippet;
33 */
34 return this;
35 },
36
37 // This is a shorthand for SVG element creation
38 _c : function (tag) {
39 return document.createElementNS(svgNS, tag);
40 },
41
42 // Returns the center point of the requesting token
43 _tokenPoint : function (node) {
44 var box = node.getBoundingClientRect();
45 return box.x + (box.width / 2);
46 },
47
Akrond67d45b2017-05-18 21:47:38 +020048 _drawAnchor : function (anchor) {
49 var startPos = this._tokenElements[anchor.first].getBoundingClientRect().left;
50 var endPos = this._tokenElements[anchor.last].getBoundingClientRect().right;
51
52 var y = (anchor.overlaps * -5) - 10;
53 var l = this._c('path');
54 l.setAttribute("d", "M " + startPos + " " + y + " L " + endPos + " " + y);
55 l.setAttribute("class", "anchor");
56 anchor.element = l;
57 anchor.y = y;
58 return l;
59 },
60
Akronf5dc5102017-05-16 20:32:57 +020061 // Create an arc with a label
62 // Potentially needs a height parameter for stacks
Akron63ae00b2017-05-16 22:03:36 +020063 _drawArc : function (arc) {
Akronf5dc5102017-05-16 20:32:57 +020064
Akrond67d45b2017-05-18 21:47:38 +020065 var startPos, endPos;
66 var startY = 0, endY = 0;
Akronf5dc5102017-05-16 20:32:57 +020067
Akrond67d45b2017-05-18 21:47:38 +020068 if (arc.startAnchor !== undefined) {
69 startPos = this._tokenPoint(arc.startAnchor.element)
70 startY = arc.startAnchor.y;
71 }
72 else {
73 startPos = this._tokenPoint(this._tokenElements[arc.first]);
74 };
75
76 if (arc.endAnchor !== undefined) {
77 endPos = this._tokenPoint(arc.endAnchor.element)
78 endY = arc.endAnchor.y;
79 }
80 else {
81 endPos = this._tokenPoint(this._tokenElements[arc.last]);
82 };
83
Akronf5dc5102017-05-16 20:32:57 +020084 var g = this._c("g");
85 var p = g.appendChild(this._c("path"));
86
87 // Create arc
88 var middle = Math.abs(endPos - startPos) / 2;
89
Akron63ae00b2017-05-16 22:03:36 +020090 // var cHeight = middle < this.maxArc ? middle : this.maxArc;
91 // TODO: take the number of tokens into account!
92 var cHeight = 10 + arc.overlaps * 12 + (middle / 2);
Akronf5dc5102017-05-16 20:32:57 +020093
94 var x = Math.min(startPos, endPos);
95
Akrond67d45b2017-05-18 21:47:38 +020096 var arcE = "M "+ startPos + " " + startY +
97 " C " + startPos + " " + (startY + endY - cHeight) +
98 " " + endPos + " " + (startY + endY - cHeight) +
99 " " + endPos + " " + endY;
100
Akron63ae00b2017-05-16 22:03:36 +0200101 p.setAttribute("d", arcE);
Akronf5dc5102017-05-16 20:32:57 +0200102
Akron63ae00b2017-05-16 22:03:36 +0200103 if (arc.label !== undefined) {
Akronf5dc5102017-05-16 20:32:57 +0200104 var labelE = g.appendChild(this._c("text"));
105 labelE.setAttribute("x", x + middle);
Akron63ae00b2017-05-16 22:03:36 +0200106 labelE.setAttribute("y", -1 * cHeight);
Akronf5dc5102017-05-16 20:32:57 +0200107 labelE.setAttribute("text-anchor", "middle");
Akron63ae00b2017-05-16 22:03:36 +0200108 labelE.appendChild(document.createTextNode(arc.label));
Akronf5dc5102017-05-16 20:32:57 +0200109 };
110
111 return g;
112 },
113
114 element : function () {
115 if (this._element !== undefined)
116 return this._element;
117
118 // Create svg
119 var svg = this._c("svg");
120 svg.setAttribute("width", 700);
121 svg.setAttribute("height", 300);
122 this._element = svg;
123 return this._element;
124 },
125
126 // Add a relation with a start, an end,
127 // a direction value and a label text
Akron63ae00b2017-05-16 22:03:36 +0200128 addArc : function (arc) {
Akronf5dc5102017-05-16 20:32:57 +0200129 },
130
131 /*
132 * All arcs need to be sorted before shown,
133 * to avoid nesting.
134 */
135 _sortArcs : function () {
136
Akrond67d45b2017-05-18 21:47:38 +0200137
138 // TODO:
139 // Keep in mind that the arcs may have long anchors!
140 // 1. Iterate over all arcs
141 // 2. Sort all multi
142 var anchors = [];
143
Akronf5dc5102017-05-16 20:32:57 +0200144 // 1. Sort by length
145 // 2. Tag all spans with the number of overlaps before
146 // a) Iterate over all spans
147 // b) check the latest preceeding overlapping span (lpos)
148 // -> not found: tag with 0
149 // -> found: Add +1 to the level of the (lpos)
150 // c) If the new tag is smaller than the previous element,
151 // reorder
Akron63ae00b2017-05-16 22:03:36 +0200152
153 // Normalize start and end
154 var sortedArcs = this._arcs.map(function (v) {
Akrond67d45b2017-05-18 21:47:38 +0200155
156 // Check for long anchors
157 if (v.start instanceof Array) {
158 var middle = Math.ceil(Math.abs(v.start[1] - v.start[0]) / 2) + v.start[0];
159
160 v.startAnchor = {
161 "first": v.start[0],
162 "last" : v.start[1],
163 "length" : v.start[1] - v.start[0]
164 };
165
166 // Add to anchors list
167 anchors.push(v.startAnchor);
168 v.start = middle;
169 };
170
171 if (v.end instanceof Array) {
172 var middle = Math.abs(v.end[0] - v.end[1]) + v.end[0];
173 v.endAnchor = {
174 "first": v.end[0],
175 "last" : v.end[1],
176 "length" : v.end[1] - v.end[0]
177 };
178
179 // Add to anchors list
180 anchors.push(v.endAnchor);
181 v.end = middle;
182 };
183
184 // calculate the arch length
Akron63ae00b2017-05-16 22:03:36 +0200185 if (v.start < v.end) {
186 v.first = v.start;
187 v.last = v.end;
188 v.length = v.end - v.start;
189 }
190 else {
191 v.first = v.end;
192 v.last = v.start;
193 v.length = v.start - v.end;
194 };
195 return v;
196 });
197
198 // Sort based on length
199 sortedArcs.sort(function (a, b) {
200 if (a.length < b.length)
201 return -1;
202 else
203 return 1;
204 });
205
Akrond67d45b2017-05-18 21:47:38 +0200206 this._sortedArcs = lengthSort(sortedArcs, false);
Akron63ae00b2017-05-16 22:03:36 +0200207
Akrond67d45b2017-05-18 21:47:38 +0200208 this._sortedAnchors = lengthSort(anchors, true);
Akronf5dc5102017-05-16 20:32:57 +0200209 },
210
211 show : function () {
212 var svg = this._element;
213
214 /*
215 * Generate token list
216 */
217 var text = svg.appendChild(this._c("text"));
218 text.setAttribute("y", 135);
219 text.setAttribute("x", 160);
220
221 var lastRight = 0;
222 for (var node_i in this._tokens) {
223 // Append svg
224 var tspan = text.appendChild(this._c("tspan"));
225 tspan.appendChild(document.createTextNode(this._tokens[node_i]));
226
227 this._tokenElements.push(tspan);
228
229 // Add whitespace!
230 text.appendChild(document.createTextNode(" "));
231 };
232
233 this.arcs = svg.appendChild(this._c("g"));
234 this.arcs.classList.add("arcs");
235
236 var textBox = text.getBoundingClientRect();
237
238 this.arcs.setAttribute(
239 "transform",
240 "translate(0," + textBox.y +")"
241 );
Akron63ae00b2017-05-16 22:03:36 +0200242
Akronf5dc5102017-05-16 20:32:57 +0200243 /*
244 * TODO:
245 * Before creating the arcs, the height of the arc
246 * needs to be calculated to make it possible to "stack" arcs.
247 * That means, the arcs need to be presorted, so massively
248 * overlapping arcs are taken first.
Akrond67d45b2017-05-18 21:47:38 +0200249 * On the other hand, anchors need to be sorted as well
250 * in the same way.
Akronf5dc5102017-05-16 20:32:57 +0200251 */
Akrond67d45b2017-05-18 21:47:38 +0200252 this._sortArcs();
253
254 var i;
255 for (i in this._sortedAnchors) {
256 this.arcs.appendChild(this._drawAnchor(this._sortedAnchors[i]));
257 };
258
259 for (i in this._sortedArcs) {
260 this.arcs.appendChild(this._drawArc(this._sortedArcs[i]));
Akronf5dc5102017-05-16 20:32:57 +0200261 };
262 }
Akrond67d45b2017-05-18 21:47:38 +0200263 };
264
265 function lengthSort (list, inclusive) {
266
267 /*
268 * The "inclusive" flag allows to
269 * modify the behaviour for inclusivity check,
270 * e.g. if identical start or endpoints mean overlap or not.
271 */
272
273 var stack = [];
274
275 // Iterate over all definitions
276 for (var i = 0; i < list.length; i++) {
277 var current = list[i];
278
279 // Check the stack order
280 var overlaps = 0;
281
282 for (var j = (stack.length - 1); j >= 0; j--) {
283 var check = stack[j];
284
285 // (a..(b..b)..a)
286 if (current.first <= check.first && current.last >= check.last) {
287 overlaps = check.overlaps + 1;
288 break;
289 }
290
291 // (a..(b..a)..b)
292 else if (current.first <= check.first && current.last >= check.first) {
293
294 if (inclusive || (current.first != check.first && current.last != check.first)) {
295 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
296 };
297 }
298
299 // (b..(a..b)..a)
300 else if (current.first <= check.last && current.last >= check.last) {
301
302 if (inclusive || (current.first != check.last && current.last != check.last)) {
303 overlaps = check.overlaps + (current.length == check.length ? 0 : 1);
304 };
305 };
306 };
307
308 // Set overlaps
309 current.overlaps = overlaps;
310
311 stack.push(current);
312
313 // Although it is already sorted,
314 // the new item has to be put at the correct place
315 // TODO: Use something like splice() instead
316 stack.sort(function (a,b) {
317 b.overlaps - a.overlaps
318 });
319 };
320
321 return stack;
322 };
Akronf5dc5102017-05-16 20:32:57 +0200323});