1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | ;(function() {
|
22 | var kBorderWidth = 1;
|
23 |
|
24 |
|
25 |
|
26 | var kPadding = 4;
|
27 |
|
28 |
|
29 | var kAspectRatio = 1.2;
|
30 |
|
31 | var focused = null;
|
32 |
|
33 | function focus(tree) {
|
34 | focused = tree;
|
35 |
|
36 |
|
37 | var level = 0;
|
38 | var root = tree;
|
39 | while (root.parent) {
|
40 | root = root.parent;
|
41 | level += 1;
|
42 | for (var i = 0, sibling; sibling = root.children[i]; ++i) {
|
43 | if (sibling.dom)
|
44 | sibling.dom.style.zIndex = 0;
|
45 | }
|
46 | }
|
47 | var width = root.dom.offsetWidth;
|
48 | var height = root.dom.offsetHeight;
|
49 |
|
50 | for (var t = tree; t.parent; t = t.parent) {
|
51 |
|
52 |
|
53 | position(t.dom, -kBorderWidth, -kBorderWidth, width, height);
|
54 | t.dom.style.zIndex = 1;
|
55 | }
|
56 |
|
57 | layout(tree, level, width, height);
|
58 | }
|
59 |
|
60 | function makeDom(tree, level) {
|
61 | var dom = document.createElement('div');
|
62 | dom.style.zIndex = 1;
|
63 | dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4);
|
64 | if (tree.data['$symbol']) {
|
65 | dom.className += (' webtreemap-symbol-' +
|
66 | tree.data['$symbol'].replace(' ', '_'));
|
67 | }
|
68 | if (tree.data['$dominant_symbol']) {
|
69 | dom.className += (' webtreemap-symbol-' +
|
70 | tree.data['$dominant_symbol'].replace(' ', '_'));
|
71 | dom.className += (' webtreemap-aggregate');
|
72 | }
|
73 |
|
74 | dom.onmousedown = function(e) {
|
75 | if (e.button == 0) {
|
76 | if (focused && tree == focused && focused.parent) {
|
77 | focus(focused.parent);
|
78 | } else {
|
79 | focus(tree);
|
80 | }
|
81 | }
|
82 | e.stopPropagation();
|
83 | return true;
|
84 | };
|
85 |
|
86 | var caption = document.createElement('div');
|
87 | caption.className = 'webtreemap-caption';
|
88 | caption.innerHTML = tree.name;
|
89 | dom.appendChild(caption);
|
90 | dom.title = tree.name;
|
91 |
|
92 | tree.dom = dom;
|
93 | return dom;
|
94 | }
|
95 |
|
96 | function position(dom, x, y, width, height) {
|
97 |
|
98 | width -= kBorderWidth*2;
|
99 | height -= kBorderWidth*2;
|
100 |
|
101 | dom.style.left = x + 'px';
|
102 | dom.style.top = y + 'px';
|
103 | dom.style.width = Math.max(width, 0) + 'px';
|
104 | dom.style.height = Math.max(height, 0) + 'px';
|
105 | }
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | function selectSpan(nodes, space, start) {
|
114 |
|
115 |
|
116 |
|
117 | var node = nodes[start];
|
118 | var rmin = node.data['$area'];
|
119 | var rmax = rmin;
|
120 | var rsum = 0;
|
121 | var last_score = 0;
|
122 | for (var end = start; node = nodes[end]; ++end) {
|
123 | var size = node.data['$area'];
|
124 | if (size < rmin)
|
125 | rmin = size;
|
126 | if (size > rmax)
|
127 | rmax = size;
|
128 | rsum += size;
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | var score = Math.max(space*space*rmax / (rsum*rsum),
|
135 | kAspectRatio*rsum*rsum / (space*space*rmin));
|
136 | if (last_score && score > last_score) {
|
137 | rsum -= size;
|
138 | break;
|
139 | }
|
140 | last_score = score;
|
141 | }
|
142 | return [end, rsum];
|
143 | }
|
144 |
|
145 | function layout(tree, level, width, height) {
|
146 | if (!('children' in tree))
|
147 | return;
|
148 |
|
149 | var total = tree.data['$area'];
|
150 |
|
151 |
|
152 | var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2;
|
153 | x1 += kPadding; y1 += kPadding;
|
154 | x2 -= kPadding; y2 -= kPadding;
|
155 | y1 += 14;
|
156 |
|
157 | var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1)));
|
158 |
|
159 |
|
160 |
|
161 | if (!tree.children.sorted) {
|
162 | tree.children.sort(function (a, b) {
|
163 | return b.data['$area'] - a.data['$area'];
|
164 | });
|
165 | tree.children.sorted = true;
|
166 | }
|
167 |
|
168 | for (var start = 0, child; child = tree.children[start]; ++start) {
|
169 | if (x2 - x1 < 60 || y2 - y1 < 40) {
|
170 | if (child.dom) {
|
171 | child.dom.style.zIndex = 0;
|
172 | position(child.dom, -2, -2, 0, 0);
|
173 | }
|
174 | continue;
|
175 | }
|
176 |
|
177 |
|
178 | var ysplit = ((y2 - y1) / (x2 - x1)) > kAspectRatio;
|
179 |
|
180 | var space;
|
181 | if (ysplit)
|
182 | space = (y2 - y1) * pixels_to_units;
|
183 | else
|
184 | space = (x2 - x1) * pixels_to_units;
|
185 |
|
186 | var span = selectSpan(tree.children, space, start);
|
187 | var end = span[0], rsum = span[1];
|
188 |
|
189 |
|
190 |
|
191 | var x = x1, y = y1;
|
192 | for (var i = start; i < end; ++i) {
|
193 | child = tree.children[i];
|
194 | if (!child.dom) {
|
195 | child.parent = tree;
|
196 | child.dom = makeDom(child, level + 1);
|
197 | tree.dom.appendChild(child.dom);
|
198 | } else {
|
199 | child.dom.style.zIndex = 1;
|
200 | }
|
201 | var size = child.data['$area'];
|
202 | var frac = size / rsum;
|
203 | if (ysplit) {
|
204 | width = rsum / space;
|
205 | height = size / width;
|
206 | } else {
|
207 | height = rsum / space;
|
208 | width = size / height;
|
209 | }
|
210 | width /= pixels_to_units;
|
211 | height /= pixels_to_units;
|
212 | width = Math.round(width);
|
213 | height = Math.round(height);
|
214 | position(child.dom, x, y, width, height);
|
215 | if ('children' in child) {
|
216 | layout(child, level + 1, width, height);
|
217 | }
|
218 | if (ysplit)
|
219 | y += height;
|
220 | else
|
221 | x += width;
|
222 | }
|
223 |
|
224 |
|
225 | if (ysplit)
|
226 | x1 += Math.round((rsum / space) / pixels_to_units);
|
227 | else
|
228 | y1 += Math.round((rsum / space) / pixels_to_units);
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | start = end - 1;
|
234 | }
|
235 | }
|
236 |
|
237 | function appendTreemap(dom, data) {
|
238 | var style = getComputedStyle(dom, null);
|
239 | var width = parseInt(style.width);
|
240 | var height = parseInt(style.height);
|
241 | if (!data.dom)
|
242 | makeDom(data, 0);
|
243 | dom.appendChild(data.dom);
|
244 | position(data.dom, 0, 0, width, height);
|
245 | layout(data, 0, width, height);
|
246 | }
|
247 |
|
248 | window.appendTreemap = appendTreemap;
|
249 | })(window);
|