blob: 9722f94c922befff19a94408ff6bfed0831f6605 [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
12 var WIDTH = 55, HEIGHT = 20;
13
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
182 canvas.setAttribute('height', g.graph().height);
183 canvas.setAttribute('width', g.graph().width);
184
185 // Create edges
186 g.edges().forEach(
187 function (e) {
188 var src = g.node(e.v);
189 var target = g.node(e.w);
190 var p = document.createElementNS(svgXmlns, 'path');
191 p.setAttributeNS(null, "d", _line(src, target));
192 p.classList.add('edge');
193 canvas.appendChild(p);
194 });
195
196 // Create nodes
197 g.nodes().forEach(
198 function (v) {
199 v = g.node(v);
200 var group = document.createElementNS(svgXmlns, 'g');
201 group.classList.add(v.class);
202
203 // Add node box
204 var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
Nils Diewald7148c6f2015-05-04 15:07:53 +0000205 rect.setAttribute('x', v.x - v.width / 2);
206 rect.setAttribute('y', v.y - v.height / 2);
207 rect.setAttribute('rx', 5);
208 rect.setAttribute('ry', 5);
209 rect.setAttribute('width', v.width);
210 rect.setAttribute('height', v.height);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000211
212 if (v.class === 'root' && v.label === undefined) {
Nils Diewald7148c6f2015-05-04 15:07:53 +0000213 rect.setAttribute('width', v.height);
214 rect.setAttribute('x', v.x - v.height / 2);
215 rect.setAttribute('class', 'empty');
Nils Diewald0e6992a2015-04-14 20:13:52 +0000216 };
217
218 // Add label
219 if (v.label !== undefined) {
220 var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
Nils Diewald7148c6f2015-05-04 15:07:53 +0000221 text.setAttribute('x', v.x - v.width / 2);
222 text.setAttribute('y', v.y - v.height / 2);
223 text.setAttribute(
Nils Diewald0e6992a2015-04-14 20:13:52 +0000224 'transform',
225 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
226 );
227
228 var tspan = document.createElementNS(svgXmlns, 'tspan');
229 tspan.appendChild(document.createTextNode(v.label));
230 text.appendChild(tspan);
231 };
232
233 canvas.appendChild(group);
234 }
235 );
236
237 return this._element;
238 }
239 };
240});