blob: dcecd5e581f285fc56ea187f568ee5fa6032144f [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.
Nils Diewald0e6992a2015-04-14 20:13:52 +00004 */
5define(['lib/dagre'], function (dagre) {
6 "use strict";
7
8 var svgXmlns = "http://www.w3.org/2000/svg";
9 var _TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
10
Nils Diewald7148c6f2015-05-04 15:07:53 +000011 // Node size
Nils Diewald4347ee92015-05-04 20:32:48 +000012 var WIDTH = 55, HEIGHT = 20, LINEHEIGHT = 14;
Nils Diewald7148c6f2015-05-04 15:07:53 +000013
Nils Diewald0e6992a2015-04-14 20:13:52 +000014 // Create path for node connections
15 function _line (src, target) {
16 var x1 = src.x,
17 y1 = src.y,
18 x2 = target.x,
19 y2 = target.y - target.height / 2;
20
21 // c 0,0 -10,0
22 return 'M ' + x1 + ',' + y1 + ' ' +
23 'C ' + x1 + ',' + y1 + ' ' +
24 x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
25 x2 + ',' + y2;
26 };
27
28 return {
Nils Diewald7148c6f2015-05-04 15:07:53 +000029
30 /**
31 * Create new tree visualization based
32 * on a match snippet.
33 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000034 create : function (snippet) {
Nils Diewald7148c6f2015-05-04 15:07:53 +000035 return Object.create(this).
36 _init(snippet);
Nils Diewald0e6992a2015-04-14 20:13:52 +000037 },
38
Nils Diewald0e6992a2015-04-14 20:13:52 +000039
Nils Diewald7148c6f2015-05-04 15:07:53 +000040 // Initialize the tree based on a snippet.
Nils Diewald0e6992a2015-04-14 20:13:52 +000041 _init : function (snippet) {
42 this._next = new Number(0);
43
44 // Create html for traversal
45 var html = document.createElement("div");
46 html.innerHTML = snippet;
47 var g = new dagre.graphlib.Graph({
48 "directed" : true
49 });
50 g.setGraph({
51 "nodesep" : 35,
52 "ranksep" : 15,
53 "marginx" : 40,
54 "marginy" : 10
55 });
56 g.setDefaultEdgeLabel({});
57
58 this._graph = g;
59
60 // This is a new root
61 this._addNode(
Akron98a933f2016-08-11 00:19:17 +020062 this._next++,
63 { "class" : "root" }
Nils Diewald0e6992a2015-04-14 20:13:52 +000064 );
65
66 // Parse nodes from root
Akron98a933f2016-08-11 00:19:17 +020067 this._parse(0, html.childNodes, undefined);
Nils Diewald0e6992a2015-04-14 20:13:52 +000068
69 // Root node has only one child - remove
70 if (g.outEdges(0).length === 1)
71 g.removeNode(0);
72
73 html = undefined;
74 return this;
75 },
76
Nils Diewald7148c6f2015-05-04 15:07:53 +000077 /**
78 * The number of nodes in the tree.
79 */
80 nodes : function () {
81 return this._next;
82 },
83
84 // Add new node to graph
85 _addNode : function (id, obj) {
86 obj["width"] = WIDTH;
87 obj["height"] = HEIGHT;
88 this._graph.setNode(id, obj)
Akron98a933f2016-08-11 00:19:17 +020089 return obj;
Nils Diewald7148c6f2015-05-04 15:07:53 +000090 },
91
92 // Add new edge to graph
93 _addEdge : function (src, target) {
94 this._graph.setEdge(src, target);
95 },
96
Nils Diewald0e6992a2015-04-14 20:13:52 +000097 // Remove foundry and layer for labels
98 _clean : function (title) {
99 return title.replace(_TermRE, "$3");
100 },
101
102 // Parse the snippet
Akron98a933f2016-08-11 00:19:17 +0200103 _parse : function (parent, children, mark) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000104 for (var i in children) {
Akron98a933f2016-08-11 00:19:17 +0200105 var c = children[i];
Nils Diewald0e6992a2015-04-14 20:13:52 +0000106
Akron98a933f2016-08-11 00:19:17 +0200107 // Element node
108 if (c.nodeType == 1) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000109
Akron98a933f2016-08-11 00:19:17 +0200110 // Get title from html
111 if (c.getAttribute("title")) {
112 var title = this._clean(c.getAttribute("title"));
Nils Diewald0e6992a2015-04-14 20:13:52 +0000113
Akron98a933f2016-08-11 00:19:17 +0200114 // Add child node
115 var id = this._next++;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000116
Akron98a933f2016-08-11 00:19:17 +0200117 var obj = this._addNode(id, {
118 "class" : "middle",
119 "label" : title
120 });
121
122 if (mark !== undefined) {
123 obj.class += ' mark';
124 };
125
126 this._addEdge(parent, id);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000127
Akron98a933f2016-08-11 00:19:17 +0200128 // Check for next level
129 if (c.hasChildNodes())
130 this._parse(id, c.childNodes, mark);
131 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000132
Akron98a933f2016-08-11 00:19:17 +0200133 // Step further
134 else if (c.hasChildNodes()) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000135
Akron98a933f2016-08-11 00:19:17 +0200136 if (c.tagName === 'MARK') {
137 this._parse(parent, c.childNodes, true);
138 }
139 else {
140 this._parse(parent, c.childNodes, mark);
141 };
142 };
143 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000144
Akron98a933f2016-08-11 00:19:17 +0200145 // Text node
146 else if (c.nodeType == 3)
Nils Diewald0e6992a2015-04-14 20:13:52 +0000147
Akron98a933f2016-08-11 00:19:17 +0200148 if (c.nodeValue.match(/[-a-z0-9]/i)) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000149
Akron98a933f2016-08-11 00:19:17 +0200150 // Add child node
151 var id = this._next++;
152 this._addNode(id, {
153 "class" : "leaf",
154 "label" : c.nodeValue
155 });
156
157 this._addEdge(parent, id);
158 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000159 };
160 return this;
161 },
162
163 /**
164 * Center the viewport of the canvas
165 */
166 center : function () {
167 if (this._element === undefined)
168 return;
169
170 var treeDiv = this._element.parentNode;
171
172 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
173 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
174 // Reposition:
175 if (cWidth > treeWidth) {
176 var scrollValue = (cWidth - treeWidth) / 2;
177 treeDiv.scrollLeft = scrollValue;
178 };
179 },
180
Nils Diewald7148c6f2015-05-04 15:07:53 +0000181 /**
182 * Get the dom element of the tree view.
183 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000184 element : function () {
185 if (this._element !== undefined)
186 return this._element;
187
188 var g = this._graph;
189
190 dagre.layout(g);
191
192 var canvas = document.createElementNS(svgXmlns, 'svg');
193 this._element = canvas;
194
Nils Diewald4347ee92015-05-04 20:32:48 +0000195 var height = g.graph().height;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000196
197 // Create edges
198 g.edges().forEach(
199 function (e) {
200 var src = g.node(e.v);
201 var target = g.node(e.w);
202 var p = document.createElementNS(svgXmlns, 'path');
203 p.setAttributeNS(null, "d", _line(src, target));
204 p.classList.add('edge');
205 canvas.appendChild(p);
206 });
207
208 // Create nodes
209 g.nodes().forEach(
210 function (v) {
211 v = g.node(v);
212 var group = document.createElementNS(svgXmlns, 'g');
Akron98a933f2016-08-11 00:19:17 +0200213 group.setAttribute('class', v.class);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000214
215 // Add node box
216 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
Nils Diewald7148c6f2015-05-04 15:07:53 +0000217 rect.setAttribute('x', v.x - v.width / 2);
218 rect.setAttribute('y', v.y - v.height / 2);
219 rect.setAttribute('rx', 5);
220 rect.setAttribute('ry', 5);
221 rect.setAttribute('width', v.width);
222 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000223
224 if (v.class === 'root' && v.label === undefined) {
Nils Diewald7148c6f2015-05-04 15:07:53 +0000225 rect.setAttribute('width', v.height);
226 rect.setAttribute('x', v.x - v.height / 2);
227 rect.setAttribute('class', 'empty');
Nils Diewald0e6992a2015-04-14 20:13:52 +0000228 };
229
230 // Add label
231 if (v.label !== undefined) {
232 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
Nils Diewald4347ee92015-05-04 20:32:48 +0000233 var y = v.y - v.height / 2;
234 text.setAttribute('y', y);
Nils Diewald7148c6f2015-05-04 15:07:53 +0000235 text.setAttribute(
Nils Diewald0e6992a2015-04-14 20:13:52 +0000236 'transform',
237 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
238 );
239
Nils Diewald4347ee92015-05-04 20:32:48 +0000240 if (v.class === "leaf") {
241 text.setAttribute('title', v.label);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000242
Nils Diewald4347ee92015-05-04 20:32:48 +0000243 var labelPart = v.label.split(" ");
244 var n = 0;
245 for (var i = 0; i < labelPart.length; i++) {
246 if (labelPart[i].length === 0)
247 continue;
248
249 var tspan = document.createElementNS(svgXmlns, 'tspan');
250 tspan.appendChild(document.createTextNode(labelPart[i]));
251 if (n !== 0)
252 tspan.setAttribute('dy', LINEHEIGHT + 'pt');
253 else
254 n = 1;
255 tspan.setAttribute('x', v.x - v.width / 2);
256 y += LINEHEIGHT;
257 text.appendChild(tspan);
258 };
259
260 y += LINEHEIGHT;
261
262 // The text is below the canvas - readjust the height!
263 if (y > height)
264 height = y;
265 }
266 else {
267 var tspan = document.createElementNS(svgXmlns, 'tspan');
268 tspan.appendChild(document.createTextNode(v.label));
269 tspan.setAttribute('x', v.x - v.width / 2);
270 text.appendChild(tspan);
271 };
272 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000273 canvas.appendChild(group);
274 }
275 );
276
Nils Diewald4347ee92015-05-04 20:32:48 +0000277 canvas.setAttribute('width', g.graph().width);
278 canvas.setAttribute('height', height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000279 return this._element;
280 }
281 };
282});