blob: dc4f229193d525ace0b8a638eafbe04181662b79 [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 () {
Akron6f32f822016-11-10 00:23:40 +0100185
186 // First clone element
187 var svgWrapper = document.createElement('div')
188 svgWrapper.innerHTML = this.element().outerHTML;
189 var svg = svgWrapper.firstChild;
190
191 var style = document.createElementNS(svgXmlns, 'style');
192 svg.getElementsByTagName('defs')[0].appendChild(style);
193
194 style.innerHTML =
195 'path.edge ' + '{ stroke: black; stroke-width: 2pt; fill: none; }' +
196 'g.root rect.empty,' +
197 'g.middle rect' + '{ stroke: black; stroke-width: 2pt; fill: #bbb; }' +
198 'g.leaf > rect ' + '{ display: none }' +
199 'g > text > tspan ' + '{ text-anchor: middle; font-size: 9pt }' +
200 'g.leaf > text > tspan ' + '{ font-size: 10pt; overflow: visible; }';
201
202 return btoa(unescape(encodeURIComponent(svg.outerHTML)));
Akronc56cf2d2016-11-09 22:02:38 +0100203 },
204
Nils Diewald7148c6f2015-05-04 15:07:53 +0000205 /**
206 * Get the dom element of the tree view.
207 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000208 element : function () {
209 if (this._element !== undefined)
Akronc56cf2d2016-11-09 22:02:38 +0100210 return this._element;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000211
212 var g = this._graph;
213
214 dagre.layout(g);
Akronc56cf2d2016-11-09 22:02:38 +0100215
Nils Diewald0e6992a2015-04-14 20:13:52 +0000216 var canvas = document.createElementNS(svgXmlns, 'svg');
217 this._element = canvas;
218
Akron6f32f822016-11-10 00:23:40 +0100219 canvas.appendChild(document.createElementNS(svgXmlns, 'defs'));
220
Nils Diewald4347ee92015-05-04 20:32:48 +0000221 var height = g.graph().height;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000222
223 // Create edges
224 g.edges().forEach(
Akronc56cf2d2016-11-09 22:02:38 +0100225 function (e) {
226 var src = g.node(e.v);
227 var target = g.node(e.w);
228 var p = document.createElementNS(svgXmlns, 'path');
229 p.setAttributeNS(null, "d", _line(src, target));
230 p.classList.add('edge');
231 canvas.appendChild(p);
232 });
Nils Diewald0e6992a2015-04-14 20:13:52 +0000233
234 // Create nodes
235 g.nodes().forEach(
Akronc56cf2d2016-11-09 22:02:38 +0100236 function (v) {
237 v = g.node(v);
238 var group = document.createElementNS(svgXmlns, 'g');
239 group.setAttribute('class', v.class);
240
241 // Add node box
242 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
243 rect.setAttribute('x', v.x - v.width / 2);
244 rect.setAttribute('y', v.y - v.height / 2);
245 rect.setAttribute('rx', 5);
246 rect.setAttribute('ry', 5);
247 rect.setAttribute('width', v.width);
248 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000249
Akronc56cf2d2016-11-09 22:02:38 +0100250 if (v.class === 'root' && v.label === undefined) {
251 rect.setAttribute('width', v.height);
252 rect.setAttribute('x', v.x - v.height / 2);
253 rect.setAttribute('class', 'empty');
254 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000255
Akronc56cf2d2016-11-09 22:02:38 +0100256 // Add label
257 if (v.label !== undefined) {
258 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
259 var y = v.y - v.height / 2;
260 text.setAttribute('y', y);
261 text.setAttribute(
262 'transform',
263 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
264 );
265
266 if (v.class === "leaf") {
267 text.setAttribute('title', v.label);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000268
Akronc56cf2d2016-11-09 22:02:38 +0100269 var labelPart = v.label.split(" ");
270 var n = 0;
271 for (var i = 0; i < labelPart.length; i++) {
272 if (labelPart[i].length === 0)
273 continue;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000274
Akronc56cf2d2016-11-09 22:02:38 +0100275 var tspan = document.createElementNS(svgXmlns, 'tspan');
276 tspan.appendChild(document.createTextNode(labelPart[i]));
277 if (n !== 0)
278 tspan.setAttribute('dy', LINEHEIGHT + 'pt');
279 else
280 n = 1;
281 tspan.setAttribute('x', v.x - v.width / 2);
282 y += LINEHEIGHT;
283 text.appendChild(tspan);
284 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000285
Akronc56cf2d2016-11-09 22:02:38 +0100286 y += LINEHEIGHT;
Nils Diewald4347ee92015-05-04 20:32:48 +0000287
Akronc56cf2d2016-11-09 22:02:38 +0100288 // The text is below the canvas - readjust the height!
289 if (y > height)
290 height = y;
291 }
292 else {
293 var tspan = document.createElementNS(svgXmlns, 'tspan');
294 tspan.appendChild(document.createTextNode(v.label));
295 tspan.setAttribute('x', v.x - v.width / 2);
296 text.appendChild(tspan);
297 };
298 };
299 canvas.appendChild(group);
300 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000301 );
302
Nils Diewald4347ee92015-05-04 20:32:48 +0000303 canvas.setAttribute('width', g.graph().width);
304 canvas.setAttribute('height', height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000305 return this._element;
306 }
307 };
308});