Commit | Line | Data |
d3b8a135 |
1 | // based closely on http://thejit.org/static/v20/Jit/Examples/Treemap/example2.html |
b2fc39a5 |
2 | var labelType, useGradients, nativeTextSupport, animate; |
3 | |
4 | (function() { |
5 | var ua = navigator.userAgent, |
6 | iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i), |
7 | typeOfCanvas = typeof HTMLCanvasElement, |
8 | nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'), |
9 | textSupport = nativeCanvasSupport |
10 | && (typeof document.createElement('canvas').getContext('2d').fillText == 'function'); |
11 | //I'm setting this based on the fact that ExCanvas provides text support for IE |
12 | //and that as of today iPhone/iPad current text support is lame |
13 | labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML'; |
14 | nativeTextSupport = labelType == 'Native'; |
15 | useGradients = nativeCanvasSupport; |
16 | animate = !(iStuff || !nativeCanvasSupport); |
c065a0c3 |
17 | console.log({ "labelType":labelType, "useGradients":useGradients, "nativeTextSupport":nativeTextSupport }); |
b2fc39a5 |
18 | })(); |
19 | |
20 | var Log = { |
21 | elem: false, |
22 | write: function(text){ |
23 | if (!this.elem) |
24 | this.elem = document.getElementById('log'); |
25 | this.elem.innerHTML = text; |
26 | this.elem.style.left = (500 - this.elem.offsetWidth / 2) + 'px'; |
27 | } |
28 | }; |
29 | |
ee6e37bf |
30 | /** |
31 | * Convert number of bytes into human readable format |
32 | * |
33 | * @param integer bytes Number of bytes to convert |
34 | * @param integer precision Number of digits after the decimal separator |
35 | * @return string |
36 | * via http://codeaid.net/javascript/convert-size-in-bytes-to-human-readable-format-(javascript) |
37 | */ |
38 | function bytesToSize(bytes, precision) { |
39 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; |
40 | var posttxt = 0; |
41 | while( bytes >= 1024 ) { |
42 | posttxt++; |
43 | bytes = bytes / 1024; |
44 | } |
45 | var num = (posttxt) ? Number(bytes).toFixed(precision) : bytes; |
46 | return num + " " + sizes[posttxt]; |
47 | } |
48 | |
49 | |
5ab994e6 |
50 | // http://stackoverflow.com/questions/5199901/how-to-sort-an-associative-array-by-its-values-in-javascript |
51 | function bySortedValue(obj, comparitor, callback, context) { |
52 | var tuples = []; |
53 | for (var key in obj) { |
54 | if (obj.hasOwnProperty(key)) { |
55 | tuples.push([key, obj[key]]); |
56 | } |
57 | } |
58 | |
59 | tuples.sort(comparitor); |
60 | |
61 | if (callback) { |
62 | var length = tuples.length; |
63 | while (length--) callback.call(context, tuples[length][0], tuples[length][1]); |
64 | } |
65 | return tuples; |
66 | } |
67 | |
68 | |
b2fc39a5 |
69 | |
8f4f09eb |
70 | function request_jit_tree(nodeId, level, depth, onComplete){ |
ee6e37bf |
71 | var params = { };//logarea: 0 }; |
8f4f09eb |
72 | jQuery.getJSON('jit_tree/'+nodeId+'/'+depth, params, onComplete); |
e8f4c506 |
73 | } |
74 | |
75 | |
b2fc39a5 |
76 | function init(){ |
8f4f09eb |
77 | var levelsToShow = 2; |
b2fc39a5 |
78 | //init TreeMap |
79 | var tm = new $jit.TM.Squarified({ |
80 | //where to inject the visualization |
81 | injectInto: 'infovis', |
82 | //show only one tree level |
8f4f09eb |
83 | levelsToShow: levelsToShow, |
b2fc39a5 |
84 | //parent box title heights |
a74b795d |
85 | titleHeight: 14, |
b2fc39a5 |
86 | //enable animations |
87 | animate: animate, |
88 | //box offsets |
89 | offset: 1, |
90 | //use canvas text |
d3b8a135 |
91 | // XXX disabled to allow the onMouseEnter/onMouseLeave Events to fire to set the blue border |
ee6e37bf |
92 | XXX_Label: { |
b2fc39a5 |
93 | type: labelType, |
a74b795d |
94 | size: 10, |
b2fc39a5 |
95 | family: 'Tahoma, Verdana, Arial' |
96 | }, |
97 | //enable specific canvas styles |
98 | //when rendering nodes |
99 | Node: { |
100 | CanvasStyles: { |
101 | shadowBlur: 0, |
102 | shadowColor: '#000' |
103 | } |
104 | }, |
105 | //Attach left and right click events |
106 | Events: { |
107 | enable: true, |
108 | onClick: function(node) { |
109 | if(node) tm.enter(node); |
110 | }, |
111 | onRightClick: function() { |
112 | tm.out(); |
113 | }, |
114 | //change node styles and canvas styles |
115 | //when hovering a node |
116 | onMouseEnter: function(node, eventInfo) { |
117 | if(node) { |
118 | //add node selected styles and replot node |
119 | node.setCanvasStyle('shadowBlur', 7); |
120 | node.setData('color', '#888'); |
121 | tm.fx.plotNode(node, tm.canvas); |
122 | tm.labels.plotLabel(tm.canvas, node); |
123 | } |
124 | }, |
125 | onMouseLeave: function(node) { |
126 | if(node) { |
127 | node.removeData('color'); |
128 | node.removeCanvasStyle('shadowBlur'); |
129 | tm.plot(); |
130 | } |
131 | } |
132 | }, |
133 | //duration of the animations |
a3382177 |
134 | duration: 300, |
b2fc39a5 |
135 | //Enable tips |
136 | Tips: { |
137 | enable: true, |
138 | type: 'Native', |
139 | //add positioning offsets |
140 | offsetX: 20, |
141 | offsetY: 20, |
142 | //implement the onShow method to |
143 | //add content to the tooltip when a node |
144 | //is hovered |
145 | onShow: function(tip, node, isLeaf, domElement) { |
d3b8a135 |
146 | |
147 | // XXX all this needs html escaping |
b2fc39a5 |
148 | var data = node.data; |
e8f4c506 |
149 | var html = "<div class=\"tip-title\">" |
ee6e37bf |
150 | + (data.title ? "\""+data.title+"\"" : "") |
e8f4c506 |
151 | + " " + data.name |
152 | + "</div><div class=\"tip-text\">"; |
bb66f8a1 |
153 | |
5ab994e6 |
154 | html += "<br />"; |
ee6e37bf |
155 | html += sprintf("Memory use: %s<br />", bytesToSize(data.self_size+data.kids_size,2)); |
156 | if (data.kids_size) { |
157 | html += sprintf("Child use: %s<br />", bytesToSize(data.kids_size,2)); |
158 | } |
5ab994e6 |
159 | if (data.self_size) { |
c98b4b83 |
160 | if (data.kids_size) |
161 | html += sprintf("Own use: %s<br />", bytesToSize(data.self_size,2)); |
ee6e37bf |
162 | html += sprintf("<div style=\"color:grey\">"); |
5ab994e6 |
163 | bySortedValue(data.leaves, |
164 | function(a, b) { return a[1] - b[1] }, |
ee6e37bf |
165 | function(k, v) { html += sprintf(" %9s: %s<br />", k, bytesToSize(v,2)); |
5ab994e6 |
166 | }); |
ee6e37bf |
167 | html += sprintf("</div>"); |
5ab994e6 |
168 | } |
ee6e37bf |
169 | html += "<br />"; |
5ab994e6 |
170 | |
c98b4b83 |
171 | html += sprintf("<div style=\"color:grey\">"); |
e8f4c506 |
172 | |
c98b4b83 |
173 | if (1) { |
5ab994e6 |
174 | html += sprintf("Attributes:<br />"); |
175 | bySortedValue(data.attr, |
176 | function(a, b) { return a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0 }, |
177 | function(k, v) { html += sprintf(" %10s: %5d<br />", k, v); |
178 | }); |
179 | html += "<br />"; |
ee6e37bf |
180 | } |
5ab994e6 |
181 | |
bb66f8a1 |
182 | if (data.child_count) { |
bcedcc81 |
183 | //html += sprintf("Children: %d of %d<br />", data.child_count, data.kids_node_count); |
184 | html += sprintf("Children: %d<br />", data.kids_node_count); |
b2fc39a5 |
185 | } |
5ab994e6 |
186 | html += sprintf("Id: %s%s<br />", node.id, data._ids_merged ? data._ids_merged : ""); |
5a78486c |
187 | html += sprintf("Depth: %d<br />", data.depth); |
188 | html += sprintf("Parent: %d<br />", data.parent_id); |
bb66f8a1 |
189 | |
c98b4b83 |
190 | html += JSON.stringify(data.attr, undefined, 4); |
e8f4c506 |
191 | //html += JSON.stringify(data, undefined, 4); |
ee6e37bf |
192 | html += sprintf("</div>"); |
e8f4c506 |
193 | |
b2fc39a5 |
194 | tip.innerHTML = html; |
195 | } |
196 | }, |
197 | //Implement this method for retrieving a requested |
198 | //subtree that has as root a node with id = nodeId, |
199 | //and level as depth. This method could also make a server-side |
200 | //call for the requested subtree. When completed, the onComplete |
201 | //callback method should be called. |
202 | request: function(nodeId, level, onComplete){ |
8f4f09eb |
203 | request_jit_tree(nodeId, level, levelsToShow, function(data) { |
e8f4c506 |
204 | console.log("Fetched node "+nodeId); |
f9d8678b |
205 | console.log(data); |
b2fc39a5 |
206 | onComplete.onComplete(nodeId, data); |
207 | }); |
a74b795d |
208 | // XXX workaround jit bug where old tooltip is still shown till the |
209 | // mouse moves |
210 | jQuery("#_tooltip").fadeOut("fast"); |
b2fc39a5 |
211 | }, |
212 | //Add the name of the node in the corresponding label |
213 | //This method is called once, on label creation and only for DOM labels. |
214 | onCreateLabel: function(domElement, node){ |
215 | domElement.innerHTML = node.name; |
c065a0c3 |
216 | |
217 | // this doesn't work with Label:{} above |
218 | var style = domElement.style; |
219 | style.display = ''; |
220 | style.border = '1px solid transparent'; |
221 | domElement.onmouseover = function() { |
222 | style.border = '1px solid #9FD4FF'; |
223 | }; |
224 | domElement.onmouseout = function() { |
225 | style.border = '1px solid transparent'; |
226 | }; |
227 | |
ee6e37bf |
228 | }, |
229 | onPlaceLabel: function(domElement, node){ }, |
b2fc39a5 |
230 | }); |
e8f4c506 |
231 | |
8f4f09eb |
232 | request_jit_tree(1, 0, levelsToShow, function(data) { |
f9d8678b |
233 | console.log(data); |
b2fc39a5 |
234 | tm.loadJSON(data); |
235 | tm.refresh(); |
236 | }); |
b2fc39a5 |
237 | |
e8f4c506 |
238 | //add event to buttons |
239 | $jit.util.addEvent($jit.id('back'), 'click', function() { tm.out() }); |
240 | $jit.util.addEvent($jit.id('logarea'), 'onchange', function() { tm.refresh() }); |
241 | |
b2fc39a5 |
242 | } |
e8f4c506 |
243 | |
244 | |