blob: c5ae92385c872d3a37c8bcae2ad81f6e4bf6c853 [file] [log] [blame]
Nils Diewald0e6992a2015-04-14 20:13:52 +00001/**
Nils Diewald7148c6f2015-05-04 15:07:53 +00002 * Visualize span annotations as a tree
3 * using Dagre.
Akron7524be12016-06-01 17:31:33 +02004 *
5 * This should be lazy loaded!
Nils Diewald0e6992a2015-04-14 20:13:52 +00006 */
7define(['lib/dagre'], function (dagre) {
8 "use strict";
9
10 var svgXmlns = "http://www.w3.org/2000/svg";
11 var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
12
Nils Diewald7148c6f2015-05-04 15:07:53 +000013 // Node size
Nils Diewald4347ee92015-05-04 20:32:48 +000014 var WIDTH = 55, HEIGHT = 20, LINEHEIGHT = 14;
Nils Diewald7148c6f2015-05-04 15:07:53 +000015
Nils Diewald0e6992a2015-04-14 20:13:52 +000016 // Create path for node connections
17 function _line (src, target) {
18 var x1 = src.x,
19 y1 = src.y,
20 x2 = target.x,
21 y2 = target.y - target.height / 2;
22
23 // c 0,0 -10,0
24 return 'M ' + x1 + ',' + y1 + ' ' +
25 'C ' + x1 + ',' + y1 + ' ' +
26 x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
27 x2 + ',' + y2;
28 };
29
30 return {
Nils Diewald7148c6f2015-05-04 15:07:53 +000031
32 /**
33 * Create new tree visualization based
34 * on a match snippet.
35 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000036 create : function (snippet) {
Nils Diewald7148c6f2015-05-04 15:07:53 +000037 return Object.create(this).
Akronc56cf2d2016-11-09 22:02:38 +010038 _init(snippet);
Nils Diewald0e6992a2015-04-14 20:13:52 +000039 },
40
Nils Diewald0e6992a2015-04-14 20:13:52 +000041
Nils Diewald7148c6f2015-05-04 15:07:53 +000042 // Initialize the tree based on a snippet.
Nils Diewald0e6992a2015-04-14 20:13:52 +000043 _init : function (snippet) {
44 this._next = new Number(0);
Akronc56cf2d2016-11-09 22:02:38 +010045
Nils Diewald0e6992a2015-04-14 20:13:52 +000046 // Create html for traversal
47 var html = document.createElement("div");
48 html.innerHTML = snippet;
49 var g = new dagre.graphlib.Graph({
Akronc56cf2d2016-11-09 22:02:38 +010050 "directed" : true
Nils Diewald0e6992a2015-04-14 20:13:52 +000051 });
52 g.setGraph({
Akronc56cf2d2016-11-09 22:02:38 +010053 "nodesep" : 35,
54 "ranksep" : 15,
55 "marginx" : 40,
56 "marginy" : 10
Nils Diewald0e6992a2015-04-14 20:13:52 +000057 });
58 g.setDefaultEdgeLabel({});
59
60 this._graph = g;
61
62 // This is a new root
63 this._addNode(
Akronc56cf2d2016-11-09 22:02:38 +010064 this._next++,
65 { "class" : "root" }
Nils Diewald0e6992a2015-04-14 20:13:52 +000066 );
67
68 // Parse nodes from root
Akron98a933f2016-08-11 00:19:17 +020069 this._parse(0, html.childNodes, undefined);
Nils Diewald0e6992a2015-04-14 20:13:52 +000070
71 // Root node has only one child - remove
72 if (g.outEdges(0).length === 1)
Akronc56cf2d2016-11-09 22:02:38 +010073 g.removeNode(0);
Nils Diewald0e6992a2015-04-14 20:13:52 +000074
75 html = undefined;
76 return this;
77 },
78
Nils Diewald7148c6f2015-05-04 15:07:53 +000079 /**
80 * The number of nodes in the tree.
81 */
82 nodes : function () {
83 return this._next;
84 },
85
86 // Add new node to graph
87 _addNode : function (id, obj) {
88 obj["width"] = WIDTH;
89 obj["height"] = HEIGHT;
90 this._graph.setNode(id, obj)
Akron98a933f2016-08-11 00:19:17 +020091 return obj;
Nils Diewald7148c6f2015-05-04 15:07:53 +000092 },
93
94 // Add new edge to graph
95 _addEdge : function (src, target) {
96 this._graph.setEdge(src, target);
97 },
98
Nils Diewald0e6992a2015-04-14 20:13:52 +000099 // Remove foundry and layer for labels
100 _clean : function (title) {
101 return title.replace(_TermRE, "$3");
102 },
103
104 // Parse the snippet
Akron98a933f2016-08-11 00:19:17 +0200105 _parse : function (parent, children, mark) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000106 for (var i in children) {
Akronc56cf2d2016-11-09 22:02:38 +0100107 var c = children[i];
Nils Diewald0e6992a2015-04-14 20:13:52 +0000108
Akronc56cf2d2016-11-09 22:02:38 +0100109 // Element node
110 if (c.nodeType == 1) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000111
Akronc56cf2d2016-11-09 22:02:38 +0100112 // Get title from html
113 if (c.getAttribute("title")) {
114 var title = this._clean(c.getAttribute("title"));
Nils Diewald0e6992a2015-04-14 20:13:52 +0000115
Akronc56cf2d2016-11-09 22:02:38 +0100116 // Add child node
117 var id = this._next++;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000118
Akronc56cf2d2016-11-09 22:02:38 +0100119 var obj = this._addNode(id, {
120 "class" : "middle",
121 "label" : title
122 });
Akron98a933f2016-08-11 00:19:17 +0200123
124 if (mark !== undefined) {
125 obj.class += ' mark';
126 };
127
Akronc56cf2d2016-11-09 22:02:38 +0100128 this._addEdge(parent, id);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000129
Akronc56cf2d2016-11-09 22:02:38 +0100130 // Check for next level
131 if (c.hasChildNodes())
132 this._parse(id, c.childNodes, mark);
133 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000134
Akronc56cf2d2016-11-09 22:02:38 +0100135 // Step further
136 else if (c.hasChildNodes()) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000137
Akron98a933f2016-08-11 00:19:17 +0200138 if (c.tagName === 'MARK') {
Akronc56cf2d2016-11-09 22:02:38 +0100139 this._parse(parent, c.childNodes, true);
Akron98a933f2016-08-11 00:19:17 +0200140 }
141 else {
Akronc56cf2d2016-11-09 22:02:38 +0100142 this._parse(parent, c.childNodes, mark);
Akron98a933f2016-08-11 00:19:17 +0200143 };
144 };
Akronc56cf2d2016-11-09 22:02:38 +0100145 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000146
Akronc56cf2d2016-11-09 22:02:38 +0100147 // Text node
148 else if (c.nodeType == 3)
Nils Diewald0e6992a2015-04-14 20:13:52 +0000149
Akronc56cf2d2016-11-09 22:02:38 +0100150 if (c.nodeValue.match(/[-a-z0-9]/i)) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000151
Akronc56cf2d2016-11-09 22:02:38 +0100152 // Add child node
153 var id = this._next++;
154 this._addNode(id, {
155 "class" : "leaf",
156 "label" : c.nodeValue
157 });
Akron98a933f2016-08-11 00:19:17 +0200158
Akronc56cf2d2016-11-09 22:02:38 +0100159 this._addEdge(parent, id);
160 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000161 };
162 return this;
163 },
164
165 /**
166 * Center the viewport of the canvas
167 */
168 center : function () {
169 if (this._element === undefined)
Akronc56cf2d2016-11-09 22:02:38 +0100170 return;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000171
172 var treeDiv = this._element.parentNode;
173
174 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
175 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
176 // Reposition:
177 if (cWidth > treeWidth) {
Akronc56cf2d2016-11-09 22:02:38 +0100178 var scrollValue = (cWidth - treeWidth) / 2;
179 treeDiv.scrollLeft = scrollValue;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000180 };
181 },
182
Akronc56cf2d2016-11-09 22:02:38 +0100183
184 toBase64 : function () {
185 return btoa(unescape(encodeURIComponent(this.element().outerHTML)));
186 },
187
Nils Diewald7148c6f2015-05-04 15:07:53 +0000188 /**
189 * Get the dom element of the tree view.
190 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000191 element : function () {
192 if (this._element !== undefined)
Akronc56cf2d2016-11-09 22:02:38 +0100193 return this._element;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000194
195 var g = this._graph;
196
197 dagre.layout(g);
Akronc56cf2d2016-11-09 22:02:38 +0100198
Nils Diewald0e6992a2015-04-14 20:13:52 +0000199 var canvas = document.createElementNS(svgXmlns, 'svg');
200 this._element = canvas;
201
Nils Diewald4347ee92015-05-04 20:32:48 +0000202 var height = g.graph().height;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000203
204 // Create edges
205 g.edges().forEach(
Akronc56cf2d2016-11-09 22:02:38 +0100206 function (e) {
207 var src = g.node(e.v);
208 var target = g.node(e.w);
209 var p = document.createElementNS(svgXmlns, 'path');
210 p.setAttributeNS(null, "d", _line(src, target));
211 p.classList.add('edge');
212 canvas.appendChild(p);
213 });
Nils Diewald0e6992a2015-04-14 20:13:52 +0000214
215 // Create nodes
216 g.nodes().forEach(
Akronc56cf2d2016-11-09 22:02:38 +0100217 function (v) {
218 v = g.node(v);
219 var group = document.createElementNS(svgXmlns, 'g');
220 group.setAttribute('class', v.class);
221
222 // Add node box
223 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
224 rect.setAttribute('x', v.x - v.width / 2);
225 rect.setAttribute('y', v.y - v.height / 2);
226 rect.setAttribute('rx', 5);
227 rect.setAttribute('ry', 5);
228 rect.setAttribute('width', v.width);
229 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000230
Akronc56cf2d2016-11-09 22:02:38 +0100231 if (v.class === 'root' && v.label === undefined) {
232 rect.setAttribute('width', v.height);
233 rect.setAttribute('x', v.x - v.height / 2);
234 rect.setAttribute('class', 'empty');
235 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000236
Akronc56cf2d2016-11-09 22:02:38 +0100237 // Add label
238 if (v.label !== undefined) {
239 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
240 var y = v.y - v.height / 2;
241 text.setAttribute('y', y);
242 text.setAttribute(
243 'transform',
244 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
245 );
246
247 if (v.class === "leaf") {
248 text.setAttribute('title', v.label);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000249
Akronc56cf2d2016-11-09 22:02:38 +0100250 var labelPart = v.label.split(" ");
251 var n = 0;
252 for (var i = 0; i < labelPart.length; i++) {
253 if (labelPart[i].length === 0)
254 continue;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000255
Akronc56cf2d2016-11-09 22:02:38 +0100256 var tspan = document.createElementNS(svgXmlns, 'tspan');
257 tspan.appendChild(document.createTextNode(labelPart[i]));
258 if (n !== 0)
259 tspan.setAttribute('dy', LINEHEIGHT + 'pt');
260 else
261 n = 1;
262 tspan.setAttribute('x', v.x - v.width / 2);
263 y += LINEHEIGHT;
264 text.appendChild(tspan);
265 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000266
Akronc56cf2d2016-11-09 22:02:38 +0100267 y += LINEHEIGHT;
Nils Diewald4347ee92015-05-04 20:32:48 +0000268
Akronc56cf2d2016-11-09 22:02:38 +0100269 // The text is below the canvas - readjust the height!
270 if (y > height)
271 height = y;
272 }
273 else {
274 var tspan = document.createElementNS(svgXmlns, 'tspan');
275 tspan.appendChild(document.createTextNode(v.label));
276 tspan.setAttribute('x', v.x - v.width / 2);
277 text.appendChild(tspan);
278 };
279 };
280 canvas.appendChild(group);
281 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000282 );
283
Nils Diewald4347ee92015-05-04 20:32:48 +0000284 canvas.setAttribute('width', g.graph().width);
285 canvas.setAttribute('height', height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000286 return this._element;
287 }
288 };
289});