blob: f8fdd91ab2d3b48dc6eb4c7a868fdc5c7458c81e [file] [log] [blame]
Nils Diewalde8518f82015-03-18 22:41:49 +00001/**
Nils Diewalda297f062015-04-02 00:23:46 +00002 * Get information on matches,
3 * generate annotation tables and trees.
Nils Diewalde8518f82015-03-18 22:41:49 +00004 *
5 * @author Nils Diewald
6 */
Nils Diewalda297f062015-04-02 00:23:46 +00007// require menu.js, dagre
Nils Diewalde8518f82015-03-18 22:41:49 +00008/*
Nils Diewald6e43ffd2015-03-25 18:55:39 +00009 * - Highlight (at least mark as bold) the match
10 * - Scroll to match vertically per default
Nils Diewalde8518f82015-03-18 22:41:49 +000011 */
12var KorAP = KorAP || {};
13
14(function (KorAP) {
15 "use strict";
16
Nils Diewald6e43ffd2015-03-25 18:55:39 +000017 var svgXmlns = "http://www.w3.org/2000/svg";
18
Nils Diewald4f6521a2015-03-20 21:30:13 +000019 // Default log message
20 KorAP.log = KorAP.log || function (type, msg) {
21 console.log(type + ": " + msg);
22 };
23
Nils Diewald6e43ffd2015-03-25 18:55:39 +000024 // Localization values
25 var loc = (KorAP.Locale = KorAP.Locale || {} );
Nils Diewalda297f062015-04-02 00:23:46 +000026 loc.ADDTREE = loc.ADDTREE || 'Add tree view';
27 loc.SHOWINFO = loc.SHOWINFO || 'Show information';
28 loc.CLOSE = loc.CLOSE || 'Close';
Nils Diewald6e43ffd2015-03-25 18:55:39 +000029
Nils Diewalde8518f82015-03-18 22:41:49 +000030 KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
Nils Diewalda297f062015-04-02 00:23:46 +000031 KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
32 KorAP._matchTerms = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
Nils Diewalde8518f82015-03-18 22:41:49 +000033
34 // API requests
35 KorAP.API = KorAP.API || {};
Nils Diewald6e43ffd2015-03-25 18:55:39 +000036
Nils Diewald8bc7e412015-03-19 22:08:27 +000037 // TODO: Make this async
Nils Diewalde8518f82015-03-18 22:41:49 +000038 KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () { return {} };
39
Nils Diewalda297f062015-04-02 00:23:46 +000040
41 /**
42 * Match object
43 */
44 KorAP.Match = {
Nils Diewalde8518f82015-03-18 22:41:49 +000045
46 /**
47 * Create a new annotation object.
48 * Expects an array of available foundry/layer=type terms.
49 * Supported types are 'spans', 'tokens' and 'rels'.
50 */
Nils Diewalda297f062015-04-02 00:23:46 +000051 create : function (match) {
52 return Object.create(KorAP.Match)._init(match);
Nils Diewalde8518f82015-03-18 22:41:49 +000053 },
54
Nils Diewald6e43ffd2015-03-25 18:55:39 +000055 /**
Nils Diewalda297f062015-04-02 00:23:46 +000056 * Initialize match.
Nils Diewald6e43ffd2015-03-25 18:55:39 +000057 */
Nils Diewalda297f062015-04-02 00:23:46 +000058 _init : function (match) {
59 this._element = null;
Nils Diewald6e43ffd2015-03-25 18:55:39 +000060
Nils Diewalda297f062015-04-02 00:23:46 +000061 // No match defined
62 if (arguments.length < 1 ||
63 match === null ||
64 match === undefined) {
65 throw new Error('Missing parameters');
66 }
Nils Diewald6e43ffd2015-03-25 18:55:39 +000067
Nils Diewalda297f062015-04-02 00:23:46 +000068 // Match defined as a node
69 else if (match instanceof Node) {
70 this._element = match;
Nils Diewald6e43ffd2015-03-25 18:55:39 +000071
Nils Diewalda297f062015-04-02 00:23:46 +000072 // Circular reference !!
73 match["_match"] = this;
74
75 this.corpusID = match.getAttribute('data-corpus-id'),
76 this.docID = match.getAttribute('data-doc-id'),
77 this.textID = match.getAttribute('data-text-id'),
78 this.matchID = match.getAttribute('data-match-id')
79
80 // List of available annotations
81 this.available = match.getAttribute('data-available-info').split(' ');
82 }
83
84 // Match as an object
85 else {
86
87 // Iterate over allowed match terms
88 for (var i in KorAP._matchTerms) {
89 var term = KorAP._matchTerms[i];
90 if (match[term] !== undefined) {
91 this[term] = match[term];
92 }
93 else {
94 this[term] = undefined;
95 }
96 };
97 };
98
Nils Diewalde8518f82015-03-18 22:41:49 +000099 this._available = {
100 tokens : [],
Nils Diewalda297f062015-04-02 00:23:46 +0000101 spans : [],
102 rels : []
Nils Diewalde8518f82015-03-18 22:41:49 +0000103 };
Nils Diewalda297f062015-04-02 00:23:46 +0000104
105 // Iterate over info layers
106 for (var i = 0; i < this.available.length; i++) {
107 var term = this.available[i];
108
Nils Diewalde8518f82015-03-18 22:41:49 +0000109 // Create info layer objects
110 try {
111 var layer = KorAP.InfoLayer.create(term);
112 this._available[layer.type].push(layer);
113 }
114 catch (e) {
115 continue;
116 };
117 };
Nils Diewalda297f062015-04-02 00:23:46 +0000118
Nils Diewalde8518f82015-03-18 22:41:49 +0000119 return this;
120 },
121
122
123 /**
124 * Return a list of parseable tree annotations.
125 */
126 getSpans : function () {
127 return this._available.spans;
128 },
129
130
131 /**
132 * Return a list of parseable token annotations.
133 */
134 getTokens : function () {
135 return this._available.tokens;
136 },
137
138
139 /**
140 * Return a list of parseable relation annotations.
141 */
142 getRels : function () {
143 return this._available.rels;
144 },
145
Nils Diewalda297f062015-04-02 00:23:46 +0000146 /**
147 * Open match
148 */
149 open : function () {
150
151 // Add actions unless it's already activated
152 var element = this._element;
153
154 // There is an element to open
155 if (this._element === undefined || this._element === null)
156 return false;
157
158 // The element is already opened
159 if (element.classList.contains('active'))
160 return false;
161
162 // Add active class to element
163 element.classList.add('active');
164
165 // Create action buttons
166 var ul = document.createElement('ul');
167 ul.classList.add('action', 'right');
168 element.appendChild(ul);
169
170 // Use localization
171 var loc = KorAP.Locale;
172
173 // Add close button
174 var close = document.createElement('li');
175 close.appendChild(document.createElement('span'))
176 .appendChild(document.createTextNode(loc.CLOSE));
177 close.classList.add('close');
178 close.setAttribute('title', loc.CLOSE);
179
180 // Add info button
181 var info = document.createElement('li');
182 info.appendChild(document.createElement('span'))
183 .appendChild(document.createTextNode(loc.SHOWINFO));
184 info.classList.add('info');
185 info.setAttribute('title', loc.SHOWINFO);
186
187 var that = this;
188
189 // Close match
190 close.addEventListener('click', function (e) {
191 e.halt();
192 that.close()
193 });
194
195 // Add information, unless it already exists
196 info.addEventListener('click', function (e) {
197 e.halt();
198 that.info();
199 });
200
201 ul.appendChild(close);
202 ul.appendChild(info);
203
204 return true;
205 },
206
Nils Diewalde8518f82015-03-18 22:41:49 +0000207
Nils Diewald8bc7e412015-03-19 22:08:27 +0000208 /**
Nils Diewalda297f062015-04-02 00:23:46 +0000209 * Close info view
210 */
211 close : function () {
212 this._element.classList.remove('active');
213
214/*
215 if (this._info !== undefined) {
216 this._info.destroy();
217 };
218*/
219 },
220
221
222
223 /**
224 * Get and open associated match info.
225 */
226 info : function () {
227
228 // Create match info
229 if (this._info === undefined)
230 this._info = KorAP.MatchInfo.create(this);
231
232 // There is an element to append
233 if (this._element === undefined ||
234 this._element === null)
235 return this._info;
236
237 // Info is already activated
238 if (this._info._elemet !== undefined)
239 return this._info;
240
241 // Append element to match
242 this._element.children[0].appendChild(
243 this._info.element()
244 );
245
246 return this._info;
247 },
248
249
250 /**
251 * Get match element.
252 */
253 element : function () {
254
255 // May be null
256 return this._element;
257 }
258 };
259
260
261
262 /**
263 * Information about a match.
264 */
265 KorAP.MatchInfo = {
266
267 /**
268 * Create new object
269 */
270 create : function (match) {
271 return Object.create(KorAP.MatchInfo)._init(match);
272 },
273
274 /**
275 * Initialize object
276 */
277 _init : function (match) {
278 this._match = match;
279 return this;
280 },
281
282
283 /**
284 * Get match object
285 */
286 match : function () {
287 return this._match;
288 },
289
290
291 /**
292 * Retrieve and parse snippet for table representation
Nils Diewald8bc7e412015-03-19 22:08:27 +0000293 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000294 getTable : function (tokens) {
295 var focus = [];
296
297 // Get all tokens
298 if (tokens === undefined) {
Nils Diewalda297f062015-04-02 00:23:46 +0000299 focus = this._match.getTokens();
Nils Diewalde8518f82015-03-18 22:41:49 +0000300 }
301
302 // Get only some tokens
303 else {
304
305 // Push newly to focus array
306 for (var i = 0; i < tokens.length; i++) {
307 var term = tokens[i];
308 try {
309 // Create info layer objects
310 var layer = KorAP.InfoLayer.create(term);
311 layer.type = "tokens";
312 focus.push(layer);
313 }
314 catch (e) {
315 continue;
316 };
317 };
318 };
319
320 // No tokens chosen
321 if (focus.length == 0)
322 return;
323
324 // Get info (may be cached)
Nils Diewald4f6521a2015-03-20 21:30:13 +0000325 // TODO: Async
Nils Diewalde8518f82015-03-18 22:41:49 +0000326 var matchResponse = KorAP.API.getMatchInfo(
327 this._match,
Nils Diewald4f6521a2015-03-20 21:30:13 +0000328 { 'spans' : false, 'layer' : focus }
Nils Diewalde8518f82015-03-18 22:41:49 +0000329 );
330
331 // Get snippet from match info
332 if (matchResponse["snippet"] !== undefined) {
Nils Diewald8bc7e412015-03-19 22:08:27 +0000333 this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
Nils Diewalde8518f82015-03-18 22:41:49 +0000334 return this._table;
335 };
336
Nils Diewalda297f062015-04-02 00:23:46 +0000337 // Todo: Store the table as a hash of the focus
338
Nils Diewalde8518f82015-03-18 22:41:49 +0000339 return null;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000340 },
Nils Diewalde8518f82015-03-18 22:41:49 +0000341
Nils Diewalda297f062015-04-02 00:23:46 +0000342
343 /**
344 * Retrieve and parse snippet for tree representation
345 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000346 getTree : function (foundry, layer) {
Nils Diewald4f6521a2015-03-20 21:30:13 +0000347 var focus = [];
348
349 // TODO: Async
350 var matchResponse = KorAP.API.getMatchInfo(
351 this._match, {
352 'spans' : true,
353 'foundry' : foundry,
354 'layer' : layer
355 }
356 );
357
358 // TODO: Support and cache multiple trees
359
360 // Get snippet from match info
361 if (matchResponse["snippet"] !== undefined) {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000362 // Todo: This should be cached somehow
363 return KorAP.MatchTree.create(matchResponse["snippet"]);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000364 };
365
366 return null;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000367 },
368
369 /**
Nils Diewalda297f062015-04-02 00:23:46 +0000370 * Destroy this match information view.
371 */
372 destroy : function () {
373
374 // Remove circular reference
375 if (this._treeMenu !== undefined)
376 delete this._treeMenu["info"];
377
378 this._treeMenu.destroy();
379 this._treeMenu = undefined;
380 this._match = undefined;
381
382 // Element destroy
383 },
384
385
386 /**
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000387 * Add a new tree view to the list
388 */
389 addTree : function (foundry, layer) {
390 var treeObj = this.getTree(foundry, layer);
391
392 // Something went wrong - probably log!!!
393 if (treeObj === null)
394 return;
395
396 var matchtree = document.createElement('div');
397 matchtree.classList.add('matchtree');
398
399 var h6 = matchtree.appendChild(document.createElement('h6'));
400 h6.appendChild(document.createElement('span'))
401 .appendChild(document.createTextNode(foundry));
402 h6.appendChild(document.createElement('span'))
403 .appendChild(document.createTextNode(layer));
404
405 var tree = matchtree.appendChild(
406 document.createElement('div')
407 );
408 tree.appendChild(treeObj.element());
409 this._element.insertBefore(matchtree, this._element.lastChild);
410
411 var close = tree.appendChild(document.createElement('em'));
412 close.addEventListener(
413 'click', function (e) {
414 matchtree.parentNode.removeChild(matchtree);
415 e.halt();
416 }
417 );
418
419 // Reposition the view to the center
420 // (This may in a future release be a reposition
421 // to move the root into the center or the actual
422 // match)
423 treeObj.center();
424 },
425
426 /**
427 * Create match information view.
428 */
429 element : function () {
Nils Diewalda297f062015-04-02 00:23:46 +0000430
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000431 if (this._element !== undefined)
432 return this._element;
433
434 // Create info table
435 var info = document.createElement('div');
436 info.classList.add('matchinfo');
437
438 // Append default table
439 var matchtable = document.createElement('div');
440 matchtable.classList.add('matchtable');
441 matchtable.appendChild(this.getTable().element());
442 info.appendChild(matchtable);
443
Nils Diewalda297f062015-04-02 00:23:46 +0000444 // Get spans
445 var spanLayers = this._match.getSpans().sort(
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000446 function (a, b) {
447 if (a.foundry < b.foundry) {
448 return -1;
449 }
450 else if (a.foundry > b.foundry) {
451 return 1;
452 }
453 else if (a.layer < b.layer) {
454 return -1;
455 }
456 else if (a.layer > b.layer) {
457 return 1;
458 };
459 return 0;
460 });
461
462 var menuList = [];
463
464 // Show tree views
465 for (var i = 0; i < spanLayers.length; i++) {
466 var span = spanLayers[i];
467
468 // Add foundry/layer to menu list
469 menuList.push([
470 span.foundry + '/' + span.layer,
471 span.foundry,
472 span.layer
473 ]);
474 };
475
476 // Create tree menu
477 var treemenu = this.treeMenu(menuList);
478 var span = info.appendChild(document.createElement('p'));
Nils Diewalda297f062015-04-02 00:23:46 +0000479 span.classList.add('addtree');
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000480 span.appendChild(document.createTextNode(loc.ADDTREE));
481
482 var treeElement = treemenu.element();
483 span.appendChild(treeElement);
484
485 span.addEventListener('click', function (e) {
486 treemenu.show('');
487 treemenu.focus();
488 });
489
490 this._element = info;
491
492 return info;
Nils Diewalda297f062015-04-02 00:23:46 +0000493
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000494 },
495
Nils Diewalda297f062015-04-02 00:23:46 +0000496
497 /**
498 * Get tree menu.
499 * There is only one menu rendered
500 * - no matter how many trees exist
501 */
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000502 treeMenu : function (list) {
503 if (this._treeMenu !== undefined)
504 return this._treeMenu;
505
506 return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
Nils Diewald8bc7e412015-03-19 22:08:27 +0000507 }
Nils Diewalde8518f82015-03-18 22:41:49 +0000508 };
509
Nils Diewalda297f062015-04-02 00:23:46 +0000510
Nils Diewalde8518f82015-03-18 22:41:49 +0000511
512 /**
513 *
514 * Alternatively pass a string as <tt>base/s=span</tt>
515 *
516 * @param foundry
517 */
518 KorAP.InfoLayer = {
519 create : function (foundry, layer, type) {
520 return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
521 },
522 _init : function (foundry, layer, type) {
523 if (foundry === undefined)
524 throw new Error("Missing parameters");
525
526 if (layer === undefined) {
527 if (KorAP._AvailableRE.exec(foundry)) {
528 this.foundry = RegExp.$1;
529 this.layer = RegExp.$2;
530 this.type = RegExp.$3;
531 }
532 else {
533 throw new Error("Missing parameters");
534 };
535 }
536 else {
537 this.foundry = foundry;
538 this.layer = layer;
539 this.type = type;
540 };
541
542 if (this.type === undefined)
543 this.type = 'tokens';
544
545 return this;
546 }
547 };
548
549
Nils Diewald8bc7e412015-03-19 22:08:27 +0000550 KorAP.MatchTable = {
Nils Diewalde8518f82015-03-18 22:41:49 +0000551 create : function (snippet) {
Nils Diewald8bc7e412015-03-19 22:08:27 +0000552 return Object.create(KorAP.MatchTable)._init(snippet);
Nils Diewalde8518f82015-03-18 22:41:49 +0000553 },
554 _init : function (snippet) {
555 // Create html for traversal
556 var html = document.createElement("div");
557 html.innerHTML = snippet;
558
559 this._pos = 0;
560 this._token = [];
561 this._info = [];
Nils Diewald8bc7e412015-03-19 22:08:27 +0000562 this._foundry = {};
563 this._layer = {};
Nils Diewalde8518f82015-03-18 22:41:49 +0000564
565 // Parse the snippet
566 this._parse(html.childNodes);
567
Nils Diewalde8518f82015-03-18 22:41:49 +0000568 html.innerHTML = '';
569 return this;
570 },
571
572 length : function () {
573 return this._pos;
574 },
575
576 getToken : function (pos) {
577 if (pos === undefined)
578 return this._token;
579 return this._token[pos];
580 },
581
582 getValue : function (pos, foundry, layer) {
583 return this._info[pos][foundry + '/' + layer]
584 },
585
586 getLayerPerFoundry : function (foundry) {
587 return this._foundry[foundry]
588 },
589
590 getFoundryPerLayer : function (layer) {
591 return this._layer[layer];
592 },
593
594 // Parse the snippet
595 _parse : function (children) {
596
597 // Get all children
598 for (var i in children) {
599 var c = children[i];
600
601 // Create object on position unless it exists
602 if (this._info[this._pos] === undefined)
603 this._info[this._pos] = {};
604
605 // Store at position in foundry/layer as array
606 var found = this._info[this._pos];
607
608 // Element with title
609 if (c.nodeType === 1) {
610 if (c.getAttribute("title") &&
611 KorAP._TermRE.exec(c.getAttribute("title"))) {
612
613 // Fill position with info
614 var foundry, layer, value;
615 if (RegExp.$2) {
616 foundry = RegExp.$1;
617 layer = RegExp.$2;
618 }
619 else {
620 foundry = "base";
621 layer = RegExp.$1
622 };
623
624 value = RegExp.$3;
625
626 if (found[foundry + "/" + layer] === undefined)
627 found[foundry + "/" + layer] = [];
628
629 // Push value to foundry/layer at correct position
630 found[foundry + "/" + layer].push(RegExp.$3);
631
632 // Set foundry
Nils Diewald8bc7e412015-03-19 22:08:27 +0000633 if (this._foundry[foundry] === undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000634 this._foundry[foundry] = {};
635 this._foundry[foundry][layer] = 1;
636
637 // Set layer
Nils Diewald8bc7e412015-03-19 22:08:27 +0000638 if (this._layer[layer] === undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000639 this._layer[layer] = {};
640 this._layer[layer][foundry] = 1;
641 };
642
643 // depth search
644 if (c.hasChildNodes())
645 this._parse(c.childNodes);
646 }
647
Nils Diewald8bc7e412015-03-19 22:08:27 +0000648 // Leaf node
649 // store string on position and go to next string
Nils Diewalde8518f82015-03-18 22:41:49 +0000650 else if (c.nodeType === 3) {
651 if (c.nodeValue.match(/[a-z0-9]/i))
652 this._token[this._pos++] = c.nodeValue;
653 };
654 };
655
656 delete this._info[this._pos];
657 },
Nils Diewald4f6521a2015-03-20 21:30:13 +0000658
659
660 /**
661 * Get HTML table view of annotations.
662 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000663 element : function () {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000664 if (this._element !== undefined)
665 return this._element;
666
Nils Diewalde8518f82015-03-18 22:41:49 +0000667 // First the legend table
Nils Diewald8bc7e412015-03-19 22:08:27 +0000668 var d = document;
669 var table = d.createElement('table');
Nils Diewald8bc7e412015-03-19 22:08:27 +0000670
Nils Diewald4f6521a2015-03-20 21:30:13 +0000671 // Single row in head
672 var tr = table.appendChild(d.createElement('thead'))
673 .appendChild(d.createElement('tr'));
674
675 // Add cell to row
Nils Diewald8bc7e412015-03-19 22:08:27 +0000676 var addCell = function (type, name) {
677 var c = this.appendChild(d.createElement(type))
678 if (name === undefined)
679 return c;
680
681 if (name instanceof Array) {
682 for (var n = 0; n < name.length; n++) {
683 c.appendChild(d.createTextNode(name[n]));
684 if (n !== name.length - 1) {
685 c.appendChild(d.createElement('br'));
686 };
687 };
688 }
689 else {
690 c.appendChild(d.createTextNode(name));
691 };
692 };
693
694 tr.addCell = addCell;
695
696 // Add header information
697 tr.addCell('th', 'Foundry');
698 tr.addCell('th', 'Layer');
699
700 // Add tokens
701 for (var i in this._token) {
702 tr.addCell('th', this.getToken(i));
703 };
704
705 var tbody = table.appendChild(
706 d.createElement('tbody')
707 );
708
709 var foundryList = Object.keys(this._foundry).sort();
710
711 for (var f = 0; f < foundryList.length; f++) {
712 var foundry = foundryList[f];
713 var layerList =
714 Object.keys(this._foundry[foundry]).sort();
715
716 for (var l = 0; l < layerList.length; l++) {
717 var layer = layerList[l];
718 tr = tbody.appendChild(
719 d.createElement('tr')
720 );
721 tr.setAttribute('tabindex', 0);
722 tr.addCell = addCell;
723
724 tr.addCell('th', foundry);
725 tr.addCell('th', layer);
726
727 for (var v = 0; v < this.length(); v++) {
728 tr.addCell(
729 'td',
730 this.getValue(v, foundry, layer)
731 );
732 };
733 };
734 };
735
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000736 return this._element = table;
Nils Diewalde8518f82015-03-18 22:41:49 +0000737 }
738 };
739
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000740
Nils Diewald4f6521a2015-03-20 21:30:13 +0000741 /**
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000742 * Visualize span annotations as a tree using Dagre.
Nils Diewald4f6521a2015-03-20 21:30:13 +0000743 */
Nils Diewald4f6521a2015-03-20 21:30:13 +0000744 KorAP.MatchTree = {
745
746 create : function (snippet) {
747 return Object.create(KorAP.MatchTree)._init(snippet);
748 },
749
750 nodes : function () {
751 return this._next;
752 },
753
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000754 _addNode : function (id, obj) {
755 obj["width"] = 55;
756 obj["height"] = 20;
757 this._graph.setNode(id, obj)
758 },
759
760 _addEdge : function (src, target) {
761 this._graph.setEdge(src, target);
762 },
763
Nils Diewald4f6521a2015-03-20 21:30:13 +0000764 _init : function (snippet) {
765 this._next = new Number(0);
766
767 // Create html for traversal
768 var html = document.createElement("div");
769 html.innerHTML = snippet;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000770 var g = new dagre.graphlib.Graph({
771 "directed" : true
772 });
773 g.setGraph({
774 "nodesep" : 35,
775 "ranksep" : 15,
776 "marginx" : 40,
777 "marginy" : 10
778 });
779 g.setDefaultEdgeLabel({});
780
781 this._graph = g;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000782
783 // This is a new root
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000784 this._addNode(
Nils Diewald4f6521a2015-03-20 21:30:13 +0000785 this._next++,
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000786 { "class" : "root" }
Nils Diewald4f6521a2015-03-20 21:30:13 +0000787 );
788
789 // Parse nodes from root
790 this._parse(0, html.childNodes);
791
792 // Root node has only one child - remove
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000793 if (g.outEdges(0).length === 1)
794 g.removeNode(0);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000795
796 html = undefined;
797 return this;
798 },
799
800 // Remove foundry and layer for labels
801 _clean : function (title) {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000802 return title.replace(KorAP._TermRE, "$3");
Nils Diewald4f6521a2015-03-20 21:30:13 +0000803 },
804
805 // Parse the snippet
806 _parse : function (parent, children) {
807 for (var i in children) {
808 var c = children[i];
809
810 // Element node
811 if (c.nodeType == 1) {
812
813 // Get title from html
814 if (c.getAttribute("title")) {
815 var title = this._clean(c.getAttribute("title"));
816
817 // Add child node
818 var id = this._next++;
819
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000820 this._addNode(id, {
821 "class" : "middle",
Nils Diewald4f6521a2015-03-20 21:30:13 +0000822 "label" : title
823 });
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000824 this._addEdge(parent, id);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000825
826 // Check for next level
827 if (c.hasChildNodes())
828 this._parse(id, c.childNodes);
829 }
830
831 // Step further
832 else if (c.hasChildNodes())
833 this._parse(parent, c.childNodes);
834 }
835
836 // Text node
837 else if (c.nodeType == 3)
838
839 if (c.nodeValue.match(/[-a-z0-9]/i)) {
840
841 // Add child node
842 var id = this._next++;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000843 this._addNode(id, {
844 "class" : "leaf",
Nils Diewald4f6521a2015-03-20 21:30:13 +0000845 "label" : c.nodeValue
846 });
847
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000848 this._addEdge(parent, id);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000849 };
850 };
851 return this;
852 },
853
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000854 /**
855 * Center the viewport of the canvas
856 */
857 center : function () {
858 if (this._element === undefined)
859 return;
860
861 var treeDiv = this._element.parentNode;
862
863 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
864 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
865 // Reposition:
866 if (cWidth > treeWidth) {
867 var scrollValue = (cWidth - treeWidth) / 2;
868 treeDiv.scrollLeft = scrollValue;
869 };
870 },
871
872 // Get element
Nils Diewald4f6521a2015-03-20 21:30:13 +0000873 element : function () {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000874 if (this._element !== undefined)
875 return this._element;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000876
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000877 var g = this._graph;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000878
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000879 dagre.layout(g);
880
881 var canvas = document.createElementNS(svgXmlns, 'svg');
882 this._element = canvas;
883
884 canvas.setAttribute('height', g.graph().height);
885 canvas.setAttribute('width', g.graph().width);
886
887 // Create edges
888 g.edges().forEach(
889 function (e) {
890 var src = g.node(e.v);
891 var target = g.node(e.w);
892 var p = document.createElementNS(svgXmlns, 'path');
893 p.setAttributeNS(null, "d", _line(src, target));
894 p.classList.add('edge');
895 canvas.appendChild(p);
896 });
897
898 // Create nodes
899 g.nodes().forEach(
900 function (v) {
901 v = g.node(v);
902 var group = document.createElementNS(svgXmlns, 'g');
903 group.classList.add(v.class);
904
905 // Add node box
906 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
907 rect.setAttributeNS(null, 'x', v.x - v.width / 2);
908 rect.setAttributeNS(null, 'y', v.y - v.height / 2);
909 rect.setAttributeNS(null, 'width', v.width);
910 rect.setAttributeNS(null, 'height', v.height);
911 rect.setAttributeNS(null, 'rx', 5);
912 rect.setAttributeNS(null, 'ry', 5);
913
914 // Add label
915 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
916 text.setAttributeNS(null, 'x', v.x - v.width / 2);
917 text.setAttributeNS(null, 'y', v.y - v.height / 2);
918 text.setAttributeNS(
919 null,
920 'transform',
921 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
922 );
923 var tspan = document.createElementNS(svgXmlns, 'tspan');
924 tspan.appendChild(document.createTextNode(v.label));
925 text.appendChild(tspan);
926 canvas.appendChild(group);
927 }
928 );
929
Nils Diewald4f6521a2015-03-20 21:30:13 +0000930 return this._element;
931 }
932 };
933
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000934 /**
935 * Menu item for tree view choice.
936 */
937 KorAP.MatchTreeItem = {
938 create : function (params) {
939 return Object.create(KorAP.MenuItem)
940 .upgradeTo(KorAP.MatchTreeItem)._init(params);
941 },
942 content : function (content) {
943 if (arguments.length === 1) {
944 this._content = content;
945 };
946 return this._content;
947 },
948
949 // The foundry attribute
950 foundry : function () {
951 return this._foundry;
952 },
953
954 // The layer attribute
955 layer : function () {
956 return this._layer;
957 },
958
959 // enter or click
960 onclick : function (e) {
961 var menu = this.menu();
962 menu.hide();
963 e.halt();
Nils Diewalda297f062015-04-02 00:23:46 +0000964 if (menu.info() !== undefined)
965 menu.info().addTree(this._foundry, this._layer);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000966 },
967
968 _init : function (params) {
969 if (params[0] === undefined)
970 throw new Error("Missing parameters");
971
972 this._name = params[0];
973 this._foundry = params[1];
974 this._layer = params[2];
975 this._content = document.createTextNode(this._name);
976 this._lcField = ' ' + this.content().textContent.toLowerCase();
977 return this;
978 }
979 };
980
981
982 /**
983 * Menu to choose from for tree views.
984 */
985 KorAP.MatchTreeMenu = {
986 create : function (info, params) {
987 var obj = Object.create(KorAP.Menu)
988 .upgradeTo(KorAP.MatchTreeMenu)
989 ._init(KorAP.MatchTreeItem, undefined, params);
990 obj.limit(6);
Nils Diewalda297f062015-04-02 00:23:46 +0000991
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000992 obj._info = info;
993
994 // This is only domspecific
995 obj.element().addEventListener('blur', function (e) {
996 this.menu.hide();
997 });
998
999 return obj;
1000 },
1001 info :function () {
1002 return this._info;
1003 }
1004 };
1005
1006
1007 // Create path for node connections
1008 function _line (src, target) {
1009 var x1 = src.x,
1010 y1 = src.y,
1011 x2 = target.x,
1012 y2 = target.y - target.height / 2;
1013
1014 // c 0,0 -10,0
1015 return 'M ' + x1 + ',' + y1 + ' ' +
1016 'C ' + x1 + ',' + y1 + ' ' +
1017 x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
1018 x2 + ',' + y2;
1019 };
1020
Nils Diewalde8518f82015-03-18 22:41:49 +00001021}(this.KorAP));