Index: runtime/third_party/binary_size/src/template/D3SymbolTreeMap.js |
diff --git a/runtime/third_party/binary_size/src/template/D3SymbolTreeMap.js b/runtime/third_party/binary_size/src/template/D3SymbolTreeMap.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4bbe82f1a32624b35abdaa148f9ded73dfe08384 |
--- /dev/null |
+++ b/runtime/third_party/binary_size/src/template/D3SymbolTreeMap.js |
@@ -0,0 +1,938 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// TODO: |
+// 1. Visibility functions: base on boxPadding.t, not 15 |
+// 2. Track a maxDisplayDepth that is user-settable: |
+// maxDepth == currentRoot.depth + maxDisplayDepth |
+function D3SymbolTreeMap(mapWidth, mapHeight, levelsToShow) { |
+ this._mapContainer = undefined; |
+ this._mapWidth = mapWidth; |
+ this._mapHeight = mapHeight; |
+ this.boxPadding = {'l': 5, 'r': 5, 't': 20, 'b': 5}; |
+ this.infobox = undefined; |
+ this._maskContainer = undefined; |
+ this._highlightContainer = undefined; |
+ // Transition in this order: |
+ // 1. Exiting items go away. |
+ // 2. Updated items move. |
+ // 3. New items enter. |
+ this._exitDuration=500; |
+ this._updateDuration=500; |
+ this._enterDuration=500; |
+ this._firstTransition=true; |
+ this._layout = undefined; |
+ this._currentRoot = undefined; |
+ this._currentNodes = undefined; |
+ this._treeData = undefined; |
+ this._maxLevelsToShow = levelsToShow; |
+ this._currentMaxDepth = this._maxLevelsToShow; |
+} |
+ |
+/** |
+ * Make a number pretty, with comma separators. |
+ */ |
+D3SymbolTreeMap._pretty = function(num) { |
+ var asString = String(num); |
+ var result = ''; |
+ var counter = 0; |
+ for (var x = asString.length - 1; x >= 0; x--) { |
+ counter++; |
+ if (counter === 4) { |
+ result = ',' + result; |
+ counter = 1; |
+ } |
+ result = asString.charAt(x) + result; |
+ } |
+ return result; |
+} |
+ |
+/** |
+ * Express a number in terms of KiB, MiB, GiB, etc. |
+ * Note that these are powers of 2, not of 10. |
+ */ |
+D3SymbolTreeMap._byteify = function(num) { |
+ var suffix; |
+ if (num >= 1024) { |
+ if (num >= 1024 * 1024 * 1024) { |
+ suffix = 'GiB'; |
+ num = num / (1024 * 1024 * 1024); |
+ } else if (num >= 1024 * 1024) { |
+ suffix = 'MiB'; |
+ num = num / (1024 * 1024); |
+ } else if (num >= 1024) { |
+ suffix = 'KiB' |
+ num = num / 1024; |
+ } |
+ return num.toFixed(2) + ' ' + suffix; |
+ } |
+ return num + ' B'; |
+} |
+ |
+D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS = { |
+ // Definitions concisely derived from the nm 'man' page |
+ 'A': 'Global absolute (A)', |
+ 'B': 'Global uninitialized data (B)', |
+ 'b': 'Local uninitialized data (b)', |
+ 'C': 'Global uninitialized common (C)', |
+ 'D': 'Global initialized data (D)', |
+ 'd': 'Local initialized data (d)', |
+ 'G': 'Global small initialized data (G)', |
+ 'g': 'Local small initialized data (g)', |
+ 'i': 'Indirect function (i)', |
+ 'N': 'Debugging (N)', |
+ 'p': 'Stack unwind (p)', |
+ 'R': 'Global read-only data (R)', |
+ 'r': 'Local read-only data (r)', |
+ 'S': 'Global small uninitialized data (S)', |
+ 's': 'Local small uninitialized data (s)', |
+ 'T': 'Global code (T)', |
+ 't': 'Local code (t)', |
+ 'U': 'Undefined (U)', |
+ 'u': 'Unique (u)', |
+ 'V': 'Global weak object (V)', |
+ 'v': 'Local weak object (v)', |
+ 'W': 'Global weak symbol (W)', |
+ 'w': 'Local weak symbol (w)', |
+ '@': 'Vtable entry (@)', // non-standard, hack. |
+ '-': 'STABS debugging (-)', |
+ '?': 'Unrecognized (?)', |
+}; |
+D3SymbolTreeMap._NM_SYMBOL_TYPES = ''; |
+for (var symbol_type in D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS) { |
+ D3SymbolTreeMap._NM_SYMBOL_TYPES += symbol_type; |
+} |
+ |
+/** |
+ * Given a symbol type code, look up and return a human-readable description |
+ * of that symbol type. If the symbol type does not match one of the known |
+ * types, the unrecognized description (corresponding to symbol type '?') is |
+ * returned instead of null or undefined. |
+ */ |
+D3SymbolTreeMap._getSymbolDescription = function(type) { |
+ var result = D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS[type]; |
+ if (result === undefined) { |
+ result = D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS['?']; |
+ } |
+ return result; |
+} |
+ |
+// Qualitative 12-value pastel Brewer palette. |
+D3SymbolTreeMap._colorArray = [ |
+ 'rgb(141,211,199)', |
+ 'rgb(255,255,179)', |
+ 'rgb(190,186,218)', |
+ 'rgb(251,128,114)', |
+ 'rgb(128,177,211)', |
+ 'rgb(253,180,98)', |
+ 'rgb(179,222,105)', |
+ 'rgb(252,205,229)', |
+ 'rgb(217,217,217)', |
+ 'rgb(188,128,189)', |
+ 'rgb(204,235,197)', |
+ 'rgb(255,237,111)']; |
+ |
+D3SymbolTreeMap._initColorMap = function() { |
+ var map = {}; |
+ var numColors = D3SymbolTreeMap._colorArray.length; |
+ var count = 0; |
+ for (var key in D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS) { |
+ var index = count++ % numColors; |
+ map[key] = d3.rgb(D3SymbolTreeMap._colorArray[index]); |
+ } |
+ D3SymbolTreeMap._colorMap = map; |
+} |
+D3SymbolTreeMap._initColorMap(); |
+ |
+D3SymbolTreeMap.getColorForType = function(type) { |
+ var result = D3SymbolTreeMap._colorMap[type]; |
+ if (result === undefined) return d3.rgb('rgb(255,255,255)'); |
+ return result; |
+} |
+ |
+D3SymbolTreeMap.prototype.init = function() { |
+ this.infobox = this._createInfoBox(); |
+ this._mapContainer = d3.select('body').append('div') |
+ .style('position', 'relative') |
+ .style('width', this._mapWidth) |
+ .style('height', this._mapHeight) |
+ .style('padding', 0) |
+ .style('margin', 0) |
+ .style('box-shadow', '5px 5px 5px #888'); |
+ this._layout = this._createTreeMapLayout(); |
+ this._setData(tree_data); // TODO: Don't use global 'tree_data' |
+} |
+ |
+/** |
+ * Sets the data displayed by the treemap and layint out the map. |
+ */ |
+D3SymbolTreeMap.prototype._setData = function(data) { |
+ this._treeData = data; |
+ console.time('_crunchStats'); |
+ this._crunchStats(data); |
+ console.timeEnd('_crunchStats'); |
+ this._currentRoot = this._treeData; |
+ this._currentNodes = this._layout.nodes(this._currentRoot); |
+ this._currentMaxDepth = this._maxLevelsToShow; |
+ this._doLayout(); |
+} |
+ |
+/** |
+ * Recursively traverses the entire tree starting from the specified node, |
+ * computing statistics and recording metadata as it goes. Call this method |
+ * only once per imported tree. |
+ */ |
+D3SymbolTreeMap.prototype._crunchStats = function(node) { |
+ var stack = []; |
+ stack.idCounter = 0; |
+ this._crunchStatsHelper(stack, node); |
+} |
+ |
+/** |
+ * Invoke the specified visitor function on all data elements currently shown |
+ * in the treemap including any and all of their children, starting at the |
+ * currently-displayed root and descening recursively. The function will be |
+ * passed the datum element representing each node. No traversal guarantees |
+ * are made. |
+ */ |
+D3SymbolTreeMap.prototype.visitFromDisplayedRoot = function(visitor) { |
+ this._visit(this._currentRoot, visitor); |
+} |
+ |
+/** |
+ * Helper function for visit functions. |
+ */ |
+D3SymbolTreeMap.prototype._visit = function(datum, visitor) { |
+ visitor.call(this, datum); |
+ if (datum.children) for (var i = 0; i < datum.children.length; i++) { |
+ this._visit(datum.children[i], visitor); |
+ } |
+} |
+ |
+D3SymbolTreeMap.prototype._crunchStatsHelper = function(stack, node) { |
+ // Only overwrite the node ID if it isn't already set. |
+ // This allows stats to be crunched multiple times on subsets of data |
+ // without breaking the data-to-ID bindings. New nodes get new IDs. |
+ if (node.id === undefined) node.id = stack.idCounter++; |
+ if (node.children === undefined) { |
+ // Leaf node (symbol); accumulate stats. |
+ for (var i = 0; i < stack.length; i++) { |
+ var ancestor = stack[i]; |
+ if (!ancestor.symbol_stats) ancestor.symbol_stats = {}; |
+ if (ancestor.symbol_stats[node.t] === undefined) { |
+ // New symbol type we haven't seen before, just record. |
+ ancestor.symbol_stats[node.t] = {'count': 1, |
+ 'size': node.value}; |
+ } else { |
+ // Existing symbol type, increment. |
+ ancestor.symbol_stats[node.t].count++; |
+ ancestor.symbol_stats[node.t].size += node.value; |
+ } |
+ } |
+ } else for (var i = 0; i < node.children.length; i++) { |
+ stack.push(node); |
+ this._crunchStatsHelper(stack, node.children[i]); |
+ stack.pop(); |
+ } |
+} |
+ |
+D3SymbolTreeMap.prototype._createTreeMapLayout = function() { |
+ var result = d3.layout.treemap() |
+ .padding([this.boxPadding.t, this.boxPadding.r, |
+ this.boxPadding.b, this.boxPadding.l]) |
+ .size([this._mapWidth, this._mapHeight]); |
+ return result; |
+} |
+ |
+D3SymbolTreeMap.prototype.resize = function(width, height) { |
+ this._mapWidth = width; |
+ this._mapHeight = height; |
+ this._mapContainer.style('width', width).style('height', height); |
+ this._layout.size([this._mapWidth, this._mapHeight]); |
+ this._currentNodes = this._layout.nodes(this._currentRoot); |
+ this._doLayout(); |
+} |
+ |
+D3SymbolTreeMap.prototype._zoomDatum = function(datum) { |
+ if (this._currentRoot === datum) return; // already here |
+ this._hideHighlight(datum); |
+ this._hideInfoBox(datum); |
+ this._currentRoot = datum; |
+ this._currentNodes = this._layout.nodes(this._currentRoot); |
+ this._currentMaxDepth = this._currentRoot.depth + this._maxLevelsToShow; |
+ console.log('zooming into datum ' + this._currentRoot.n); |
+ this._doLayout(); |
+} |
+ |
+D3SymbolTreeMap.prototype.setMaxLevels = function(levelsToShow) { |
+ this._maxLevelsToShow = levelsToShow; |
+ this._currentNodes = this._layout.nodes(this._currentRoot); |
+ this._currentMaxDepth = this._currentRoot.depth + this._maxLevelsToShow; |
+ console.log('setting max levels to show: ' + this._maxLevelsToShow); |
+ this._doLayout(); |
+} |
+ |
+/** |
+ * Clone the specified tree, returning an independent copy of the data. |
+ * Only the original attributes expected to exist prior to invoking |
+ * _crunchStatsHelper are retained, with the exception of the 'id' attribute |
+ * (which must be retained for proper transitions). |
+ * If the optional filter parameter is provided, it will be called with 'this' |
+ * set to this treemap instance and passed the 'datum' object as an argument. |
+ * When specified, the copy will retain only the data for which the filter |
+ * function returns true. |
+ */ |
+D3SymbolTreeMap.prototype._clone = function(datum, filter) { |
+ var trackingStats = false; |
+ if (this.__cloneState === undefined) { |
+ console.time('_clone'); |
+ trackingStats = true; |
+ this.__cloneState = {'accepted': 0, 'rejected': 0, |
+ 'forced': 0, 'pruned': 0}; |
+ } |
+ |
+ // Must go depth-first. All parents of children that are accepted by the |
+ // filter must be preserved! |
+ var copy = {'n': datum.n, 'k': datum.k}; |
+ var childAccepted = false; |
+ if (datum.children !== undefined) { |
+ for (var i = 0; i < datum.children.length; i++) { |
+ var copiedChild = this._clone(datum.children[i], filter); |
+ if (copiedChild !== undefined) { |
+ childAccepted = true; // parent must also be accepted. |
+ if (copy.children === undefined) copy.children = []; |
+ copy.children.push(copiedChild); |
+ } |
+ } |
+ } |
+ |
+ // Ignore nodes that don't match the filter, when present. |
+ var accept = false; |
+ if (childAccepted) { |
+ // Parent of an accepted child must also be accepted. |
+ this.__cloneState.forced++; |
+ accept = true; |
+ } else if (filter !== undefined && filter.call(this, datum) !== true) { |
+ this.__cloneState.rejected++; |
+ } else if (datum.children === undefined) { |
+ // Accept leaf nodes that passed the filter |
+ this.__cloneState.accepted++; |
+ accept = true; |
+ } else { |
+ // Non-leaf node. If no children are accepted, prune it. |
+ this.__cloneState.pruned++; |
+ } |
+ |
+ if (accept) { |
+ if (datum.id !== undefined) copy.id = datum.id; |
+ if (datum.lastPathElement !== undefined) { |
+ copy.lastPathElement = datum.lastPathElement; |
+ } |
+ if (datum.t !== undefined) copy.t = datum.t; |
+ if (datum.value !== undefined && datum.children === undefined) { |
+ copy.value = datum.value; |
+ } |
+ } else { |
+ // Discard the copy we were going to return |
+ copy = undefined; |
+ } |
+ |
+ if (trackingStats === true) { |
+ // We are the fist call in the recursive chain. |
+ console.timeEnd('_clone'); |
+ var totalAccepted = this.__cloneState.accepted + |
+ this.__cloneState.forced; |
+ console.log( |
+ totalAccepted + ' nodes retained (' + |
+ this.__cloneState.forced + ' forced by accepted children, ' + |
+ this.__cloneState.accepted + ' accepted on their own merits), ' + |
+ this.__cloneState.rejected + ' nodes (and their children) ' + |
+ 'filtered out,' + |
+ this.__cloneState.pruned + ' nodes pruned because because no ' + |
+ 'children remained.'); |
+ delete this.__cloneState; |
+ } |
+ return copy; |
+} |
+ |
+D3SymbolTreeMap.prototype.filter = function(filter) { |
+ // Ensure we have a copy of the original root. |
+ if (this._backupTree === undefined) this._backupTree = this._treeData; |
+ this._mapContainer.selectAll('div').remove(); |
+ this._setData(this._clone(this._backupTree, filter)); |
+} |
+ |
+D3SymbolTreeMap.prototype._doLayout = function() { |
+ console.time('_doLayout'); |
+ this._handleInodes(); |
+ this._handleLeaves(); |
+ this._firstTransition = false; |
+ console.timeEnd('_doLayout'); |
+} |
+ |
+D3SymbolTreeMap.prototype._highlightElement = function(datum, selection) { |
+ this._showHighlight(datum, selection); |
+} |
+ |
+D3SymbolTreeMap.prototype._unhighlightElement = function(datum, selection) { |
+ this._hideHighlight(datum, selection); |
+} |
+ |
+D3SymbolTreeMap.prototype._handleInodes = function() { |
+ console.time('_handleInodes'); |
+ var thisTreeMap = this; |
+ var inodes = this._currentNodes.filter(function(datum){ |
+ return (datum.depth <= thisTreeMap._currentMaxDepth) && |
+ datum.children !== undefined; |
+ }); |
+ var cellsEnter = this._mapContainer.selectAll('div.inode') |
+ .data(inodes, function(datum) { return datum.id; }) |
+ .enter() |
+ .append('div').attr('class', 'inode').attr('id', function(datum){ |
+ return 'node-' + datum.id;}); |
+ |
+ |
+ // Define enter/update/exit for inodes |
+ cellsEnter |
+ .append('div') |
+ .attr('class', 'rect inode_rect_entering') |
+ .style('z-index', function(datum) { return datum.id * 2; }) |
+ .style('position', 'absolute') |
+ .style('left', function(datum) { return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum){ return datum.dx; }) |
+ .style('height', function(datum){ return datum.dy; }) |
+ .style('opacity', '0') |
+ .style('border', '1px solid black') |
+ .style('background-image', function(datum) { |
+ return thisTreeMap._makeSymbolBucketBackgroundImage.call( |
+ thisTreeMap, datum); |
+ }) |
+ .style('background-color', function(datum) { |
+ if (datum.t === undefined) return 'rgb(220,220,220)'; |
+ return D3SymbolTreeMap.getColorForType(datum.t).toString(); |
+ }) |
+ .on('mouseover', function(datum){ |
+ thisTreeMap._highlightElement.call( |
+ thisTreeMap, datum, d3.select(this)); |
+ thisTreeMap._showInfoBox.call(thisTreeMap, datum); |
+ }) |
+ .on('mouseout', function(datum){ |
+ thisTreeMap._unhighlightElement.call( |
+ thisTreeMap, datum, d3.select(this)); |
+ thisTreeMap._hideInfoBox.call(thisTreeMap, datum); |
+ }) |
+ .on('mousemove', function(){ |
+ thisTreeMap._moveInfoBox.call(thisTreeMap, event); |
+ }) |
+ .on('dblclick', function(datum){ |
+ if (datum !== thisTreeMap._currentRoot) { |
+ // Zoom into the selection |
+ thisTreeMap._zoomDatum(datum); |
+ } else if (datum.parent) { |
+ console.log('event.shiftKey=' + event.shiftKey); |
+ if (event.shiftKey === true) { |
+ // Back to root |
+ thisTreeMap._zoomDatum(thisTreeMap._treeData); |
+ } else { |
+ // Zoom out of the selection |
+ thisTreeMap._zoomDatum(datum.parent); |
+ } |
+ } |
+ }); |
+ cellsEnter |
+ .append('div') |
+ .attr('class', 'label inode_label_entering') |
+ .style('z-index', function(datum) { return (datum.id * 2) + 1; }) |
+ .style('position', 'absolute') |
+ .style('left', function(datum){ return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum) { return datum.dx; }) |
+ .style('height', function(datum) { return thisTreeMap.boxPadding.t; }) |
+ .style('opacity', '0') |
+ .style('pointer-events', 'none') |
+ .style('-webkit-user-select', 'none') |
+ .style('overflow', 'hidden') // required for ellipsis |
+ .style('white-space', 'nowrap') // required for ellipsis |
+ .style('text-overflow', 'ellipsis') |
+ .style('text-align', 'center') |
+ .style('vertical-align', 'top') |
+ .style('visibility', function(datum) { |
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible'; |
+ }) |
+ .text(function(datum) { |
+ var sizeish = ' [' + D3SymbolTreeMap._byteify(datum.value) + ']' |
+ var text; |
+ if (datum.k === 'b') { // bucket |
+ if (datum === thisTreeMap._currentRoot) { |
+ text = thisTreeMap.pathFor(datum) + ': ' |
+ + D3SymbolTreeMap._getSymbolDescription(datum.t) |
+ } else { |
+ text = D3SymbolTreeMap._getSymbolDescription(datum.t); |
+ } |
+ } else if (datum === thisTreeMap._currentRoot) { |
+ // The top-most level should always show the complete path |
+ text = thisTreeMap.pathFor(datum); |
+ } else { |
+ // Anything that isn't a bucket or a leaf (symbol) or the |
+ // current root should just show its name. |
+ text = datum.n; |
+ } |
+ return text + sizeish; |
+ } |
+ ); |
+ |
+ // Complicated transition logic: |
+ // For nodes that are entering, we want to fade them in in-place AFTER |
+ // any adjusting nodes have resized and moved around. That way, new nodes |
+ // seamlessly appear in the right spot after their containers have resized |
+ // and moved around. |
+ // To do this we do some trickery: |
+ // 1. Define a '_entering' class on the entering elements |
+ // 2. Use this to select only the entering elements and apply the opacity |
+ // transition. |
+ // 3. Use the same transition to drop the '_entering' suffix, so that they |
+ // will correctly update in later zoom/resize/whatever operations. |
+ // 4. The update transition is achieved by selecting the elements without |
+ // the '_entering_' suffix and applying movement and resizing transition |
+ // effects. |
+ this._mapContainer.selectAll('div.inode_rect_entering').transition() |
+ .duration(thisTreeMap._enterDuration).delay( |
+ this._firstTransition ? 0 : thisTreeMap._exitDuration + |
+ thisTreeMap._updateDuration) |
+ .attr('class', 'rect inode_rect') |
+ .style('opacity', '1') |
+ this._mapContainer.selectAll('div.inode_label_entering').transition() |
+ .duration(thisTreeMap._enterDuration).delay( |
+ this._firstTransition ? 0 : thisTreeMap._exitDuration + |
+ thisTreeMap._updateDuration) |
+ .attr('class', 'label inode_label') |
+ .style('opacity', '1') |
+ this._mapContainer.selectAll('div.inode_rect').transition() |
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration) |
+ .style('opacity', '1') |
+ .style('background-image', function(datum) { |
+ return thisTreeMap._makeSymbolBucketBackgroundImage.call( |
+ thisTreeMap, datum); |
+ }) |
+ .style('left', function(datum) { return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum){ return datum.dx; }) |
+ .style('height', function(datum){ return datum.dy; }); |
+ this._mapContainer.selectAll('div.inode_label').transition() |
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration) |
+ .style('opacity', '1') |
+ .style('visibility', function(datum) { |
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible'; |
+ }) |
+ .style('left', function(datum){ return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum) { return datum.dx; }) |
+ .style('height', function(datum) { return thisTreeMap.boxPadding.t; }) |
+ .text(function(datum) { |
+ var sizeish = ' [' + D3SymbolTreeMap._byteify(datum.value) + ']' |
+ var text; |
+ if (datum.k === 'b') { |
+ if (datum === thisTreeMap._currentRoot) { |
+ text = thisTreeMap.pathFor(datum) + ': ' + |
+ D3SymbolTreeMap._getSymbolDescription(datum.t) |
+ } else { |
+ text = D3SymbolTreeMap._getSymbolDescription(datum.t); |
+ } |
+ } else if (datum === thisTreeMap._currentRoot) { |
+ // The top-most level should always show the complete path |
+ text = thisTreeMap.pathFor(datum); |
+ } else { |
+ // Anything that isn't a bucket or a leaf (symbol) or the |
+ // current root should just show its name. |
+ text = datum.n; |
+ } |
+ return text + sizeish; |
+ }); |
+ var exit = this._mapContainer.selectAll('div.inode') |
+ .data(inodes, function(datum) { return 'inode-' + datum.id; }) |
+ .exit(); |
+ exit.selectAll('div.inode_rect').transition().duration( |
+ thisTreeMap._exitDuration).style('opacity', 0); |
+ exit.selectAll('div.inode_label').transition().duration( |
+ thisTreeMap._exitDuration).style('opacity', 0); |
+ exit.transition().delay(thisTreeMap._exitDuration + 1).remove(); |
+ |
+ console.log(inodes.length + ' inodes layed out.'); |
+ console.timeEnd('_handleInodes'); |
+} |
+ |
+D3SymbolTreeMap.prototype._handleLeaves = function() { |
+ console.time('_handleLeaves'); |
+ var color_fn = d3.scale.category10(); |
+ var thisTreeMap = this; |
+ var leaves = this._currentNodes.filter(function(datum){ |
+ return (datum.depth <= thisTreeMap._currentMaxDepth) && |
+ datum.children === undefined; }); |
+ var cellsEnter = this._mapContainer.selectAll('div.leaf') |
+ .data(leaves, function(datum) { return datum.id; }) |
+ .enter() |
+ .append('div').attr('class', 'leaf').attr('id', function(datum){ |
+ return 'node-' + datum.id; |
+ }); |
+ |
+ // Define enter/update/exit for leaves |
+ cellsEnter |
+ .append('div') |
+ .attr('class', 'rect leaf_rect_entering') |
+ .style('z-index', function(datum) { return datum.id * 2; }) |
+ .style('position', 'absolute') |
+ .style('left', function(datum){ return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum){ return datum.dx; }) |
+ .style('height', function(datum){ return datum.dy; }) |
+ .style('opacity', '0') |
+ .style('background-color', function(datum) { |
+ if (datum.t === undefined) return 'rgb(220,220,220)'; |
+ return D3SymbolTreeMap.getColorForType(datum.t) |
+ .darker(0.3).toString(); |
+ }) |
+ .style('border', '1px solid black') |
+ .on('mouseover', function(datum){ |
+ thisTreeMap._highlightElement.call( |
+ thisTreeMap, datum, d3.select(this)); |
+ thisTreeMap._showInfoBox.call(thisTreeMap, datum); |
+ }) |
+ .on('mouseout', function(datum){ |
+ thisTreeMap._unhighlightElement.call( |
+ thisTreeMap, datum, d3.select(this)); |
+ thisTreeMap._hideInfoBox.call(thisTreeMap, datum); |
+ }) |
+ .on('mousemove', function(){ thisTreeMap._moveInfoBox.call( |
+ thisTreeMap, event); |
+ }); |
+ cellsEnter |
+ .append('div') |
+ .attr('class', 'label leaf_label_entering') |
+ .style('z-index', function(datum) { return (datum.id * 2) + 1; }) |
+ .style('position', 'absolute') |
+ .style('left', function(datum){ return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum) { return datum.dx; }) |
+ .style('height', function(datum) { return datum.dy; }) |
+ .style('opacity', '0') |
+ .style('pointer-events', 'none') |
+ .style('-webkit-user-select', 'none') |
+ .style('overflow', 'hidden') // required for ellipsis |
+ .style('white-space', 'nowrap') // required for ellipsis |
+ .style('text-overflow', 'ellipsis') |
+ .style('text-align', 'center') |
+ .style('vertical-align', 'middle') |
+ .style('visibility', function(datum) { |
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible'; |
+ }) |
+ .text(function(datum) { return datum.n; }); |
+ |
+ // Complicated transition logic: See note in _handleInodes() |
+ this._mapContainer.selectAll('div.leaf_rect_entering').transition() |
+ .duration(thisTreeMap._enterDuration).delay( |
+ this._firstTransition ? 0 : thisTreeMap._exitDuration + |
+ thisTreeMap._updateDuration) |
+ .attr('class', 'rect leaf_rect') |
+ .style('opacity', '1') |
+ this._mapContainer.selectAll('div.leaf_label_entering').transition() |
+ .duration(thisTreeMap._enterDuration).delay( |
+ this._firstTransition ? 0 : thisTreeMap._exitDuration + |
+ thisTreeMap._updateDuration) |
+ .attr('class', 'label leaf_label') |
+ .style('opacity', '1') |
+ this._mapContainer.selectAll('div.leaf_rect').transition() |
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration) |
+ .style('opacity', '1') |
+ .style('left', function(datum){ return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum){ return datum.dx; }) |
+ .style('height', function(datum){ return datum.dy; }); |
+ this._mapContainer.selectAll('div.leaf_label').transition() |
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration) |
+ .style('opacity', '1') |
+ .style('visibility', function(datum) { |
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible'; |
+ }) |
+ .style('left', function(datum){ return datum.x; }) |
+ .style('top', function(datum){ return datum.y; }) |
+ .style('width', function(datum) { return datum.dx; }) |
+ .style('height', function(datum) { return datum.dy; }); |
+ var exit = this._mapContainer.selectAll('div.leaf') |
+ .data(leaves, function(datum) { return 'leaf-' + datum.id; }) |
+ .exit(); |
+ exit.selectAll('div.leaf_rect').transition() |
+ .duration(thisTreeMap._exitDuration) |
+ .style('opacity', 0); |
+ exit.selectAll('div.leaf_label').transition() |
+ .duration(thisTreeMap._exitDuration) |
+ .style('opacity', 0); |
+ exit.transition().delay(thisTreeMap._exitDuration + 1).remove(); |
+ |
+ console.log(leaves.length + ' leaves layed out.'); |
+ console.timeEnd('_handleLeaves'); |
+} |
+ |
+D3SymbolTreeMap.prototype._makeSymbolBucketBackgroundImage = function(datum) { |
+ if (!(datum.t === undefined && datum.depth == this._currentMaxDepth)) { |
+ return 'none'; |
+ } |
+ var text = ''; |
+ var lastStop = 0; |
+ for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) { |
+ symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x); |
+ var stats = datum.symbol_stats[symbol_type]; |
+ if (stats !== undefined) { |
+ if (text.length !== 0) { |
+ text += ', '; |
+ } |
+ var percent = 100 * (stats.size / datum.value); |
+ var nowStop = lastStop + percent; |
+ var tempcolor = D3SymbolTreeMap.getColorForType(symbol_type); |
+ var color = d3.rgb(tempcolor).toString(); |
+ text += color + ' ' + lastStop + '%, ' + color + ' ' + |
+ nowStop + '%'; |
+ lastStop = nowStop; |
+ } |
+ } |
+ return 'linear-gradient(' + (datum.dx > datum.dy ? 'to right' : |
+ 'to bottom') + ', ' + text + ')'; |
+} |
+ |
+D3SymbolTreeMap.prototype.pathFor = function(datum) { |
+ if (datum.__path) return datum.__path; |
+ parts=[]; |
+ node = datum; |
+ while (node) { |
+ if (node.k === 'p') { // path node |
+ if(node.n !== '/') parts.unshift(node.n); |
+ } |
+ node = node.parent; |
+ } |
+ datum.__path = '/' + parts.join('/'); |
+ return datum.__path; |
+} |
+ |
+D3SymbolTreeMap.prototype._createHighlight = function(datum, selection) { |
+ var x = parseInt(selection.style('left')); |
+ var y = parseInt(selection.style('top')); |
+ var w = parseInt(selection.style('width')); |
+ var h = parseInt(selection.style('height')); |
+ datum.highlight = this._mapContainer.append('div') |
+ .attr('id', 'h-' + datum.id) |
+ .attr('class', 'highlight') |
+ .style('pointer-events', 'none') |
+ .style('-webkit-user-select', 'none') |
+ .style('z-index', '999999') |
+ .style('position', 'absolute') |
+ .style('top', y-2) |
+ .style('left', x-2) |
+ .style('width', w+4) |
+ .style('height', h+4) |
+ .style('margin', 0) |
+ .style('padding', 0) |
+ .style('border', '4px outset rgba(250,40,200,0.9)') |
+ .style('box-sizing', 'border-box') |
+ .style('opacity', 0.0); |
+} |
+ |
+D3SymbolTreeMap.prototype._showHighlight = function(datum, selection) { |
+ if (datum === this._currentRoot) return; |
+ if (datum.highlight === undefined) { |
+ this._createHighlight(datum, selection); |
+ } |
+ datum.highlight.transition().duration(200).style('opacity', 1.0); |
+} |
+ |
+D3SymbolTreeMap.prototype._hideHighlight = function(datum, selection) { |
+ if (datum.highlight === undefined) return; |
+ datum.highlight.transition().duration(750) |
+ .style('opacity', 0) |
+ .each('end', function(){ |
+ if (datum.highlight) datum.highlight.remove(); |
+ delete datum.highlight; |
+ }); |
+} |
+ |
+D3SymbolTreeMap.prototype._createInfoBox = function() { |
+ return d3.select('body') |
+ .append('div') |
+ .attr('id', 'infobox') |
+ .style('z-index', '2147483647') // (2^31) - 1: Hopefully safe :) |
+ .style('position', 'absolute') |
+ .style('visibility', 'hidden') |
+ .style('background-color', 'rgba(255,255,255, 0.9)') |
+ .style('border', '1px solid black') |
+ .style('padding', '10px') |
+ .style('-webkit-user-select', 'none') |
+ .style('box-shadow', '3px 3px rgba(70,70,70,0.5)') |
+ .style('border-radius', '10px') |
+ .style('white-space', 'nowrap'); |
+} |
+ |
+D3SymbolTreeMap.prototype._showInfoBox = function(datum) { |
+ this.infobox.text(''); |
+ var numSymbols = 0; |
+ var sizeish = D3SymbolTreeMap._pretty(datum.value) + ' bytes (' + |
+ D3SymbolTreeMap._byteify(datum.value) + ')'; |
+ if (datum.k === 'p' || datum.k === 'b') { // path or bucket |
+ if (datum.symbol_stats) { // can be empty if filters are applied |
+ for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) { |
+ symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x); |
+ var stats = datum.symbol_stats[symbol_type]; |
+ if (stats !== undefined) numSymbols += stats.count; |
+ } |
+ } |
+ } else if (datum.k === 's') { // symbol |
+ numSymbols = 1; |
+ } |
+ |
+ if (datum.k === 'p' && !datum.lastPathElement) { |
+ this.infobox.append('div').text('Directory: ' + this.pathFor(datum)) |
+ this.infobox.append('div').text('Size: ' + sizeish); |
+ } else { |
+ if (datum.k === 'p') { // path |
+ this.infobox.append('div').text('File: ' + this.pathFor(datum)) |
+ this.infobox.append('div').text('Size: ' + sizeish); |
+ } else if (datum.k === 'b') { // bucket |
+ this.infobox.append('div').text('Symbol Bucket: ' + |
+ D3SymbolTreeMap._getSymbolDescription(datum.t)); |
+ this.infobox.append('div').text('Count: ' + numSymbols); |
+ this.infobox.append('div').text('Size: ' + sizeish); |
+ this.infobox.append('div').text('Location: ' + this.pathFor(datum)) |
+ } else if (datum.k === 's') { // symbol |
+ this.infobox.append('div').text('Symbol: ' + datum.n); |
+ this.infobox.append('div').text('Type: ' + |
+ D3SymbolTreeMap._getSymbolDescription(datum.t)); |
+ this.infobox.append('div').text('Size: ' + sizeish); |
+ this.infobox.append('div').text('Location: ' + this.pathFor(datum)) |
+ } |
+ } |
+ if (datum.k === 'p') { |
+ this.infobox.append('div') |
+ .text('Number of symbols: ' + D3SymbolTreeMap._pretty(numSymbols)); |
+ if (datum.symbol_stats) { // can be empty if filters are applied |
+ var table = this.infobox.append('table') |
+ .attr('border', 1).append('tbody'); |
+ var header = table.append('tr'); |
+ header.append('th').text('Type'); |
+ header.append('th').text('Count'); |
+ header.append('th') |
+ .style('white-space', 'nowrap') |
+ .text('Total Size (Bytes)'); |
+ for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) { |
+ symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x); |
+ var stats = datum.symbol_stats[symbol_type]; |
+ if (stats !== undefined) { |
+ var tr = table.append('tr'); |
+ tr.append('td') |
+ .style('white-space', 'nowrap') |
+ .text(D3SymbolTreeMap._getSymbolDescription( |
+ symbol_type)); |
+ tr.append('td').text(D3SymbolTreeMap._pretty(stats.count)); |
+ tr.append('td').text(D3SymbolTreeMap._pretty(stats.size)); |
+ } |
+ } |
+ } |
+ } |
+ this.infobox.style('visibility', 'visible'); |
+} |
+ |
+D3SymbolTreeMap.prototype._hideInfoBox = function(datum) { |
+ this.infobox.style('visibility', 'hidden'); |
+} |
+ |
+D3SymbolTreeMap.prototype._moveInfoBox = function(event) { |
+ var element = document.getElementById('infobox'); |
+ var w = element.offsetWidth; |
+ var h = element.offsetHeight; |
+ var offsetLeft = 10; |
+ var offsetTop = 10; |
+ |
+ var rightLimit = window.innerWidth; |
+ var rightEdge = event.pageX + offsetLeft + w; |
+ if (rightEdge > rightLimit) { |
+ // Too close to screen edge, reflect around the cursor |
+ offsetLeft = -1 * (w + offsetLeft); |
+ } |
+ |
+ var bottomLimit = window.innerHeight; |
+ var bottomEdge = event.pageY + offsetTop + h; |
+ if (bottomEdge > bottomLimit) { |
+ // Too close ot screen edge, reflect around the cursor |
+ offsetTop = -1 * (h + offsetTop); |
+ } |
+ |
+ this.infobox.style('top', (event.pageY + offsetTop) + 'px') |
+ .style('left', (event.pageX + offsetLeft) + 'px'); |
+} |
+ |
+D3SymbolTreeMap.prototype.biggestSymbols = function(maxRecords) { |
+ var result = undefined; |
+ var smallest = undefined; |
+ var sortFunction = function(a,b) { |
+ var result = b.value - a.value; |
+ if (result !== 0) return result; // sort by size |
+ var pathA = treemap.pathFor(a); // sort by path |
+ var pathB = treemap.pathFor(b); |
+ if (pathA > pathB) return 1; |
+ if (pathB > pathA) return -1; |
+ return a.n - b.n; // sort by symbol name |
+ }; |
+ this.visitFromDisplayedRoot(function(datum) { |
+ if (datum.children) return; // ignore non-leaves |
+ if (!result) { // first element |
+ result = [datum]; |
+ smallest = datum.value; |
+ return; |
+ } |
+ if (result.length < maxRecords) { // filling the array |
+ result.push(datum); |
+ return; |
+ } |
+ if (datum.value > smallest) { // array is already full |
+ result.push(datum); |
+ result.sort(sortFunction); |
+ result.pop(); // get rid of smallest element |
+ smallest = result[maxRecords - 1].value; // new threshold for entry |
+ } |
+ }); |
+ result.sort(sortFunction); |
+ return result; |
+} |
+ |
+D3SymbolTreeMap.prototype.biggestPaths = function(maxRecords) { |
+ var result = undefined; |
+ var smallest = undefined; |
+ var sortFunction = function(a,b) { |
+ var result = b.value - a.value; |
+ if (result !== 0) return result; // sort by size |
+ var pathA = treemap.pathFor(a); // sort by path |
+ var pathB = treemap.pathFor(b); |
+ if (pathA > pathB) return 1; |
+ if (pathB > pathA) return -1; |
+ console.log('warning, multiple entries for the same path: ' + pathA); |
+ return 0; // should be impossible |
+ }; |
+ this.visitFromDisplayedRoot(function(datum) { |
+ if (!datum.lastPathElement) return; // ignore non-files |
+ if (!result) { // first element |
+ result = [datum]; |
+ smallest = datum.value; |
+ return; |
+ } |
+ if (result.length < maxRecords) { // filling the array |
+ result.push(datum); |
+ return; |
+ } |
+ if (datum.value > smallest) { // array is already full |
+ result.push(datum); |
+ result.sort(sortFunction); |
+ result.pop(); // get rid of smallest element |
+ smallest = result[maxRecords - 1].value; // new threshold for entry |
+ } |
+ }); |
+ result.sort(sortFunction); |
+ return result; |
+} |