blob: e45d05ffdc59cdb44b9b764f7edd2558cc27db6f [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(
62 this._next++,
63 { "class" : "root" }
64 );
65
66 // Parse nodes from root
67 this._parse(0, html.childNodes);
68
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)
89 },
90
91 // Add new edge to graph
92 _addEdge : function (src, target) {
93 this._graph.setEdge(src, target);
94 },
95
Nils Diewald0e6992a2015-04-14 20:13:52 +000096 // Remove foundry and layer for labels
97 _clean : function (title) {
98 return title.replace(_TermRE, "$3");
99 },
100
101 // Parse the snippet
102 _parse : function (parent, children) {
103 for (var i in children) {
104 var c = children[i];
105
106 // Element node
107 if (c.nodeType == 1) {
108
109 // Get title from html
110 if (c.getAttribute("title")) {
111 var title = this._clean(c.getAttribute("title"));
112
113 // Add child node
114 var id = this._next++;
115
116 this._addNode(id, {
117 "class" : "middle",
118 "label" : title
119 });
120 this._addEdge(parent, id);
121
122 // Check for next level
123 if (c.hasChildNodes())
124 this._parse(id, c.childNodes);
125 }
126
127 // Step further
128 else if (c.hasChildNodes())
129 this._parse(parent, c.childNodes);
130 }
131
132 // Text node
133 else if (c.nodeType == 3)
134
135 if (c.nodeValue.match(/[-a-z0-9]/i)) {
136
137 // Add child node
138 var id = this._next++;
139 this._addNode(id, {
140 "class" : "leaf",
141 "label" : c.nodeValue
142 });
143
144 this._addEdge(parent, id);
145 };
146 };
147 return this;
148 },
149
150 /**
151 * Center the viewport of the canvas
152 */
153 center : function () {
154 if (this._element === undefined)
155 return;
156
157 var treeDiv = this._element.parentNode;
158
159 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
160 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
161 // Reposition:
162 if (cWidth > treeWidth) {
163 var scrollValue = (cWidth - treeWidth) / 2;
164 treeDiv.scrollLeft = scrollValue;
165 };
166 },
167
Nils Diewald7148c6f2015-05-04 15:07:53 +0000168 /**
169 * Get the dom element of the tree view.
170 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000171 element : function () {
172 if (this._element !== undefined)
173 return this._element;
174
175 var g = this._graph;
176
177 dagre.layout(g);
178
179 var canvas = document.createElementNS(svgXmlns, 'svg');
180 this._element = canvas;
181
Nils Diewald4347ee92015-05-04 20:32:48 +0000182 var height = g.graph().height;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000183
184 // Create edges
185 g.edges().forEach(
186 function (e) {
187 var src = g.node(e.v);
188 var target = g.node(e.w);
189 var p = document.createElementNS(svgXmlns, 'path');
190 p.setAttributeNS(null, "d", _line(src, target));
191 p.classList.add('edge');
192 canvas.appendChild(p);
193 });
194
195 // Create nodes
196 g.nodes().forEach(
197 function (v) {
198 v = g.node(v);
199 var group = document.createElementNS(svgXmlns, 'g');
200 group.classList.add(v.class);
201
202 // Add node box
203 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
Nils Diewald7148c6f2015-05-04 15:07:53 +0000204 rect.setAttribute('x', v.x - v.width / 2);
205 rect.setAttribute('y', v.y - v.height / 2);
206 rect.setAttribute('rx', 5);
207 rect.setAttribute('ry', 5);
208 rect.setAttribute('width', v.width);
209 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000210
211 if (v.class === 'root' && v.label === undefined) {
Nils Diewald7148c6f2015-05-04 15:07:53 +0000212 rect.setAttribute('width', v.height);
213 rect.setAttribute('x', v.x - v.height / 2);
214 rect.setAttribute('class', 'empty');
Nils Diewald0e6992a2015-04-14 20:13:52 +0000215 };
216
217 // Add label
218 if (v.label !== undefined) {
219 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
Nils Diewald4347ee92015-05-04 20:32:48 +0000220 var y = v.y - v.height / 2;
221 text.setAttribute('y', y);
Nils Diewald7148c6f2015-05-04 15:07:53 +0000222 text.setAttribute(
Nils Diewald0e6992a2015-04-14 20:13:52 +0000223 'transform',
224 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
225 );
226
Nils Diewald4347ee92015-05-04 20:32:48 +0000227 if (v.class === "leaf") {
228 text.setAttribute('title', v.label);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000229
Nils Diewald4347ee92015-05-04 20:32:48 +0000230 var labelPart = v.label.split(" ");
231 var n = 0;
232 for (var i = 0; i < labelPart.length; i++) {
233 if (labelPart[i].length === 0)
234 continue;
235
236 var tspan = document.createElementNS(svgXmlns, 'tspan');
237 tspan.appendChild(document.createTextNode(labelPart[i]));
238 if (n !== 0)
239 tspan.setAttribute('dy', LINEHEIGHT + 'pt');
240 else
241 n = 1;
242 tspan.setAttribute('x', v.x - v.width / 2);
243 y += LINEHEIGHT;
244 text.appendChild(tspan);
245 };
246
247 y += LINEHEIGHT;
248
249 // The text is below the canvas - readjust the height!
250 if (y > height)
251 height = y;
252 }
253 else {
254 var tspan = document.createElementNS(svgXmlns, 'tspan');
255 tspan.appendChild(document.createTextNode(v.label));
256 tspan.setAttribute('x', v.x - v.width / 2);
257 text.appendChild(tspan);
258 };
259 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000260 canvas.appendChild(group);
261 }
262 );
263
Nils Diewald4347ee92015-05-04 20:32:48 +0000264 canvas.setAttribute('width', g.graph().width);
265 canvas.setAttribute('height', height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000266 return this._element;
267 }
268 };
269});