blob: 88f054201a341d08f41e50baab16e3dfd1477360 [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
Akron0988d882017-11-10 16:13:12 +0100165 // Dummy method to be compatible with relTree
166 show : function () {
167 return;
168 },
169
Nils Diewald0e6992a2015-04-14 20:13:52 +0000170 /**
171 * Center the viewport of the canvas
Akron0988d882017-11-10 16:13:12 +0100172 * TODO:
173 * This is identical to relations
Nils Diewald0e6992a2015-04-14 20:13:52 +0000174 */
175 center : function () {
176 if (this._element === undefined)
Akronc56cf2d2016-11-09 22:02:38 +0100177 return;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000178
179 var treeDiv = this._element.parentNode;
180
181 var cWidth = parseFloat(window.getComputedStyle(this._element).width);
182 var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
183 // Reposition:
184 if (cWidth > treeWidth) {
Akronc56cf2d2016-11-09 22:02:38 +0100185 var scrollValue = (cWidth - treeWidth) / 2;
186 treeDiv.scrollLeft = scrollValue;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000187 };
188 },
189
Akronc56cf2d2016-11-09 22:02:38 +0100190
191 toBase64 : function () {
Akron6f32f822016-11-10 00:23:40 +0100192
193 // First clone element
194 var svgWrapper = document.createElement('div')
195 svgWrapper.innerHTML = this.element().outerHTML;
196 var svg = svgWrapper.firstChild;
197
198 var style = document.createElementNS(svgXmlns, 'style');
199 svg.getElementsByTagName('defs')[0].appendChild(style);
200
201 style.innerHTML =
202 'path.edge ' + '{ stroke: black; stroke-width: 2pt; fill: none; }' +
203 'g.root rect.empty,' +
204 'g.middle rect' + '{ stroke: black; stroke-width: 2pt; fill: #bbb; }' +
205 'g.leaf > rect ' + '{ display: none }' +
206 'g > text > tspan ' + '{ text-anchor: middle; font-size: 9pt }' +
207 'g.leaf > text > tspan ' + '{ font-size: 10pt; overflow: visible; }';
208
209 return btoa(unescape(encodeURIComponent(svg.outerHTML)));
Akronc56cf2d2016-11-09 22:02:38 +0100210 },
211
Nils Diewald7148c6f2015-05-04 15:07:53 +0000212 /**
213 * Get the dom element of the tree view.
214 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000215 element : function () {
216 if (this._element !== undefined)
Akronc56cf2d2016-11-09 22:02:38 +0100217 return this._element;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000218
219 var g = this._graph;
220
221 dagre.layout(g);
Akronc56cf2d2016-11-09 22:02:38 +0100222
Nils Diewald0e6992a2015-04-14 20:13:52 +0000223 var canvas = document.createElementNS(svgXmlns, 'svg');
224 this._element = canvas;
225
Akron6f32f822016-11-10 00:23:40 +0100226 canvas.appendChild(document.createElementNS(svgXmlns, 'defs'));
227
Nils Diewald4347ee92015-05-04 20:32:48 +0000228 var height = g.graph().height;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000229
230 // Create edges
231 g.edges().forEach(
Akronc56cf2d2016-11-09 22:02:38 +0100232 function (e) {
233 var src = g.node(e.v);
234 var target = g.node(e.w);
235 var p = document.createElementNS(svgXmlns, 'path');
236 p.setAttributeNS(null, "d", _line(src, target));
237 p.classList.add('edge');
238 canvas.appendChild(p);
239 });
Nils Diewald0e6992a2015-04-14 20:13:52 +0000240
241 // Create nodes
242 g.nodes().forEach(
Akronc56cf2d2016-11-09 22:02:38 +0100243 function (v) {
244 v = g.node(v);
245 var group = document.createElementNS(svgXmlns, 'g');
246 group.setAttribute('class', v.class);
247
248 // Add node box
249 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
250 rect.setAttribute('x', v.x - v.width / 2);
251 rect.setAttribute('y', v.y - v.height / 2);
252 rect.setAttribute('rx', 5);
253 rect.setAttribute('ry', 5);
254 rect.setAttribute('width', v.width);
255 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000256
Akronc56cf2d2016-11-09 22:02:38 +0100257 if (v.class === 'root' && v.label === undefined) {
258 rect.setAttribute('width', v.height);
259 rect.setAttribute('x', v.x - v.height / 2);
260 rect.setAttribute('class', 'empty');
261 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000262
Akronc56cf2d2016-11-09 22:02:38 +0100263 // Add label
264 if (v.label !== undefined) {
265 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
266 var y = v.y - v.height / 2;
267 text.setAttribute('y', y);
268 text.setAttribute(
269 'transform',
270 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
271 );
272
273 if (v.class === "leaf") {
274 text.setAttribute('title', v.label);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000275
Akronc56cf2d2016-11-09 22:02:38 +0100276 var labelPart = v.label.split(" ");
277 var n = 0;
278 for (var i = 0; i < labelPart.length; i++) {
279 if (labelPart[i].length === 0)
280 continue;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000281
Akronc56cf2d2016-11-09 22:02:38 +0100282 var tspan = document.createElementNS(svgXmlns, 'tspan');
283 tspan.appendChild(document.createTextNode(labelPart[i]));
284 if (n !== 0)
285 tspan.setAttribute('dy', LINEHEIGHT + 'pt');
286 else
287 n = 1;
288 tspan.setAttribute('x', v.x - v.width / 2);
289 y += LINEHEIGHT;
290 text.appendChild(tspan);
291 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000292
Akronc56cf2d2016-11-09 22:02:38 +0100293 y += LINEHEIGHT;
Nils Diewald4347ee92015-05-04 20:32:48 +0000294
Akronc56cf2d2016-11-09 22:02:38 +0100295 // The text is below the canvas - readjust the height!
296 if (y > height)
297 height = y;
298 }
299 else {
300 var tspan = document.createElementNS(svgXmlns, 'tspan');
301 tspan.appendChild(document.createTextNode(v.label));
302 tspan.setAttribute('x', v.x - v.width / 2);
303 text.appendChild(tspan);
304 };
305 };
306 canvas.appendChild(group);
307 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000308 );
309
Nils Diewald4347ee92015-05-04 20:32:48 +0000310 canvas.setAttribute('width', g.graph().width);
311 canvas.setAttribute('height', height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000312 return this._element;
313 }
314 };
315});