From f72c1055aa2647cb008629e18adf37e96ff168b7 Mon Sep 17 00:00:00 2001 From: Ajaykumar Date: Wed, 12 Feb 2020 15:57:24 -0800 Subject: [PATCH] support to color tiles in output bundles (#10) --- .gitignore | 4 +- bin/index.js | 4 +- example/lasso-analyze.html | 736 +++++++++++++++++++------------------ package-lock.json | 21 +- package.json | 5 +- src/index.js | 7 +- src/tree.js | 30 +- static/webtreemap.js | 421 ++++++++++----------- yarn.lock | 5 + 9 files changed, 644 insertions(+), 589 deletions(-) diff --git a/.gitignore b/.gitignore index 28f1ba7..f9c8c62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -.DS_Store \ No newline at end of file +.DS_Store +lasso-analyze.html +.vscode/ \ No newline at end of file diff --git a/bin/index.js b/bin/index.js index 1461636..6420374 100644 --- a/bin/index.js +++ b/bin/index.js @@ -10,11 +10,13 @@ const borderX = `${Array(30).join('-')}\n`; const input = argv._ || []; // extract output file. let outputFile = parseArgs(process.argv).output; +let colors = parseArgs(process.argv).c; +colors = colors ? true : false; if (input.length > 0) { input.map((fileName) => { createBundle({ path: fileName, outputPath: 'lasso-analyze.js' }).createBundle; - lassoAnalyzer('lasso-analyze.js', outputFile); + lassoAnalyzer('lasso-analyze.js', { outputFile, colors }); }); outputFile = outputFile || 'lasso-analyze'; const startLog = `${borderX}` + `${outputFile}.html is created \n` + diff --git a/example/lasso-analyze.html b/example/lasso-analyze.html index ff6d1eb..5838a3b 100644 --- a/example/lasso-analyze.html +++ b/example/lasso-analyze.html @@ -2,381 +2,397 @@ - - Lasso Bundle Analyzer + + Lasso Bundle Analyzer - - + + -
- - + window.appendTreemap = appendTreemap; + })(window); + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b20ddeb..9950116 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lasso-analyzer", - "version": "1.1.10", + "version": "1.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -25,11 +25,6 @@ "concat-map": "0.0.1" } }, - "bundle-me": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bundle-me/-/bundle-me-1.1.0.tgz", - "integrity": "sha512-qtOOuqqqlaI93pKomLmuZx1F1Mn8UMCbyp1CGWAFa8CcX3CRsRdDRiCY6QneGdyEPu0pVWUd6ofyFgxxgsjFFw==" - }, "cli": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", @@ -185,6 +180,8 @@ }, "jshint": { "version": "2.10.2", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", + "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", "dev": true, "requires": { "cli": "~1.0.0", @@ -199,6 +196,8 @@ }, "lasso-unpack": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lasso-unpack/-/lasso-unpack-1.0.2.tgz", + "integrity": "sha512-pL6lvOKXJhF8PhrLF4JAUzFvp20rFNizh8ZuyusmM7jwczUlClVOhpcjei8IxbokitfTiFqpLBahnJZXkaxyTw==", "requires": { "acorn": "^5.5.3", "minimist": "1.2.0" @@ -233,12 +232,22 @@ "wrappy": "1" } }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "randomcolor": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.5.4.tgz", + "integrity": "sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA==" + }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", diff --git a/package.json b/package.json index 555a953..f4bb5db 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "dependencies": { "bundle-me": "^1.1.4", "lasso-unpack": "1.0.2", - "opener": "^1.5.1" + "opener": "^1.5.1", + "randomcolor": "^0.5.4" }, "repository": { "type": "git", @@ -35,4 +36,4 @@ "devDependencies": { "jshint": "^2.9.6" } -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index ae694ed..c64aefd 100644 --- a/src/index.js +++ b/src/index.js @@ -4,22 +4,23 @@ const lassoUnpack = require('lasso-unpack'); const opener = require('opener'); const Tree = require('./tree'); -function bundleAnalyzer(fileName, bundleName) { +function bundleAnalyzer(fileName, options) { // load lasso-unpack and create lasso-stats.json // unpack the bundle using lasso-unpack. lassoUnpack(fileName); const readFile = fs.readFileSync(path.resolve("lasso-stats.json"), 'utf8'); const readJSON = JSON.parse(readFile); + const bundleName = (options && options.bundleName) || 'lasso-analyze' if (readJSON.length > 0) { readJSON.shift() }; const tree = new Tree('/'); + tree.setEnableColors(options && options.colors || false); readJSON.forEach((source) => { - tree.createNode(source, tree); + tree.createNode(source, tree, tree.isColorEnabled()); }); tree.createTile(tree, tree.data['$area']); const html = generateHTML(tree); - if (!bundleName) bundleName = "lasso-analyze"; fs.writeFileSync(getOutputHTML(bundleName), html); // open the browser with generated html opener(getOutputHTML(bundleName)); diff --git a/src/tree.js b/src/tree.js index 04f9665..5d98189 100644 --- a/src/tree.js +++ b/src/tree.js @@ -1,5 +1,6 @@ // Inspired from https://github.com/evmar/webtreemap/blob/master/tree.ts - +'use strict'; +const randomColor = require('randomcolor'); class Tree { constructor(fileName) { this.name = fileName || ''; @@ -8,6 +9,8 @@ class Tree { '$area': 0 }; this.children = []; + this.className = ''; + this.enableColors = false; } setFileName(name) { @@ -16,6 +19,12 @@ class Tree { getFileName() { return this.name; } + setEnableColors(enableColors) { + this.enableColors = enableColors; + } + isColorEnabled() { + return this.enableColors; + } setSize(size) { this.data.$area = size; } @@ -28,23 +37,28 @@ class Tree { getSize() { return this.data.$area; } + setClassName(className) { + this.className = className; + } // add new nodes - createNode(source, tree) { + createNode(source, tree, enableColors) { const parts = source.path.split('/'); - var node = tree; + let node = tree; node.data['$area'] += source.size; - parts.forEach(function (part) { + let color = randomColor({ hue: 'green' }); + parts.forEach((part) => { let child = node.children.find(function (child) { return child.name == part; }); if (!child) { child = new Tree(part); + applyColor(child, color, enableColors); node.children.push(child); } node = child; node.data['$area'] += source.size; }); - }; + } createTile(node, totalSize) { const size = node.data['$area']; @@ -53,7 +67,7 @@ class Tree { node.children.forEach((eachNode) => { this.createTile(eachNode, totalSize) }); - }; + } }; // convert bytes in to KB, MB, etc. // https://stackoverflow.com/a/18650828/388951 @@ -66,4 +80,8 @@ function bytesToSize(bytes, decimals) { return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } +function applyColor(child, color, enableColors) { + if (enableColors) child.setClassName(color); +} + module.exports = Tree; \ No newline at end of file diff --git a/static/webtreemap.js b/static/webtreemap.js index 6e731ee..6257b5f 100644 --- a/static/webtreemap.js +++ b/static/webtreemap.js @@ -18,232 +18,233 @@ // We could support arbitrary borders using getComputedStyle(), but I am // skeptical the extra complexity (and performance hit) is worth it. -;(function() { -var kBorderWidth = 1; - -// Padding around contents. -// TODO: do this with a nested div to allow it to be CSS-styleable. -var kPadding = 4; - -// x/y ratio to aim for -- wider rectangles are better for text display -var kAspectRatio = 1.2; - -var focused = null; - -function focus(tree) { - focused = tree; - - // Hide all visible siblings of all our ancestors by lowering them. - var level = 0; - var root = tree; - while (root.parent) { - root = root.parent; - level += 1; - for (var i = 0, sibling; sibling = root.children[i]; ++i) { - if (sibling.dom) - sibling.dom.style.zIndex = 0; +; (function () { + var kBorderWidth = 1; + + // Padding around contents. + // TODO: do this with a nested div to allow it to be CSS-styleable. + var kPadding = 4; + + // x/y ratio to aim for -- wider rectangles are better for text display + var kAspectRatio = 1.2; + + var focused = null; + + function focus(tree) { + focused = tree; + + // Hide all visible siblings of all our ancestors by lowering them. + var level = 0; + var root = tree; + while (root.parent) { + root = root.parent; + level += 1; + for (var i = 0, sibling; sibling = root.children[i]; ++i) { + if (sibling.dom) + sibling.dom.style.zIndex = 0; + } } - } - var width = root.dom.offsetWidth; - var height = root.dom.offsetHeight; - // Unhide (raise) and maximize us and our ancestors. - for (var t = tree; t.parent; t = t.parent) { - // Shift off by border so we don't get nested borders. - // TODO: actually make nested borders work (need to adjust width/height). - position(t.dom, -kBorderWidth, -kBorderWidth, width, height); - t.dom.style.zIndex = 1; - } - // And layout into the topmost box. - layout(tree, level, width, height); -} - -function makeDom(tree, level) { - var dom = document.createElement('div'); - dom.style.zIndex = 1; - dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4); - if (tree.data['$symbol']) { - dom.className += (' webtreemap-symbol-' + - tree.data['$symbol'].replace(' ', '_')); - } - if (tree.data['$dominant_symbol']) { - dom.className += (' webtreemap-symbol-' + - tree.data['$dominant_symbol'].replace(' ', '_')); - dom.className += (' webtreemap-aggregate'); + var width = root.dom.offsetWidth; + var height = root.dom.offsetHeight; + // Unhide (raise) and maximize us and our ancestors. + for (var t = tree; t.parent; t = t.parent) { + // Shift off by border so we don't get nested borders. + // TODO: actually make nested borders work (need to adjust width/height). + position(t.dom, -kBorderWidth, -kBorderWidth, width, height); + t.dom.style.zIndex = 1; + } + // And layout into the topmost box. + layout(tree, level, width, height); } - dom.onmousedown = function(e) { - if (e.button == 0) { - if (focused && tree == focused && focused.parent) { - focus(focused.parent); - } else { - focus(tree); - } + function makeDom(tree, level) { + var dom = document.createElement('div'); + dom.style.zIndex = 1; + dom.style.backgroundColor = tree.className ? tree.className : ""; + dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4); + if (tree.data['$symbol']) { + dom.className += (' webtreemap-symbol-' + + tree.data['$symbol'].replace(' ', '_')); } - e.stopPropagation(); - return true; - }; - - var caption = document.createElement('div'); - caption.className = 'webtreemap-caption'; - caption.innerHTML = tree.name; - dom.appendChild(caption); - dom.title = tree.name; - - tree.dom = dom; - return dom; -} - -function position(dom, x, y, width, height) { - // CSS width/height does not include border. - width -= kBorderWidth*2; - height -= kBorderWidth*2; - - dom.style.left = x + 'px'; - dom.style.top = y + 'px'; - dom.style.width = Math.max(width, 0) + 'px'; - dom.style.height = Math.max(height, 0) + 'px'; -} - -// Given a list of rectangles |nodes|, the 1-d space available -// |space|, and a starting rectangle index |start|, compute an span of -// rectangles that optimizes a pleasant aspect ratio. -// -// Returns [end, sum], where end is one past the last rectangle and sum is the -// 2-d sum of the rectangles' areas. -function selectSpan(nodes, space, start) { - // Add rectangle one by one, stopping when aspect ratios begin to go - // bad. Result is [start,end) covering the best run for this span. - // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.36.6685 - var node = nodes[start]; - var rmin = node.data['$area']; // Smallest seen child so far. - var rmax = rmin; // Largest child. - var rsum = 0; // Sum of children in this span. - var last_score = 0; // Best score yet found. - for (var end = start; node = nodes[end]; ++end) { - var size = node.data['$area']; - if (size < rmin) - rmin = size; - if (size > rmax) - rmax = size; - rsum += size; - - // This formula is from the paper, but you can easily prove to - // yourself it's taking the larger of the x/y aspect ratio or the - // y/x aspect ratio. The additional magic fudge constant of kAspectRatio - // lets us prefer wider rectangles to taller ones. - var score = Math.max(space*space*rmax / (rsum*rsum), - kAspectRatio*rsum*rsum / (space*space*rmin)); - if (last_score && score > last_score) { - rsum -= size; // Undo size addition from just above. - break; + if (tree.data['$dominant_symbol']) { + dom.className += (' webtreemap-symbol-' + + tree.data['$dominant_symbol'].replace(' ', '_')); + dom.className += (' webtreemap-aggregate'); } - last_score = score; + + dom.onmousedown = function (e) { + if (e.button == 0) { + if (focused && tree == focused && focused.parent) { + focus(focused.parent); + } else { + focus(tree); + } + } + e.stopPropagation(); + return true; + }; + + var caption = document.createElement('div'); + caption.className = 'webtreemap-caption'; + caption.innerHTML = tree.name; + dom.appendChild(caption); + dom.title = tree.name; + + tree.dom = dom; + return dom; } - return [end, rsum]; -} - -function layout(tree, level, width, height) { - if (!('children' in tree)) - return; - - var total = tree.data['$area']; - - // XXX why do I need an extra -1/-2 here for width/height to look right? - var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2; - x1 += kPadding; y1 += kPadding; - x2 -= kPadding; y2 -= kPadding; - y1 += 14; // XXX get first child height for caption spacing - - var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1))); - - // The algorithm does best at laying out items from largest to smallest. - // Sort them to ensure this. - if (!tree.children.sorted) { - tree.children.sort(function (a, b) { - return b.data['$area'] - a.data['$area']; - }); - tree.children.sorted = true; + + function position(dom, x, y, width, height) { + // CSS width/height does not include border. + width -= kBorderWidth * 2; + height -= kBorderWidth * 2; + + dom.style.left = x + 'px'; + dom.style.top = y + 'px'; + dom.style.width = Math.max(width, 0) + 'px'; + dom.style.height = Math.max(height, 0) + 'px'; } - for (var start = 0, child; child = tree.children[start]; ++start) { - if (x2 - x1 < 60 || y2 - y1 < 40) { - if (child.dom) { - child.dom.style.zIndex = 0; - position(child.dom, -2, -2, 0, 0); + // Given a list of rectangles |nodes|, the 1-d space available + // |space|, and a starting rectangle index |start|, compute an span of + // rectangles that optimizes a pleasant aspect ratio. + // + // Returns [end, sum], where end is one past the last rectangle and sum is the + // 2-d sum of the rectangles' areas. + function selectSpan(nodes, space, start) { + // Add rectangle one by one, stopping when aspect ratios begin to go + // bad. Result is [start,end) covering the best run for this span. + // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.36.6685 + var node = nodes[start]; + var rmin = node.data['$area']; // Smallest seen child so far. + var rmax = rmin; // Largest child. + var rsum = 0; // Sum of children in this span. + var last_score = 0; // Best score yet found. + for (var end = start; node = nodes[end]; ++end) { + var size = node.data['$area']; + if (size < rmin) + rmin = size; + if (size > rmax) + rmax = size; + rsum += size; + + // This formula is from the paper, but you can easily prove to + // yourself it's taking the larger of the x/y aspect ratio or the + // y/x aspect ratio. The additional magic fudge constant of kAspectRatio + // lets us prefer wider rectangles to taller ones. + var score = Math.max(space * space * rmax / (rsum * rsum), + kAspectRatio * rsum * rsum / (space * space * rmin)); + if (last_score && score > last_score) { + rsum -= size; // Undo size addition from just above. + break; } - continue; + last_score = score; } + return [end, rsum]; + } - // Dynamically decide whether to split in x or y based on aspect ratio. - var ysplit = ((y2 - y1) / (x2 - x1)) > kAspectRatio; - - var space; // Space available along layout axis. - if (ysplit) - space = (y2 - y1) * pixels_to_units; - else - space = (x2 - x1) * pixels_to_units; - - var span = selectSpan(tree.children, space, start); - var end = span[0], rsum = span[1]; - - // Now that we've selected a span, lay out rectangles [start,end) in our - // available space. - var x = x1, y = y1; - for (var i = start; i < end; ++i) { - child = tree.children[i]; - if (!child.dom) { - child.parent = tree; - child.dom = makeDom(child, level + 1); - tree.dom.appendChild(child.dom); - } else { - child.dom.style.zIndex = 1; - } - var size = child.data['$area']; - var frac = size / rsum; - if (ysplit) { - width = rsum / space; - height = size / width; - } else { - height = rsum / space; - width = size / height; + function layout(tree, level, width, height) { + if (!('children' in tree)) + return; + + var total = tree.data['$area']; + + // XXX why do I need an extra -1/-2 here for width/height to look right? + var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2; + x1 += kPadding; y1 += kPadding; + x2 -= kPadding; y2 -= kPadding; + y1 += 14; // XXX get first child height for caption spacing + + var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1))); + + // The algorithm does best at laying out items from largest to smallest. + // Sort them to ensure this. + if (!tree.children.sorted) { + tree.children.sort(function (a, b) { + return b.data['$area'] - a.data['$area']; + }); + tree.children.sorted = true; + } + + for (var start = 0, child; child = tree.children[start]; ++start) { + if (x2 - x1 < 60 || y2 - y1 < 40) { + if (child.dom) { + child.dom.style.zIndex = 0; + position(child.dom, -2, -2, 0, 0); + } + continue; } - width /= pixels_to_units; - height /= pixels_to_units; - width = Math.round(width); - height = Math.round(height); - position(child.dom, x, y, width, height); - if ('children' in child) { - layout(child, level + 1, width, height); + + // Dynamically decide whether to split in x or y based on aspect ratio. + var ysplit = ((y2 - y1) / (x2 - x1)) > kAspectRatio; + + var space; // Space available along layout axis. + if (ysplit) + space = (y2 - y1) * pixels_to_units; + else + space = (x2 - x1) * pixels_to_units; + + var span = selectSpan(tree.children, space, start); + var end = span[0], rsum = span[1]; + + // Now that we've selected a span, lay out rectangles [start,end) in our + // available space. + var x = x1, y = y1; + for (var i = start; i < end; ++i) { + child = tree.children[i]; + if (!child.dom) { + child.parent = tree; + child.dom = makeDom(child, level + 1); + tree.dom.appendChild(child.dom); + } else { + child.dom.style.zIndex = 1; + } + var size = child.data['$area']; + var frac = size / rsum; + if (ysplit) { + width = rsum / space; + height = size / width; + } else { + height = rsum / space; + width = size / height; + } + width /= pixels_to_units; + height /= pixels_to_units; + width = Math.round(width); + height = Math.round(height); + position(child.dom, x, y, width, height); + if ('children' in child) { + layout(child, level + 1, width, height); + } + if (ysplit) + y += height; + else + x += width; } + + // Shrink our available space based on the amount we used. if (ysplit) - y += height; + x1 += Math.round((rsum / space) / pixels_to_units); else - x += width; - } + y1 += Math.round((rsum / space) / pixels_to_units); - // Shrink our available space based on the amount we used. - if (ysplit) - x1 += Math.round((rsum / space) / pixels_to_units); - else - y1 += Math.round((rsum / space) / pixels_to_units); + // end points one past where we ended, which is where we want to + // begin the next iteration, but subtract one to balance the ++ in + // the loop. + start = end - 1; + } + } - // end points one past where we ended, which is where we want to - // begin the next iteration, but subtract one to balance the ++ in - // the loop. - start = end - 1; + function appendTreemap(dom, data) { + var style = getComputedStyle(dom, null); + var width = parseInt(style.width); + var height = parseInt(style.height); + if (!data.dom) + makeDom(data, 0); + dom.appendChild(data.dom); + position(data.dom, 0, 0, width, height); + layout(data, 0, width, height); } -} - -function appendTreemap(dom, data) { - var style = getComputedStyle(dom, null); - var width = parseInt(style.width); - var height = parseInt(style.height); - if (!data.dom) - makeDom(data, 0); - dom.appendChild(data.dom); - position(data.dom, 0, 0, width, height); - layout(data, 0, width, height); -} - -window.appendTreemap = appendTreemap; + + window.appendTreemap = appendTreemap; })(window); diff --git a/yarn.lock b/yarn.lock index 4403085..eabbf45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -205,6 +205,11 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +randomcolor@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.5.4.tgz#df615b13f25b89ea58c5f8f72647f0a6f07adcc3" + integrity sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA== + readable-stream@1.1: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"