Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(119)

Side by Side Diff: tools/binary_size/template/D3SymbolTreeMap.js

Issue 2813963002: //tools/binary_size: Consolidate most tools into "supersize" command (Closed)
Patch Set: Fix readme formatting. Make archive's --outoput-file a positional arg Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/binary_size/supersize ('k') | tools/binary_size/template/index.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // TODO:
6 // 1. Visibility functions: base on boxPadding.t, not 15
7 // 2. Track a maxDisplayDepth that is user-settable:
8 // maxDepth == currentRoot.depth + maxDisplayDepth
9 function D3SymbolTreeMap(mapWidth, mapHeight, levelsToShow) {
10 this._mapContainer = undefined;
11 this._mapWidth = mapWidth;
12 this._mapHeight = mapHeight;
13 this.boxPadding = {'l': 5, 'r': 5, 't': 20, 'b': 5};
14 this.infobox = undefined;
15 this._maskContainer = undefined;
16 this._highlightContainer = undefined;
17 // Transition in this order:
18 // 1. Exiting items go away.
19 // 2. Updated items move.
20 // 3. New items enter.
21 this._exitDuration=500;
22 this._updateDuration=500;
23 this._enterDuration=500;
24 this._firstTransition=true;
25 this._layout = undefined;
26 this._currentRoot = undefined;
27 this._currentNodes = undefined;
28 this._treeData = undefined;
29 this._maxLevelsToShow = levelsToShow;
30 this._currentMaxDepth = this._maxLevelsToShow;
31 }
32
33 /**
34 * Make a number pretty, with comma separators.
35 */
36 D3SymbolTreeMap._pretty = function(num) {
37 var asString = String(num);
38 var result = '';
39 var counter = 0;
40 for (var x = asString.length - 1; x >= 0; x--) {
41 counter++;
42 if (counter === 4) {
43 result = ',' + result;
44 counter = 1;
45 }
46 result = asString.charAt(x) + result;
47 }
48 return result;
49 }
50
51 /**
52 * Express a number in terms of KiB, MiB, GiB, etc.
53 * Note that these are powers of 2, not of 10.
54 */
55 D3SymbolTreeMap._byteify = function(num) {
56 var suffix;
57 if (num >= 1024) {
58 if (num >= 1024 * 1024 * 1024) {
59 suffix = 'GiB';
60 num = num / (1024 * 1024 * 1024);
61 } else if (num >= 1024 * 1024) {
62 suffix = 'MiB';
63 num = num / (1024 * 1024);
64 } else if (num >= 1024) {
65 suffix = 'KiB'
66 num = num / 1024;
67 }
68 return num.toFixed(2) + ' ' + suffix;
69 }
70 return num + ' B';
71 }
72
73 D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS = {
74 'b': '.bss',
75 'd': '.data and .data.*',
76 'r': '.rodata',
77 't': '.text',
78 'v': 'Vtable entry',
79 '!': 'Generated Symbols (typeinfo, thunks, etc)',
80 };
81 D3SymbolTreeMap._NM_SYMBOL_TYPES = '';
82 for (var symbol_type in D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS) {
83 D3SymbolTreeMap._NM_SYMBOL_TYPES += symbol_type;
84 }
85
86 /**
87 * Given a symbol type code, look up and return a human-readable description
88 * of that symbol type. If the symbol type does not match one of the known
89 * types, the unrecognized description (corresponding to symbol type '?') is
90 * returned instead of null or undefined.
91 */
92 D3SymbolTreeMap._getSymbolDescription = function(type) {
93 var result = D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS[type];
94 if (result === undefined) {
95 result = D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS['?'];
96 }
97 return result;
98 }
99
100 D3SymbolTreeMap._colorArray = [
101 'rgb(190,186,218)',
102 'rgb(253,180,98)',
103 'rgb(141,211,199)',
104 'rgb(128,177,211)',
105 'rgb(255,237,111)',
106 'rgb(204,235,197)',
107 ]
108
109 D3SymbolTreeMap._initColorMap = function() {
110 var map = {};
111 var numColors = D3SymbolTreeMap._colorArray.length;
112 var count = 0;
113 for (var key in D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS) {
114 var index = count++ % numColors;
115 map[key] = d3.rgb(D3SymbolTreeMap._colorArray[index]);
116 }
117 D3SymbolTreeMap._colorMap = map;
118 }
119 D3SymbolTreeMap._initColorMap();
120
121 D3SymbolTreeMap.getColorForType = function(type) {
122 var result = D3SymbolTreeMap._colorMap[type];
123 if (result === undefined) return d3.rgb('rgb(255,255,255)');
124 return result;
125 }
126
127 D3SymbolTreeMap.prototype.init = function() {
128 this.infobox = this._createInfoBox();
129 this._mapContainer = d3.select('body').append('div')
130 .style('position', 'relative')
131 .style('width', this._mapWidth)
132 .style('height', this._mapHeight)
133 .style('padding', 0)
134 .style('margin', 0)
135 .style('box-shadow', '5px 5px 5px #888');
136 this._layout = this._createTreeMapLayout();
137 this._setData(tree_data); // TODO: Don't use global 'tree_data'
138 }
139
140 /**
141 * Sets the data displayed by the treemap and layint out the map.
142 */
143 D3SymbolTreeMap.prototype._setData = function(data) {
144 this._treeData = data;
145 console.time('_crunchStats');
146 this._crunchStats(data);
147 console.timeEnd('_crunchStats');
148 this._currentRoot = this._treeData;
149 this._currentNodes = this._layout.nodes(this._currentRoot);
150 this._currentMaxDepth = this._maxLevelsToShow;
151 this._doLayout();
152 }
153
154 /**
155 * Recursively traverses the entire tree starting from the specified node,
156 * computing statistics and recording metadata as it goes. Call this method
157 * only once per imported tree.
158 */
159 D3SymbolTreeMap.prototype._crunchStats = function(node) {
160 var stack = [];
161 stack.idCounter = 0;
162 this._crunchStatsHelper(stack, node);
163 }
164
165 /**
166 * Invoke the specified visitor function on all data elements currently shown
167 * in the treemap including any and all of their children, starting at the
168 * currently-displayed root and descening recursively. The function will be
169 * passed the datum element representing each node. No traversal guarantees
170 * are made.
171 */
172 D3SymbolTreeMap.prototype.visitFromDisplayedRoot = function(visitor) {
173 this._visit(this._currentRoot, visitor);
174 }
175
176 /**
177 * Helper function for visit functions.
178 */
179 D3SymbolTreeMap.prototype._visit = function(datum, visitor) {
180 visitor.call(this, datum);
181 if (datum.children) for (var i = 0; i < datum.children.length; i++) {
182 this._visit(datum.children[i], visitor);
183 }
184 }
185
186 D3SymbolTreeMap.prototype._crunchStatsHelper = function(stack, node) {
187 // Only overwrite the node ID if it isn't already set.
188 // This allows stats to be crunched multiple times on subsets of data
189 // without breaking the data-to-ID bindings. New nodes get new IDs.
190 if (node.id === undefined) node.id = stack.idCounter++;
191 if (node.children === undefined) {
192 // Leaf node (symbol); accumulate stats.
193 for (var i = 0; i < stack.length; i++) {
194 var ancestor = stack[i];
195 if (!ancestor.symbol_stats) ancestor.symbol_stats = {};
196 if (ancestor.symbol_stats[node.t] === undefined) {
197 // New symbol type we haven't seen before, just record.
198 ancestor.symbol_stats[node.t] = {'count': 1,
199 'size': node.value};
200 } else {
201 // Existing symbol type, increment.
202 ancestor.symbol_stats[node.t].count++;
203 ancestor.symbol_stats[node.t].size += node.value;
204 }
205 }
206 } else for (var i = 0; i < node.children.length; i++) {
207 stack.push(node);
208 this._crunchStatsHelper(stack, node.children[i]);
209 stack.pop();
210 }
211 }
212
213 D3SymbolTreeMap.prototype._createTreeMapLayout = function() {
214 var result = d3.layout.treemap()
215 .padding([this.boxPadding.t, this.boxPadding.r,
216 this.boxPadding.b, this.boxPadding.l])
217 .size([this._mapWidth, this._mapHeight]);
218 return result;
219 }
220
221 D3SymbolTreeMap.prototype.resize = function(width, height) {
222 this._mapWidth = width;
223 this._mapHeight = height;
224 this._mapContainer.style('width', width).style('height', height);
225 this._layout.size([this._mapWidth, this._mapHeight]);
226 this._currentNodes = this._layout.nodes(this._currentRoot);
227 this._doLayout();
228 }
229
230 D3SymbolTreeMap.prototype._zoomDatum = function(datum) {
231 if (this._currentRoot === datum) return; // already here
232 this._hideHighlight(datum);
233 this._hideInfoBox(datum);
234 this._currentRoot = datum;
235 this._currentNodes = this._layout.nodes(this._currentRoot);
236 this._currentMaxDepth = this._currentRoot.depth + this._maxLevelsToShow;
237 console.log('zooming into datum ' + this._currentRoot.n);
238 this._doLayout();
239 }
240
241 D3SymbolTreeMap.prototype.setMaxLevels = function(levelsToShow) {
242 this._maxLevelsToShow = levelsToShow;
243 this._currentNodes = this._layout.nodes(this._currentRoot);
244 this._currentMaxDepth = this._currentRoot.depth + this._maxLevelsToShow;
245 console.log('setting max levels to show: ' + this._maxLevelsToShow);
246 this._doLayout();
247 }
248
249 /**
250 * Clone the specified tree, returning an independent copy of the data.
251 * Only the original attributes expected to exist prior to invoking
252 * _crunchStatsHelper are retained, with the exception of the 'id' attribute
253 * (which must be retained for proper transitions).
254 * If the optional filter parameter is provided, it will be called with 'this'
255 * set to this treemap instance and passed the 'datum' object as an argument.
256 * When specified, the copy will retain only the data for which the filter
257 * function returns true.
258 */
259 D3SymbolTreeMap.prototype._clone = function(datum, filter) {
260 var trackingStats = false;
261 if (this.__cloneState === undefined) {
262 console.time('_clone');
263 trackingStats = true;
264 this.__cloneState = {'accepted': 0, 'rejected': 0,
265 'forced': 0, 'pruned': 0};
266 }
267
268 // Must go depth-first. All parents of children that are accepted by the
269 // filter must be preserved!
270 var copy = {'n': datum.n, 'k': datum.k};
271 var childAccepted = false;
272 if (datum.children !== undefined) {
273 for (var i = 0; i < datum.children.length; i++) {
274 var copiedChild = this._clone(datum.children[i], filter);
275 if (copiedChild !== undefined) {
276 childAccepted = true; // parent must also be accepted.
277 if (copy.children === undefined) copy.children = [];
278 copy.children.push(copiedChild);
279 }
280 }
281 }
282
283 // Ignore nodes that don't match the filter, when present.
284 var accept = false;
285 if (childAccepted) {
286 // Parent of an accepted child must also be accepted.
287 this.__cloneState.forced++;
288 accept = true;
289 } else if (filter !== undefined && filter.call(this, datum) !== true) {
290 this.__cloneState.rejected++;
291 } else if (datum.children === undefined) {
292 // Accept leaf nodes that passed the filter
293 this.__cloneState.accepted++;
294 accept = true;
295 } else {
296 // Non-leaf node. If no children are accepted, prune it.
297 this.__cloneState.pruned++;
298 }
299
300 if (accept) {
301 if (datum.id !== undefined) copy.id = datum.id;
302 if (datum.lastPathElement !== undefined) {
303 copy.lastPathElement = datum.lastPathElement;
304 }
305 if (datum.t !== undefined) copy.t = datum.t;
306 if (datum.value !== undefined && datum.children === undefined) {
307 copy.value = datum.value;
308 }
309 } else {
310 // Discard the copy we were going to return
311 copy = undefined;
312 }
313
314 if (trackingStats === true) {
315 // We are the fist call in the recursive chain.
316 console.timeEnd('_clone');
317 var totalAccepted = this.__cloneState.accepted +
318 this.__cloneState.forced;
319 console.log(
320 totalAccepted + ' nodes retained (' +
321 this.__cloneState.forced + ' forced by accepted children, ' +
322 this.__cloneState.accepted + ' accepted on their own merits), ' +
323 this.__cloneState.rejected + ' nodes (and their children) ' +
324 'filtered out,' +
325 this.__cloneState.pruned + ' nodes pruned because because no ' +
326 'children remained.');
327 delete this.__cloneState;
328 }
329 return copy;
330 }
331
332 D3SymbolTreeMap.prototype.filter = function(filter) {
333 // Ensure we have a copy of the original root.
334 if (this._backupTree === undefined) this._backupTree = this._treeData;
335 this._mapContainer.selectAll('div').remove();
336 this._setData(this._clone(this._backupTree, filter));
337 }
338
339 D3SymbolTreeMap.prototype._doLayout = function() {
340 console.time('_doLayout');
341 this._handleInodes();
342 this._handleLeaves();
343 this._firstTransition = false;
344 console.timeEnd('_doLayout');
345 }
346
347 D3SymbolTreeMap.prototype._highlightElement = function(datum, selection) {
348 this._showHighlight(datum, selection);
349 }
350
351 D3SymbolTreeMap.prototype._unhighlightElement = function(datum, selection) {
352 this._hideHighlight(datum, selection);
353 }
354
355 D3SymbolTreeMap.prototype._handleInodes = function() {
356 console.time('_handleInodes');
357 var thisTreeMap = this;
358 var inodes = this._currentNodes.filter(function(datum){
359 return (datum.depth <= thisTreeMap._currentMaxDepth) &&
360 datum.children !== undefined;
361 });
362 var cellsEnter = this._mapContainer.selectAll('div.inode')
363 .data(inodes, function(datum) { return datum.id; })
364 .enter()
365 .append('div').attr('class', 'inode').attr('id', function(datum){
366 return 'node-' + datum.id;});
367
368
369 // Define enter/update/exit for inodes
370 cellsEnter
371 .append('div')
372 .attr('class', 'rect inode_rect_entering')
373 .style('z-index', function(datum) { return datum.id * 2; })
374 .style('position', 'absolute')
375 .style('left', function(datum) { return datum.x; })
376 .style('top', function(datum){ return datum.y; })
377 .style('width', function(datum){ return datum.dx; })
378 .style('height', function(datum){ return datum.dy; })
379 .style('opacity', '0')
380 .style('border', '1px solid black')
381 .style('background-image', function(datum) {
382 return thisTreeMap._makeSymbolBucketBackgroundImage.call(
383 thisTreeMap, datum);
384 })
385 .style('background-color', function(datum) {
386 if (datum.t === undefined) return 'rgb(220,220,220)';
387 return D3SymbolTreeMap.getColorForType(datum.t).toString();
388 })
389 .on('mouseover', function(datum){
390 thisTreeMap._highlightElement.call(
391 thisTreeMap, datum, d3.select(this));
392 thisTreeMap._showInfoBox.call(thisTreeMap, datum);
393 })
394 .on('mouseout', function(datum){
395 thisTreeMap._unhighlightElement.call(
396 thisTreeMap, datum, d3.select(this));
397 thisTreeMap._hideInfoBox.call(thisTreeMap, datum);
398 })
399 .on('mousemove', function(){
400 thisTreeMap._moveInfoBox.call(thisTreeMap, event);
401 })
402 .on('dblclick', function(datum){
403 if (datum !== thisTreeMap._currentRoot) {
404 // Zoom into the selection
405 thisTreeMap._zoomDatum(datum);
406 } else if (datum.parent) {
407 console.log('event.shiftKey=' + event.shiftKey);
408 if (event.shiftKey === true) {
409 // Back to root
410 thisTreeMap._zoomDatum(thisTreeMap._treeData);
411 } else {
412 // Zoom out of the selection
413 thisTreeMap._zoomDatum(datum.parent);
414 }
415 }
416 });
417 cellsEnter
418 .append('div')
419 .attr('class', 'label inode_label_entering')
420 .style('z-index', function(datum) { return (datum.id * 2) + 1; })
421 .style('position', 'absolute')
422 .style('left', function(datum){ return datum.x; })
423 .style('top', function(datum){ return datum.y; })
424 .style('width', function(datum) { return datum.dx; })
425 .style('height', function(datum) { return thisTreeMap.boxPadding.t; })
426 .style('opacity', '0')
427 .style('pointer-events', 'none')
428 .style('-webkit-user-select', 'none')
429 .style('overflow', 'hidden') // required for ellipsis
430 .style('white-space', 'nowrap') // required for ellipsis
431 .style('text-overflow', 'ellipsis')
432 .style('text-align', 'center')
433 .style('vertical-align', 'top')
434 .style('visibility', function(datum) {
435 return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
436 })
437 .text(function(datum) {
438 var sizeish = ' [' + D3SymbolTreeMap._byteify(datum.value) + ']'
439 var text;
440 if (datum.k === 'b') { // bucket
441 if (datum === thisTreeMap._currentRoot) {
442 text = thisTreeMap.pathFor(datum) + ': '
443 + D3SymbolTreeMap._getSymbolDescription(datum.t)
444 } else {
445 text = D3SymbolTreeMap._getSymbolDescription(datum.t);
446 }
447 } else if (datum === thisTreeMap._currentRoot) {
448 // The top-most level should always show the complete path
449 text = thisTreeMap.pathFor(datum);
450 } else {
451 // Anything that isn't a bucket or a leaf (symbol) or the
452 // current root should just show its name.
453 text = datum.n;
454 }
455 return text + sizeish;
456 }
457 );
458
459 // Complicated transition logic:
460 // For nodes that are entering, we want to fade them in in-place AFTER
461 // any adjusting nodes have resized and moved around. That way, new nodes
462 // seamlessly appear in the right spot after their containers have resized
463 // and moved around.
464 // To do this we do some trickery:
465 // 1. Define a '_entering' class on the entering elements
466 // 2. Use this to select only the entering elements and apply the opacity
467 // transition.
468 // 3. Use the same transition to drop the '_entering' suffix, so that they
469 // will correctly update in later zoom/resize/whatever operations.
470 // 4. The update transition is achieved by selecting the elements without
471 // the '_entering_' suffix and applying movement and resizing transition
472 // effects.
473 this._mapContainer.selectAll('div.inode_rect_entering').transition()
474 .duration(thisTreeMap._enterDuration).delay(
475 this._firstTransition ? 0 : thisTreeMap._exitDuration +
476 thisTreeMap._updateDuration)
477 .attr('class', 'rect inode_rect')
478 .style('opacity', '1')
479 this._mapContainer.selectAll('div.inode_label_entering').transition()
480 .duration(thisTreeMap._enterDuration).delay(
481 this._firstTransition ? 0 : thisTreeMap._exitDuration +
482 thisTreeMap._updateDuration)
483 .attr('class', 'label inode_label')
484 .style('opacity', '1')
485 this._mapContainer.selectAll('div.inode_rect').transition()
486 .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
487 .style('opacity', '1')
488 .style('background-image', function(datum) {
489 return thisTreeMap._makeSymbolBucketBackgroundImage.call(
490 thisTreeMap, datum);
491 })
492 .style('left', function(datum) { return datum.x; })
493 .style('top', function(datum){ return datum.y; })
494 .style('width', function(datum){ return datum.dx; })
495 .style('height', function(datum){ return datum.dy; });
496 this._mapContainer.selectAll('div.inode_label').transition()
497 .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
498 .style('opacity', '1')
499 .style('visibility', function(datum) {
500 return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
501 })
502 .style('left', function(datum){ return datum.x; })
503 .style('top', function(datum){ return datum.y; })
504 .style('width', function(datum) { return datum.dx; })
505 .style('height', function(datum) { return thisTreeMap.boxPadding.t; })
506 .text(function(datum) {
507 var sizeish = ' [' + D3SymbolTreeMap._byteify(datum.value) + ']'
508 var text;
509 if (datum.k === 'b') {
510 if (datum === thisTreeMap._currentRoot) {
511 text = thisTreeMap.pathFor(datum) + ': ' +
512 D3SymbolTreeMap._getSymbolDescription(datum.t)
513 } else {
514 text = D3SymbolTreeMap._getSymbolDescription(datum.t);
515 }
516 } else if (datum === thisTreeMap._currentRoot) {
517 // The top-most level should always show the complete path
518 text = thisTreeMap.pathFor(datum);
519 } else {
520 // Anything that isn't a bucket or a leaf (symbol) or the
521 // current root should just show its name.
522 text = datum.n;
523 }
524 return text + sizeish;
525 });
526 var exit = this._mapContainer.selectAll('div.inode')
527 .data(inodes, function(datum) { return 'inode-' + datum.id; })
528 .exit();
529 exit.selectAll('div.inode_rect').transition().duration(
530 thisTreeMap._exitDuration).style('opacity', 0);
531 exit.selectAll('div.inode_label').transition().duration(
532 thisTreeMap._exitDuration).style('opacity', 0);
533 exit.transition().delay(thisTreeMap._exitDuration + 1).remove();
534
535 console.log(inodes.length + ' inodes layed out.');
536 console.timeEnd('_handleInodes');
537 }
538
539 D3SymbolTreeMap.prototype._handleLeaves = function() {
540 console.time('_handleLeaves');
541 var color_fn = d3.scale.category10();
542 var thisTreeMap = this;
543 var leaves = this._currentNodes.filter(function(datum){
544 return (datum.depth <= thisTreeMap._currentMaxDepth) &&
545 datum.children === undefined; });
546 var cellsEnter = this._mapContainer.selectAll('div.leaf')
547 .data(leaves, function(datum) { return datum.id; })
548 .enter()
549 .append('div').attr('class', 'leaf').attr('id', function(datum){
550 return 'node-' + datum.id;
551 });
552
553 // Define enter/update/exit for leaves
554 cellsEnter
555 .append('div')
556 .attr('class', 'rect leaf_rect_entering')
557 .style('z-index', function(datum) { return datum.id * 2; })
558 .style('position', 'absolute')
559 .style('left', function(datum){ return datum.x; })
560 .style('top', function(datum){ return datum.y; })
561 .style('width', function(datum){ return datum.dx; })
562 .style('height', function(datum){ return datum.dy; })
563 .style('opacity', '0')
564 .style('background-color', function(datum) {
565 if (datum.t === undefined) return 'rgb(220,220,220)';
566 return D3SymbolTreeMap.getColorForType(datum.t)
567 .darker(0.3).toString();
568 })
569 .style('border', '1px solid black')
570 .on('mouseover', function(datum){
571 thisTreeMap._highlightElement.call(
572 thisTreeMap, datum, d3.select(this));
573 thisTreeMap._showInfoBox.call(thisTreeMap, datum);
574 })
575 .on('mouseout', function(datum){
576 thisTreeMap._unhighlightElement.call(
577 thisTreeMap, datum, d3.select(this));
578 thisTreeMap._hideInfoBox.call(thisTreeMap, datum);
579 })
580 .on('mousemove', function(){ thisTreeMap._moveInfoBox.call(
581 thisTreeMap, event);
582 });
583 cellsEnter
584 .append('div')
585 .attr('class', 'label leaf_label_entering')
586 .style('z-index', function(datum) { return (datum.id * 2) + 1; })
587 .style('position', 'absolute')
588 .style('left', function(datum){ return datum.x; })
589 .style('top', function(datum){ return datum.y; })
590 .style('width', function(datum) { return datum.dx; })
591 .style('height', function(datum) { return datum.dy; })
592 .style('opacity', '0')
593 .style('pointer-events', 'none')
594 .style('-webkit-user-select', 'none')
595 .style('overflow', 'hidden') // required for ellipsis
596 .style('white-space', 'nowrap') // required for ellipsis
597 .style('text-overflow', 'ellipsis')
598 .style('text-align', 'center')
599 .style('vertical-align', 'middle')
600 .style('visibility', function(datum) {
601 return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
602 })
603 .text(function(datum) { return datum.n; });
604
605 // Complicated transition logic: See note in _handleInodes()
606 this._mapContainer.selectAll('div.leaf_rect_entering').transition()
607 .duration(thisTreeMap._enterDuration).delay(
608 this._firstTransition ? 0 : thisTreeMap._exitDuration +
609 thisTreeMap._updateDuration)
610 .attr('class', 'rect leaf_rect')
611 .style('opacity', '1')
612 this._mapContainer.selectAll('div.leaf_label_entering').transition()
613 .duration(thisTreeMap._enterDuration).delay(
614 this._firstTransition ? 0 : thisTreeMap._exitDuration +
615 thisTreeMap._updateDuration)
616 .attr('class', 'label leaf_label')
617 .style('opacity', '1')
618 this._mapContainer.selectAll('div.leaf_rect').transition()
619 .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
620 .style('opacity', '1')
621 .style('left', function(datum){ return datum.x; })
622 .style('top', function(datum){ return datum.y; })
623 .style('width', function(datum){ return datum.dx; })
624 .style('height', function(datum){ return datum.dy; });
625 this._mapContainer.selectAll('div.leaf_label').transition()
626 .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
627 .style('opacity', '1')
628 .style('visibility', function(datum) {
629 return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
630 })
631 .style('left', function(datum){ return datum.x; })
632 .style('top', function(datum){ return datum.y; })
633 .style('width', function(datum) { return datum.dx; })
634 .style('height', function(datum) { return datum.dy; });
635 var exit = this._mapContainer.selectAll('div.leaf')
636 .data(leaves, function(datum) { return 'leaf-' + datum.id; })
637 .exit();
638 exit.selectAll('div.leaf_rect').transition()
639 .duration(thisTreeMap._exitDuration)
640 .style('opacity', 0);
641 exit.selectAll('div.leaf_label').transition()
642 .duration(thisTreeMap._exitDuration)
643 .style('opacity', 0);
644 exit.transition().delay(thisTreeMap._exitDuration + 1).remove();
645
646 console.log(leaves.length + ' leaves layed out.');
647 console.timeEnd('_handleLeaves');
648 }
649
650 D3SymbolTreeMap.prototype._makeSymbolBucketBackgroundImage = function(datum) {
651 if (!(datum.t === undefined && datum.depth == this._currentMaxDepth)) {
652 return 'none';
653 }
654 var text = '';
655 var lastStop = 0;
656 for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) {
657 symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x);
658 var stats = datum.symbol_stats[symbol_type];
659 if (stats !== undefined) {
660 if (text.length !== 0) {
661 text += ', ';
662 }
663 var percent = 100 * (stats.size / datum.value);
664 var nowStop = lastStop + percent;
665 var tempcolor = D3SymbolTreeMap.getColorForType(symbol_type);
666 var color = d3.rgb(tempcolor).toString();
667 text += color + ' ' + lastStop + '%, ' + color + ' ' +
668 nowStop + '%';
669 lastStop = nowStop;
670 }
671 }
672 return 'linear-gradient(' + (datum.dx > datum.dy ? 'to right' :
673 'to bottom') + ', ' + text + ')';
674 }
675
676 D3SymbolTreeMap.prototype.pathFor = function(datum) {
677 if (datum.__path) return datum.__path;
678 parts=[];
679 node = datum;
680 while (node) {
681 if (node.k === 'p') { // path node
682 if(node.n !== '/') parts.unshift(node.n);
683 }
684 node = node.parent;
685 }
686 datum.__path = '/' + parts.join('/');
687 return datum.__path;
688 }
689
690 D3SymbolTreeMap.prototype._createHighlight = function(datum, selection) {
691 var x = parseInt(selection.style('left'));
692 var y = parseInt(selection.style('top'));
693 var w = parseInt(selection.style('width'));
694 var h = parseInt(selection.style('height'));
695 datum.highlight = this._mapContainer.append('div')
696 .attr('id', 'h-' + datum.id)
697 .attr('class', 'highlight')
698 .style('pointer-events', 'none')
699 .style('-webkit-user-select', 'none')
700 .style('z-index', '999999')
701 .style('position', 'absolute')
702 .style('top', y-2)
703 .style('left', x-2)
704 .style('width', w+4)
705 .style('height', h+4)
706 .style('margin', 0)
707 .style('padding', 0)
708 .style('border', '4px outset rgba(250,40,200,0.9)')
709 .style('box-sizing', 'border-box')
710 .style('opacity', 0.0);
711 }
712
713 D3SymbolTreeMap.prototype._showHighlight = function(datum, selection) {
714 if (datum === this._currentRoot) return;
715 if (datum.highlight === undefined) {
716 this._createHighlight(datum, selection);
717 }
718 datum.highlight.transition().duration(200).style('opacity', 1.0);
719 }
720
721 D3SymbolTreeMap.prototype._hideHighlight = function(datum, selection) {
722 if (datum.highlight === undefined) return;
723 datum.highlight.transition().duration(750)
724 .style('opacity', 0)
725 .each('end', function(){
726 if (datum.highlight) datum.highlight.remove();
727 delete datum.highlight;
728 });
729 }
730
731 D3SymbolTreeMap.prototype._createInfoBox = function() {
732 return d3.select('body')
733 .append('div')
734 .attr('id', 'infobox')
735 .style('z-index', '2147483647') // (2^31) - 1: Hopefully safe :)
736 .style('position', 'absolute')
737 .style('visibility', 'hidden')
738 .style('background-color', 'rgba(255,255,255, 0.9)')
739 .style('border', '1px solid black')
740 .style('padding', '10px')
741 .style('-webkit-user-select', 'none')
742 .style('box-shadow', '3px 3px rgba(70,70,70,0.5)')
743 .style('border-radius', '10px')
744 .style('white-space', 'nowrap');
745 }
746
747 D3SymbolTreeMap.prototype._showInfoBox = function(datum) {
748 this.infobox.text('');
749 var numSymbols = 0;
750 var sizeish = D3SymbolTreeMap._pretty(datum.value) + ' bytes (' +
751 D3SymbolTreeMap._byteify(datum.value) + ')';
752 if (datum.k === 'p' || datum.k === 'b') { // path or bucket
753 if (datum.symbol_stats) { // can be empty if filters are applied
754 for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) {
755 symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x);
756 var stats = datum.symbol_stats[symbol_type];
757 if (stats !== undefined) numSymbols += stats.count;
758 }
759 }
760 } else if (datum.k === 's') { // symbol
761 numSymbols = 1;
762 }
763
764 if (datum.k === 'p' && !datum.lastPathElement) {
765 this.infobox.append('div').text('Directory: ' + this.pathFor(datum))
766 this.infobox.append('div').text('Size: ' + sizeish);
767 } else {
768 if (datum.k === 'p') { // path
769 this.infobox.append('div').text('File: ' + this.pathFor(datum))
770 this.infobox.append('div').text('Size: ' + sizeish);
771 } else if (datum.k === 'b') { // bucket
772 this.infobox.append('div').text('Symbol Bucket: ' +
773 D3SymbolTreeMap._getSymbolDescription(datum.t));
774 this.infobox.append('div').text('Count: ' + numSymbols);
775 this.infobox.append('div').text('Size: ' + sizeish);
776 this.infobox.append('div').text('Location: ' + this.pathFor(datum))
777 } else if (datum.k === 's') { // symbol
778 this.infobox.append('div').text('Symbol: ' + datum.n);
779 this.infobox.append('div').text('Type: ' +
780 D3SymbolTreeMap._getSymbolDescription(datum.t));
781 this.infobox.append('div').text('Size: ' + sizeish);
782 this.infobox.append('div').text('Location: ' + this.pathFor(datum))
783 }
784 }
785 if (datum.k === 'p') {
786 this.infobox.append('div')
787 .text('Number of symbols: ' + D3SymbolTreeMap._pretty(numSymbols));
788 if (datum.symbol_stats) { // can be empty if filters are applied
789 var table = this.infobox.append('table')
790 .attr('border', 1).append('tbody');
791 var header = table.append('tr');
792 header.append('th').text('Type');
793 header.append('th').text('Count');
794 header.append('th')
795 .style('white-space', 'nowrap')
796 .text('Total Size (Bytes)');
797 for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) {
798 symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x);
799 var stats = datum.symbol_stats[symbol_type];
800 if (stats !== undefined) {
801 var tr = table.append('tr');
802 tr.append('td')
803 .style('white-space', 'nowrap')
804 .text(D3SymbolTreeMap._getSymbolDescription(
805 symbol_type));
806 tr.append('td').text(D3SymbolTreeMap._pretty(stats.count));
807 tr.append('td').text(D3SymbolTreeMap._pretty(stats.size));
808 }
809 }
810 }
811 }
812 this.infobox.style('visibility', 'visible');
813 }
814
815 D3SymbolTreeMap.prototype._hideInfoBox = function(datum) {
816 this.infobox.style('visibility', 'hidden');
817 }
818
819 D3SymbolTreeMap.prototype._moveInfoBox = function(event) {
820 var element = document.getElementById('infobox');
821 var w = element.offsetWidth;
822 var h = element.offsetHeight;
823 var offsetLeft = 10;
824 var offsetTop = 10;
825
826 var rightLimit = window.innerWidth;
827 var rightEdge = event.pageX + offsetLeft + w;
828 if (rightEdge > rightLimit) {
829 // Too close to screen edge, reflect around the cursor
830 offsetLeft = -1 * (w + offsetLeft);
831 }
832
833 var bottomLimit = window.innerHeight;
834 var bottomEdge = event.pageY + offsetTop + h;
835 if (bottomEdge > bottomLimit) {
836 // Too close ot screen edge, reflect around the cursor
837 offsetTop = -1 * (h + offsetTop);
838 }
839
840 this.infobox.style('top', (event.pageY + offsetTop) + 'px')
841 .style('left', (event.pageX + offsetLeft) + 'px');
842 }
843
844 D3SymbolTreeMap.prototype.biggestSymbols = function(maxRecords) {
845 var result = undefined;
846 var smallest = undefined;
847 var sortFunction = function(a,b) {
848 var result = b.value - a.value;
849 if (result !== 0) return result; // sort by size
850 var pathA = treemap.pathFor(a); // sort by path
851 var pathB = treemap.pathFor(b);
852 if (pathA > pathB) return 1;
853 if (pathB > pathA) return -1;
854 return a.n - b.n; // sort by symbol name
855 };
856 this.visitFromDisplayedRoot(function(datum) {
857 if (datum.children) return; // ignore non-leaves
858 if (!result) { // first element
859 result = [datum];
860 smallest = datum.value;
861 return;
862 }
863 if (result.length < maxRecords) { // filling the array
864 result.push(datum);
865 return;
866 }
867 if (datum.value > smallest) { // array is already full
868 result.push(datum);
869 result.sort(sortFunction);
870 result.pop(); // get rid of smallest element
871 smallest = result[maxRecords - 1].value; // new threshold for entry
872 }
873 });
874 result.sort(sortFunction);
875 return result;
876 }
877
878 D3SymbolTreeMap.prototype.biggestPaths = function(maxRecords) {
879 var result = undefined;
880 var smallest = undefined;
881 var sortFunction = function(a,b) {
882 var result = b.value - a.value;
883 if (result !== 0) return result; // sort by size
884 var pathA = treemap.pathFor(a); // sort by path
885 var pathB = treemap.pathFor(b);
886 if (pathA > pathB) return 1;
887 if (pathB > pathA) return -1;
888 console.log('warning, multiple entries for the same path: ' + pathA);
889 return 0; // should be impossible
890 };
891 this.visitFromDisplayedRoot(function(datum) {
892 if (!datum.lastPathElement) return; // ignore non-files
893 if (!result) { // first element
894 result = [datum];
895 smallest = datum.value;
896 return;
897 }
898 if (result.length < maxRecords) { // filling the array
899 result.push(datum);
900 return;
901 }
902 if (datum.value > smallest) { // array is already full
903 result.push(datum);
904 result.sort(sortFunction);
905 result.pop(); // get rid of smallest element
906 smallest = result[maxRecords - 1].value; // new threshold for entry
907 }
908 });
909 result.sort(sortFunction);
910 return result;
911 }
OLDNEW
« no previous file with comments | « tools/binary_size/supersize ('k') | tools/binary_size/template/index.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698