blob: 5e39a1a41989a2315b193eb500980906e8666030 [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).
38 _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);
45
46 // Create html for traversal
47 var html = document.createElement("div");
48 html.innerHTML = snippet;
49 var g = new dagre.graphlib.Graph({
50 "directed" : true
51 });
52 g.setGraph({
53 "nodesep" : 35,
54 "ranksep" : 15,
55 "marginx" : 40,
56 "marginy" : 10
57 });
58 g.setDefaultEdgeLabel({});
59
60 this._graph = g;
61
62 // This is a new root
63 this._addNode(
Akron98a933f2016-08-11 00:19:17 +020064 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)
73 g.removeNode(0);
74
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) {
Akron98a933f2016-08-11 00:19:17 +0200107 var c = children[i];
Nils Diewald0e6992a2015-04-14 20:13:52 +0000108
Akron98a933f2016-08-11 00:19:17 +0200109 // Element node
110 if (c.nodeType == 1) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000111
Akron98a933f2016-08-11 00:19:17 +0200112 // 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
Akron98a933f2016-08-11 00:19:17 +0200116 // Add child node
117 var id = this._next++;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000118
Akron98a933f2016-08-11 00:19:17 +0200119 var obj = this._addNode(id, {
120 "class" : "middle",
121 "label" : title
122 });
123
124 if (mark !== undefined) {
125 obj.class += ' mark';
126 };
127
128 this._addEdge(parent, id);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000129
Akron98a933f2016-08-11 00:19:17 +0200130 // Check for next level
131 if (c.hasChildNodes())
132 this._parse(id, c.childNodes, mark);
133 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000134
Akron98a933f2016-08-11 00:19:17 +0200135 // 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') {
139 this._parse(parent, c.childNodes, true);
140 }
141 else {
142 this._parse(parent, c.childNodes, mark);
143 };
144 };
145 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000146
Akron98a933f2016-08-11 00:19:17 +0200147 // Text node
148 else if (c.nodeType == 3)
Nils Diewald0e6992a2015-04-14 20:13:52 +0000149
Akron98a933f2016-08-11 00:19:17 +0200150 if (c.nodeValue.match(/[-a-z0-9]/i)) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000151
Akron98a933f2016-08-11 00:19:17 +0200152 // Add child node
153 var id = this._next++;
154 this._addNode(id, {
155 "class" : "leaf",
156 "label" : c.nodeValue
157 });
158
159 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)
170 return;
171
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) {
178 var scrollValue = (cWidth - treeWidth) / 2;
179 treeDiv.scrollLeft = scrollValue;
180 };
181 },
182
Nils Diewald7148c6f2015-05-04 15:07:53 +0000183 /**
184 * Get the dom element of the tree view.
185 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000186 element : function () {
187 if (this._element !== undefined)
188 return this._element;
189
190 var g = this._graph;
191
192 dagre.layout(g);
193
194 var canvas = document.createElementNS(svgXmlns, 'svg');
195 this._element = canvas;
196
Nils Diewald4347ee92015-05-04 20:32:48 +0000197 var height = g.graph().height;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000198
199 // Create edges
200 g.edges().forEach(
201 function (e) {
202 var src = g.node(e.v);
203 var target = g.node(e.w);
204 var p = document.createElementNS(svgXmlns, 'path');
205 p.setAttributeNS(null, "d", _line(src, target));
206 p.classList.add('edge');
207 canvas.appendChild(p);
208 });
209
210 // Create nodes
211 g.nodes().forEach(
212 function (v) {
213 v = g.node(v);
214 var group = document.createElementNS(svgXmlns, 'g');
Akron98a933f2016-08-11 00:19:17 +0200215 group.setAttribute('class', v.class);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000216
217 // Add node box
218 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
Nils Diewald7148c6f2015-05-04 15:07:53 +0000219 rect.setAttribute('x', v.x - v.width / 2);
220 rect.setAttribute('y', v.y - v.height / 2);
221 rect.setAttribute('rx', 5);
222 rect.setAttribute('ry', 5);
223 rect.setAttribute('width', v.width);
224 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000225
226 if (v.class === 'root' && v.label === undefined) {
Nils Diewald7148c6f2015-05-04 15:07:53 +0000227 rect.setAttribute('width', v.height);
228 rect.setAttribute('x', v.x - v.height / 2);
229 rect.setAttribute('class', 'empty');
Nils Diewald0e6992a2015-04-14 20:13:52 +0000230 };
231
232 // Add label
233 if (v.label !== undefined) {
234 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
Nils Diewald4347ee92015-05-04 20:32:48 +0000235 var y = v.y - v.height / 2;
236 text.setAttribute('y', y);
Nils Diewald7148c6f2015-05-04 15:07:53 +0000237 text.setAttribute(
Nils Diewald0e6992a2015-04-14 20:13:52 +0000238 'transform',
239 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
240 );
241
Nils Diewald4347ee92015-05-04 20:32:48 +0000242 if (v.class === "leaf") {
243 text.setAttribute('title', v.label);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000244
Nils Diewald4347ee92015-05-04 20:32:48 +0000245 var labelPart = v.label.split(" ");
246 var n = 0;
247 for (var i = 0; i < labelPart.length; i++) {
248 if (labelPart[i].length === 0)
249 continue;
250
251 var tspan = document.createElementNS(svgXmlns, 'tspan');
252 tspan.appendChild(document.createTextNode(labelPart[i]));
253 if (n !== 0)
254 tspan.setAttribute('dy', LINEHEIGHT + 'pt');
255 else
256 n = 1;
257 tspan.setAttribute('x', v.x - v.width / 2);
258 y += LINEHEIGHT;
259 text.appendChild(tspan);
260 };
261
262 y += LINEHEIGHT;
263
264 // The text is below the canvas - readjust the height!
265 if (y > height)
266 height = y;
267 }
268 else {
269 var tspan = document.createElementNS(svgXmlns, 'tspan');
270 tspan.appendChild(document.createTextNode(v.label));
271 tspan.setAttribute('x', v.x - v.width / 2);
272 text.appendChild(tspan);
273 };
274 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000275 canvas.appendChild(group);
276 }
277 );
278
Nils Diewald4347ee92015-05-04 20:32:48 +0000279 canvas.setAttribute('width', g.graph().width);
280 canvas.setAttribute('height', height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000281 return this._element;
282 }
283 };
284});