blob: b974031f502abd2f7d3b1e00a8653cc64426879e [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();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000198 that.info().toggle();
Nils Diewalda297f062015-04-02 00:23:46 +0000199 });
200
201 ul.appendChild(close);
202 ul.appendChild(info);
203
204 return true;
205 },
206
Nils Diewald8bc7e412015-03-19 22:08:27 +0000207 /**
Nils Diewalda297f062015-04-02 00:23:46 +0000208 * Close info view
209 */
210 close : function () {
211 this._element.classList.remove('active');
212
213/*
214 if (this._info !== undefined) {
215 this._info.destroy();
216 };
217*/
218 },
219
220
Nils Diewalda297f062015-04-02 00:23:46 +0000221 /**
222 * Get and open associated match info.
223 */
224 info : function () {
225
226 // Create match info
227 if (this._info === undefined)
228 this._info = KorAP.MatchInfo.create(this);
229
230 // There is an element to append
231 if (this._element === undefined ||
232 this._element === null)
233 return this._info;
234
235 // Info is already activated
Nils Diewald5c5a7472015-04-02 22:13:38 +0000236 if (this._info._element !== undefined)
Nils Diewalda297f062015-04-02 00:23:46 +0000237 return this._info;
238
Nils Diewalda297f062015-04-02 00:23:46 +0000239 return this._info;
240 },
241
242
243 /**
244 * Get match element.
245 */
246 element : function () {
247
248 // May be null
249 return this._element;
250 }
251 };
252
253
254
255 /**
256 * Information about a match.
257 */
258 KorAP.MatchInfo = {
259
260 /**
261 * Create new object
262 */
263 create : function (match) {
264 return Object.create(KorAP.MatchInfo)._init(match);
265 },
266
267 /**
268 * Initialize object
269 */
270 _init : function (match) {
271 this._match = match;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000272 this.opened = false;
Nils Diewalda297f062015-04-02 00:23:46 +0000273 return this;
274 },
275
Nils Diewalda297f062015-04-02 00:23:46 +0000276 /**
277 * Get match object
278 */
279 match : function () {
280 return this._match;
281 },
282
Nils Diewald5c5a7472015-04-02 22:13:38 +0000283 toggle : function () {
284 if (this.opened == true) {
285 this._match.element().children[0].removeChild(
286 this.element()
287 );
288 this.opened = false;
289 }
290 else {
291 // Append element to match
292 this._match.element().children[0].appendChild(
293 this.element()
294 );
295 this.opened = true;
296 };
297
298 return this.opened;
299 },
300
Nils Diewalda297f062015-04-02 00:23:46 +0000301
302 /**
303 * Retrieve and parse snippet for table representation
Nils Diewald8bc7e412015-03-19 22:08:27 +0000304 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000305 getTable : function (tokens) {
306 var focus = [];
307
308 // Get all tokens
309 if (tokens === undefined) {
Nils Diewalda297f062015-04-02 00:23:46 +0000310 focus = this._match.getTokens();
Nils Diewalde8518f82015-03-18 22:41:49 +0000311 }
312
313 // Get only some tokens
314 else {
315
316 // Push newly to focus array
317 for (var i = 0; i < tokens.length; i++) {
318 var term = tokens[i];
319 try {
320 // Create info layer objects
321 var layer = KorAP.InfoLayer.create(term);
322 layer.type = "tokens";
323 focus.push(layer);
324 }
325 catch (e) {
326 continue;
327 };
328 };
329 };
330
331 // No tokens chosen
332 if (focus.length == 0)
333 return;
334
335 // Get info (may be cached)
Nils Diewald4f6521a2015-03-20 21:30:13 +0000336 // TODO: Async
Nils Diewalde8518f82015-03-18 22:41:49 +0000337 var matchResponse = KorAP.API.getMatchInfo(
338 this._match,
Nils Diewald4f6521a2015-03-20 21:30:13 +0000339 { 'spans' : false, 'layer' : focus }
Nils Diewalde8518f82015-03-18 22:41:49 +0000340 );
341
342 // Get snippet from match info
343 if (matchResponse["snippet"] !== undefined) {
Nils Diewald8bc7e412015-03-19 22:08:27 +0000344 this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
Nils Diewalde8518f82015-03-18 22:41:49 +0000345 return this._table;
346 };
347
Nils Diewalda297f062015-04-02 00:23:46 +0000348 // Todo: Store the table as a hash of the focus
349
Nils Diewalde8518f82015-03-18 22:41:49 +0000350 return null;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000351 },
Nils Diewalde8518f82015-03-18 22:41:49 +0000352
Nils Diewalda297f062015-04-02 00:23:46 +0000353
354 /**
355 * Retrieve and parse snippet for tree representation
356 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000357 getTree : function (foundry, layer) {
Nils Diewald4f6521a2015-03-20 21:30:13 +0000358 var focus = [];
359
360 // TODO: Async
361 var matchResponse = KorAP.API.getMatchInfo(
362 this._match, {
363 'spans' : true,
364 'foundry' : foundry,
365 'layer' : layer
366 }
367 );
368
369 // TODO: Support and cache multiple trees
370
371 // Get snippet from match info
372 if (matchResponse["snippet"] !== undefined) {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000373 // Todo: This should be cached somehow
374 return KorAP.MatchTree.create(matchResponse["snippet"]);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000375 };
376
377 return null;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000378 },
379
380 /**
Nils Diewalda297f062015-04-02 00:23:46 +0000381 * Destroy this match information view.
382 */
383 destroy : function () {
384
385 // Remove circular reference
386 if (this._treeMenu !== undefined)
387 delete this._treeMenu["info"];
388
389 this._treeMenu.destroy();
390 this._treeMenu = undefined;
391 this._match = undefined;
392
393 // Element destroy
394 },
395
396
397 /**
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000398 * Add a new tree view to the list
399 */
400 addTree : function (foundry, layer) {
401 var treeObj = this.getTree(foundry, layer);
402
403 // Something went wrong - probably log!!!
404 if (treeObj === null)
405 return;
406
407 var matchtree = document.createElement('div');
408 matchtree.classList.add('matchtree');
409
410 var h6 = matchtree.appendChild(document.createElement('h6'));
411 h6.appendChild(document.createElement('span'))
412 .appendChild(document.createTextNode(foundry));
413 h6.appendChild(document.createElement('span'))
414 .appendChild(document.createTextNode(layer));
415
416 var tree = matchtree.appendChild(
417 document.createElement('div')
418 );
419 tree.appendChild(treeObj.element());
420 this._element.insertBefore(matchtree, this._element.lastChild);
421
422 var close = tree.appendChild(document.createElement('em'));
423 close.addEventListener(
424 'click', function (e) {
425 matchtree.parentNode.removeChild(matchtree);
426 e.halt();
427 }
428 );
429
430 // Reposition the view to the center
431 // (This may in a future release be a reposition
432 // to move the root into the center or the actual
433 // match)
434 treeObj.center();
435 },
436
437 /**
438 * Create match information view.
439 */
440 element : function () {
Nils Diewalda297f062015-04-02 00:23:46 +0000441
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000442 if (this._element !== undefined)
443 return this._element;
444
445 // Create info table
446 var info = document.createElement('div');
447 info.classList.add('matchinfo');
448
449 // Append default table
450 var matchtable = document.createElement('div');
451 matchtable.classList.add('matchtable');
452 matchtable.appendChild(this.getTable().element());
453 info.appendChild(matchtable);
454
Nils Diewalda297f062015-04-02 00:23:46 +0000455 // Get spans
456 var spanLayers = this._match.getSpans().sort(
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000457 function (a, b) {
458 if (a.foundry < b.foundry) {
459 return -1;
460 }
461 else if (a.foundry > b.foundry) {
462 return 1;
463 }
464 else if (a.layer < b.layer) {
465 return -1;
466 }
467 else if (a.layer > b.layer) {
468 return 1;
469 };
470 return 0;
471 });
472
473 var menuList = [];
474
475 // Show tree views
476 for (var i = 0; i < spanLayers.length; i++) {
477 var span = spanLayers[i];
478
479 // Add foundry/layer to menu list
480 menuList.push([
481 span.foundry + '/' + span.layer,
482 span.foundry,
483 span.layer
484 ]);
485 };
486
487 // Create tree menu
488 var treemenu = this.treeMenu(menuList);
489 var span = info.appendChild(document.createElement('p'));
Nils Diewalda297f062015-04-02 00:23:46 +0000490 span.classList.add('addtree');
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000491 span.appendChild(document.createTextNode(loc.ADDTREE));
492
493 var treeElement = treemenu.element();
494 span.appendChild(treeElement);
495
496 span.addEventListener('click', function (e) {
497 treemenu.show('');
498 treemenu.focus();
499 });
500
501 this._element = info;
502
503 return info;
Nils Diewalda297f062015-04-02 00:23:46 +0000504
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000505 },
506
Nils Diewalda297f062015-04-02 00:23:46 +0000507
508 /**
509 * Get tree menu.
510 * There is only one menu rendered
511 * - no matter how many trees exist
512 */
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000513 treeMenu : function (list) {
514 if (this._treeMenu !== undefined)
515 return this._treeMenu;
516
517 return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
Nils Diewald8bc7e412015-03-19 22:08:27 +0000518 }
Nils Diewalde8518f82015-03-18 22:41:49 +0000519 };
520
Nils Diewalda297f062015-04-02 00:23:46 +0000521
Nils Diewalde8518f82015-03-18 22:41:49 +0000522
523 /**
524 *
525 * Alternatively pass a string as <tt>base/s=span</tt>
526 *
527 * @param foundry
528 */
529 KorAP.InfoLayer = {
530 create : function (foundry, layer, type) {
531 return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
532 },
533 _init : function (foundry, layer, type) {
534 if (foundry === undefined)
535 throw new Error("Missing parameters");
536
537 if (layer === undefined) {
538 if (KorAP._AvailableRE.exec(foundry)) {
539 this.foundry = RegExp.$1;
540 this.layer = RegExp.$2;
541 this.type = RegExp.$3;
542 }
543 else {
544 throw new Error("Missing parameters");
545 };
546 }
547 else {
548 this.foundry = foundry;
549 this.layer = layer;
550 this.type = type;
551 };
552
553 if (this.type === undefined)
554 this.type = 'tokens';
555
556 return this;
557 }
558 };
559
560
Nils Diewald8bc7e412015-03-19 22:08:27 +0000561 KorAP.MatchTable = {
Nils Diewalde8518f82015-03-18 22:41:49 +0000562 create : function (snippet) {
Nils Diewald8bc7e412015-03-19 22:08:27 +0000563 return Object.create(KorAP.MatchTable)._init(snippet);
Nils Diewalde8518f82015-03-18 22:41:49 +0000564 },
565 _init : function (snippet) {
566 // Create html for traversal
567 var html = document.createElement("div");
568 html.innerHTML = snippet;
569
570 this._pos = 0;
571 this._token = [];
572 this._info = [];
Nils Diewald8bc7e412015-03-19 22:08:27 +0000573 this._foundry = {};
574 this._layer = {};
Nils Diewalde8518f82015-03-18 22:41:49 +0000575
576 // Parse the snippet
577 this._parse(html.childNodes);
578
Nils Diewalde8518f82015-03-18 22:41:49 +0000579 html.innerHTML = '';
580 return this;
581 },
582
583 length : function () {
584 return this._pos;
585 },
586
587 getToken : function (pos) {
588 if (pos === undefined)
589 return this._token;
590 return this._token[pos];
591 },
592
593 getValue : function (pos, foundry, layer) {
594 return this._info[pos][foundry + '/' + layer]
595 },
596
597 getLayerPerFoundry : function (foundry) {
598 return this._foundry[foundry]
599 },
600
601 getFoundryPerLayer : function (layer) {
602 return this._layer[layer];
603 },
604
605 // Parse the snippet
606 _parse : function (children) {
607
608 // Get all children
609 for (var i in children) {
610 var c = children[i];
611
612 // Create object on position unless it exists
613 if (this._info[this._pos] === undefined)
614 this._info[this._pos] = {};
615
616 // Store at position in foundry/layer as array
617 var found = this._info[this._pos];
618
619 // Element with title
620 if (c.nodeType === 1) {
621 if (c.getAttribute("title") &&
622 KorAP._TermRE.exec(c.getAttribute("title"))) {
623
624 // Fill position with info
625 var foundry, layer, value;
626 if (RegExp.$2) {
627 foundry = RegExp.$1;
628 layer = RegExp.$2;
629 }
630 else {
631 foundry = "base";
632 layer = RegExp.$1
633 };
634
635 value = RegExp.$3;
636
637 if (found[foundry + "/" + layer] === undefined)
638 found[foundry + "/" + layer] = [];
639
640 // Push value to foundry/layer at correct position
641 found[foundry + "/" + layer].push(RegExp.$3);
642
643 // Set foundry
Nils Diewald8bc7e412015-03-19 22:08:27 +0000644 if (this._foundry[foundry] === undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000645 this._foundry[foundry] = {};
646 this._foundry[foundry][layer] = 1;
647
648 // Set layer
Nils Diewald8bc7e412015-03-19 22:08:27 +0000649 if (this._layer[layer] === undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000650 this._layer[layer] = {};
651 this._layer[layer][foundry] = 1;
652 };
653
654 // depth search
655 if (c.hasChildNodes())
656 this._parse(c.childNodes);
657 }
658
Nils Diewald8bc7e412015-03-19 22:08:27 +0000659 // Leaf node
660 // store string on position and go to next string
Nils Diewalde8518f82015-03-18 22:41:49 +0000661 else if (c.nodeType === 3) {
662 if (c.nodeValue.match(/[a-z0-9]/i))
663 this._token[this._pos++] = c.nodeValue;
664 };
665 };
666
667 delete this._info[this._pos];
668 },
Nils Diewald4f6521a2015-03-20 21:30:13 +0000669
670
671 /**
672 * Get HTML table view of annotations.
673 */
Nils Diewalde8518f82015-03-18 22:41:49 +0000674 element : function () {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000675 if (this._element !== undefined)
676 return this._element;
677
Nils Diewalde8518f82015-03-18 22:41:49 +0000678 // First the legend table
Nils Diewald8bc7e412015-03-19 22:08:27 +0000679 var d = document;
680 var table = d.createElement('table');
Nils Diewald8bc7e412015-03-19 22:08:27 +0000681
Nils Diewald4f6521a2015-03-20 21:30:13 +0000682 // Single row in head
683 var tr = table.appendChild(d.createElement('thead'))
684 .appendChild(d.createElement('tr'));
685
686 // Add cell to row
Nils Diewald8bc7e412015-03-19 22:08:27 +0000687 var addCell = function (type, name) {
688 var c = this.appendChild(d.createElement(type))
689 if (name === undefined)
690 return c;
691
692 if (name instanceof Array) {
693 for (var n = 0; n < name.length; n++) {
694 c.appendChild(d.createTextNode(name[n]));
695 if (n !== name.length - 1) {
696 c.appendChild(d.createElement('br'));
697 };
698 };
699 }
700 else {
701 c.appendChild(d.createTextNode(name));
702 };
703 };
704
705 tr.addCell = addCell;
706
707 // Add header information
708 tr.addCell('th', 'Foundry');
709 tr.addCell('th', 'Layer');
710
711 // Add tokens
712 for (var i in this._token) {
713 tr.addCell('th', this.getToken(i));
714 };
715
716 var tbody = table.appendChild(
717 d.createElement('tbody')
718 );
719
720 var foundryList = Object.keys(this._foundry).sort();
721
722 for (var f = 0; f < foundryList.length; f++) {
723 var foundry = foundryList[f];
724 var layerList =
725 Object.keys(this._foundry[foundry]).sort();
726
727 for (var l = 0; l < layerList.length; l++) {
728 var layer = layerList[l];
729 tr = tbody.appendChild(
730 d.createElement('tr')
731 );
732 tr.setAttribute('tabindex', 0);
733 tr.addCell = addCell;
734
735 tr.addCell('th', foundry);
736 tr.addCell('th', layer);
737
738 for (var v = 0; v < this.length(); v++) {
739 tr.addCell(
740 'td',
741 this.getValue(v, foundry, layer)
742 );
743 };
744 };
745 };
746
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000747 return this._element = table;
Nils Diewalde8518f82015-03-18 22:41:49 +0000748 }
749 };
750
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000751
Nils Diewald4f6521a2015-03-20 21:30:13 +0000752 /**
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000753 * Visualize span annotations as a tree using Dagre.
Nils Diewald4f6521a2015-03-20 21:30:13 +0000754 */
Nils Diewald4f6521a2015-03-20 21:30:13 +0000755 KorAP.MatchTree = {
756
757 create : function (snippet) {
758 return Object.create(KorAP.MatchTree)._init(snippet);
759 },
760
761 nodes : function () {
762 return this._next;
763 },
764
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000765 _addNode : function (id, obj) {
766 obj["width"] = 55;
767 obj["height"] = 20;
768 this._graph.setNode(id, obj)
769 },
770
771 _addEdge : function (src, target) {
772 this._graph.setEdge(src, target);
773 },
774
Nils Diewald4f6521a2015-03-20 21:30:13 +0000775 _init : function (snippet) {
776 this._next = new Number(0);
777
778 // Create html for traversal
779 var html = document.createElement("div");
780 html.innerHTML = snippet;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000781 var g = new dagre.graphlib.Graph({
782 "directed" : true
783 });
784 g.setGraph({
785 "nodesep" : 35,
786 "ranksep" : 15,
787 "marginx" : 40,
788 "marginy" : 10
789 });
790 g.setDefaultEdgeLabel({});
791
792 this._graph = g;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000793
794 // This is a new root
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000795 this._addNode(
Nils Diewald4f6521a2015-03-20 21:30:13 +0000796 this._next++,
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000797 { "class" : "root" }
Nils Diewald4f6521a2015-03-20 21:30:13 +0000798 );
799
800 // Parse nodes from root
801 this._parse(0, html.childNodes);
802
803 // Root node has only one child - remove
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000804 if (g.outEdges(0).length === 1)
805 g.removeNode(0);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000806
807 html = undefined;
808 return this;
809 },
810
811 // Remove foundry and layer for labels
812 _clean : function (title) {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000813 return title.replace(KorAP._TermRE, "$3");
Nils Diewald4f6521a2015-03-20 21:30:13 +0000814 },
815
816 // Parse the snippet
817 _parse : function (parent, children) {
818 for (var i in children) {
819 var c = children[i];
820
821 // Element node
822 if (c.nodeType == 1) {
823
824 // Get title from html
825 if (c.getAttribute("title")) {
826 var title = this._clean(c.getAttribute("title"));
827
828 // Add child node
829 var id = this._next++;
830
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000831 this._addNode(id, {
832 "class" : "middle",
Nils Diewald4f6521a2015-03-20 21:30:13 +0000833 "label" : title
834 });
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000835 this._addEdge(parent, id);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000836
837 // Check for next level
838 if (c.hasChildNodes())
839 this._parse(id, c.childNodes);
840 }
841
842 // Step further
843 else if (c.hasChildNodes())
844 this._parse(parent, c.childNodes);
845 }
846
847 // Text node
848 else if (c.nodeType == 3)
849
850 if (c.nodeValue.match(/[-a-z0-9]/i)) {
851
852 // Add child node
853 var id = this._next++;
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000854 this._addNode(id, {
855 "class" : "leaf",
Nils Diewald4f6521a2015-03-20 21:30:13 +0000856 "label" : c.nodeValue
857 });
858
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000859 this._addEdge(parent, id);
Nils Diewald4f6521a2015-03-20 21:30:13 +0000860 };
861 };
862 return this;
863 },
864
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000865 /**
866 * Center the viewport of the canvas
867 */
868 center : function () {
869 if (this._element === undefined)
870 return;
871
872 var treeDiv = this._element.parentNode;
873
874 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
875 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
876 // Reposition:
877 if (cWidth > treeWidth) {
878 var scrollValue = (cWidth - treeWidth) / 2;
879 treeDiv.scrollLeft = scrollValue;
880 };
881 },
882
883 // Get element
Nils Diewald4f6521a2015-03-20 21:30:13 +0000884 element : function () {
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000885 if (this._element !== undefined)
886 return this._element;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000887
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000888 var g = this._graph;
Nils Diewald4f6521a2015-03-20 21:30:13 +0000889
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000890 dagre.layout(g);
891
892 var canvas = document.createElementNS(svgXmlns, 'svg');
893 this._element = canvas;
894
895 canvas.setAttribute('height', g.graph().height);
896 canvas.setAttribute('width', g.graph().width);
897
898 // Create edges
899 g.edges().forEach(
900 function (e) {
901 var src = g.node(e.v);
902 var target = g.node(e.w);
903 var p = document.createElementNS(svgXmlns, 'path');
904 p.setAttributeNS(null, "d", _line(src, target));
905 p.classList.add('edge');
906 canvas.appendChild(p);
907 });
908
909 // Create nodes
910 g.nodes().forEach(
911 function (v) {
912 v = g.node(v);
913 var group = document.createElementNS(svgXmlns, 'g');
914 group.classList.add(v.class);
915
916 // Add node box
917 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
918 rect.setAttributeNS(null, 'x', v.x - v.width / 2);
919 rect.setAttributeNS(null, 'y', v.y - v.height / 2);
920 rect.setAttributeNS(null, 'width', v.width);
921 rect.setAttributeNS(null, 'height', v.height);
922 rect.setAttributeNS(null, 'rx', 5);
923 rect.setAttributeNS(null, 'ry', 5);
924
925 // Add label
926 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
927 text.setAttributeNS(null, 'x', v.x - v.width / 2);
928 text.setAttributeNS(null, 'y', v.y - v.height / 2);
929 text.setAttributeNS(
930 null,
931 'transform',
932 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
933 );
934 var tspan = document.createElementNS(svgXmlns, 'tspan');
935 tspan.appendChild(document.createTextNode(v.label));
936 text.appendChild(tspan);
937 canvas.appendChild(group);
938 }
939 );
940
Nils Diewald4f6521a2015-03-20 21:30:13 +0000941 return this._element;
942 }
943 };
944
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000945 /**
946 * Menu item for tree view choice.
947 */
948 KorAP.MatchTreeItem = {
949 create : function (params) {
950 return Object.create(KorAP.MenuItem)
951 .upgradeTo(KorAP.MatchTreeItem)._init(params);
952 },
953 content : function (content) {
954 if (arguments.length === 1) {
955 this._content = content;
956 };
957 return this._content;
958 },
959
960 // The foundry attribute
961 foundry : function () {
962 return this._foundry;
963 },
964
965 // The layer attribute
966 layer : function () {
967 return this._layer;
968 },
969
970 // enter or click
971 onclick : function (e) {
972 var menu = this.menu();
973 menu.hide();
974 e.halt();
Nils Diewalda297f062015-04-02 00:23:46 +0000975 if (menu.info() !== undefined)
976 menu.info().addTree(this._foundry, this._layer);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000977 },
978
979 _init : function (params) {
980 if (params[0] === undefined)
981 throw new Error("Missing parameters");
982
983 this._name = params[0];
984 this._foundry = params[1];
985 this._layer = params[2];
986 this._content = document.createTextNode(this._name);
987 this._lcField = ' ' + this.content().textContent.toLowerCase();
988 return this;
989 }
990 };
991
992
993 /**
994 * Menu to choose from for tree views.
995 */
996 KorAP.MatchTreeMenu = {
997 create : function (info, params) {
998 var obj = Object.create(KorAP.Menu)
999 .upgradeTo(KorAP.MatchTreeMenu)
1000 ._init(KorAP.MatchTreeItem, undefined, params);
1001 obj.limit(6);
Nils Diewalda297f062015-04-02 00:23:46 +00001002
Nils Diewald6e43ffd2015-03-25 18:55:39 +00001003 obj._info = info;
1004
1005 // This is only domspecific
1006 obj.element().addEventListener('blur', function (e) {
1007 this.menu.hide();
1008 });
1009
1010 return obj;
1011 },
1012 info :function () {
1013 return this._info;
1014 }
1015 };
1016
1017
1018 // Create path for node connections
1019 function _line (src, target) {
1020 var x1 = src.x,
1021 y1 = src.y,
1022 x2 = target.x,
1023 y2 = target.y - target.height / 2;
1024
1025 // c 0,0 -10,0
1026 return 'M ' + x1 + ',' + y1 + ' ' +
1027 'C ' + x1 + ',' + y1 + ' ' +
1028 x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
1029 x2 + ',' + y2;
1030 };
1031
Nils Diewalde8518f82015-03-18 22:41:49 +00001032}(this.KorAP));