Skip to content

Commit

Permalink
misc(treemap): add data table (#12363)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark authored Apr 29, 2021
1 parent c93834a commit a1f994e
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 13 deletions.
7 changes: 7 additions & 0 deletions build/build-treemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ async function run() {
appDir: `${__dirname}/../lighthouse-treemap/app`,
html: {path: 'index.html'},
stylesheets: [
fs.readFileSync(require.resolve('tabulator-tables/dist/css/tabulator.min.css'), 'utf8'),
{path: 'styles/*'},
],
javascripts: [
/* eslint-disable max-len */
fs.readFileSync(require.resolve('webtreemap-cdt'), 'utf8'),
fs.readFileSync(require.resolve('tabulator-tables/dist/js/tabulator_core.js'), 'utf8'),
fs.readFileSync(require.resolve('tabulator-tables/dist/js/modules/sort.js'), 'utf8'),
fs.readFileSync(require.resolve('tabulator-tables/dist/js/modules/format.js'), 'utf8'),
fs.readFileSync(require.resolve('tabulator-tables/dist/js/modules/resize_columns.js'), 'utf8'),
/* eslint-enable max-len */
{path: 'src/*'},
],
assets: [
Expand Down
11 changes: 9 additions & 2 deletions lighthouse-treemap/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</head>

<body class="vars">
<main class="lh-main">
<main class="lh-main lh-main--show-table">
<div class="lh-settings">
<header>
<div class="lh-header--section">
Expand Down Expand Up @@ -57,7 +57,10 @@
<span class="lh-header--title lh-text-dim">Lighthouse Treemap</span>
</span>

<select class="bundle-selector"></select>
<span class="lh-header__inputs">
<select class="bundle-selector"></select>
<button class="lh-button lh-button--active lh-button--toggle-table">Toggle Table</button>
</span>
</div>

<div class="lh-header--section">
Expand All @@ -76,6 +79,10 @@
<div class="lh-treemap">
<!-- treemap goes here -->
</div>

<div class="lh-table">
<!-- table goes here -->
</div>
</main>

<script src="src/bundled.js"></script>
Expand Down
140 changes: 135 additions & 5 deletions lighthouse-treemap/app/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@

/* eslint-env browser */

/* globals webtreemap TreemapUtil */
/* globals webtreemap TreemapUtil Tabulator Cell Row */

const UNUSED_BYTES_IGNORE_THRESHOLD = 20 * 1024;
const UNUSED_BYTES_IGNORE_BUNDLE_SOURCE_RATIO = 0.5;

/** @type {TreemapViewer} */
let treemapViewer;

// Make scrolling in Tabulator more performant.
// @ts-expect-error
Cell.prototype.clearHeight = () => {};
// @ts-expect-error
Row.prototype.calcHeight = function() {
this.height = 24;
this.outerHeight = 24;
this.heightStyled = '24px';
};

class TreemapViewer {
/**
* @param {LH.Treemap.Options} options
Expand Down Expand Up @@ -85,6 +95,9 @@ class TreemapViewer {
TreemapUtil.find('.lh-header--size').textContent = TreemapUtil.formatBytes(bytes);

this.createBundleSelector();

const toggleTableBtn = TreemapUtil.find('.lh-button--toggle-table');
toggleTableBtn.addEventListener('click', () => treemapViewer.toggleTable());
}

createBundleSelector() {
Expand Down Expand Up @@ -130,11 +143,11 @@ class TreemapViewer {
}

initListeners() {
window.addEventListener('resize', () => {
this.resize();
});

const treemapEl = TreemapUtil.find('.lh-treemap');

const resizeObserver = new ResizeObserver(() => this.resize());
resizeObserver.observe(treemapEl);

treemapEl.addEventListener('click', (e) => {
if (!(e.target instanceof HTMLElement)) return;
const nodeEl = e.target.closest('.webtreemap-node');
Expand Down Expand Up @@ -294,6 +307,8 @@ class TreemapViewer {
this.el.innerHTML = '';
this.treemap.render(this.el);
TreemapUtil.find('.webtreemap-node').classList.add('webtreemap-node--root');

this.createTable();
}

if (rootChanged || viewChanged) {
Expand All @@ -307,11 +322,126 @@ class TreemapViewer {
};
}

createTable() {
const tableEl = TreemapUtil.find('.lh-table');
tableEl.innerHTML = '';

/** @type {Array<{name: string, bundleNode?: LH.Treemap.Node, resourceBytes: number, unusedBytes?: number}>} */
const data = [];
TreemapUtil.walk(this.currentTreemapRoot, (node, path) => {
if (node.children) return;

const depthOneNode = this.nodeToDepthOneNodeMap.get(node);
const bundleNode = depthOneNode && depthOneNode.children ? depthOneNode : undefined;

let name;
if (bundleNode) {
const bundleNodePath = this.nodeToPathMap.get(bundleNode);
const amountToTrim = bundleNodePath ? bundleNodePath.length : 0; // should never be 0.
name = `(bundle) ${path.slice(amountToTrim).join('/')}`;
} else {
// Elide the first path component, which is common to all nodes.
if (path[0] === this.currentTreemapRoot.name) {
name = path.slice(1).join('/');
} else {
name = path.join('/');
}

// Elide the document URL.
if (name.startsWith(this.currentTreemapRoot.name)) {
name = name.replace(this.currentTreemapRoot.name, '//');
}
}

data.push({
name,
bundleNode,
resourceBytes: node.resourceBytes,
unusedBytes: node.unusedBytes,
});
});

/** @param {Tabulator.CellComponent} cell */
const makeNameTooltip = (cell) => {
/** @type {typeof data[number]} */
const dataRow = cell.getRow().getData();
if (!dataRow.bundleNode) return '';

return `${dataRow.bundleNode.name} (bundle) ${dataRow.name}`;
};

/** @param {Tabulator.CellComponent} cell */
const makeCoverageTooltip = (cell) => {
/** @type {typeof data[number]} */
const dataRow = cell.getRow().getData();
if (!dataRow.unusedBytes) return '';

const percent = Math.floor(100 * dataRow.unusedBytes / dataRow.resourceBytes);
return `${percent}% bytes unused`;
};

const gridEl = document.createElement('div');
tableEl.append(gridEl);

const children = this.currentTreemapRoot.children || [];
const maxSize = Math.max(...children.map(node => node.resourceBytes));

this.table = new Tabulator(gridEl, {
data,
height: '100%',
layout: 'fitColumns',
tooltips: true,
addRowPos: 'top',
resizableColumns: true,
initialSort: [
{column: 'resourceBytes', dir: 'desc'},
],
columns: [
{title: 'Name', field: 'name', widthGrow: 5, tooltip: makeNameTooltip},
{title: 'Size', field: 'resourceBytes', headerSortStartingDir: 'desc', formatter: cell => {
const value = cell.getValue();
return TreemapUtil.formatBytes(value);
}},
// eslint-disable-next-line max-len
{title: 'Unused', field: 'unusedBytes', widthGrow: 1, sorterParams: {alignEmptyValues: 'bottom'}, headerSortStartingDir: 'desc', formatter: cell => {
const value = cell.getValue();
if (value === undefined) return '';
return TreemapUtil.formatBytes(value);
}},
// eslint-disable-next-line max-len
{title: 'Coverage', widthGrow: 3, headerSort: false, tooltip: makeCoverageTooltip, formatter: cell => {
/** @type {typeof data[number]} */
const dataRow = cell.getRow().getData();

const el = TreemapUtil.createElement('div', 'lh-coverage-bar');
if (dataRow.unusedBytes === undefined) return el;

el.style.setProperty('--max', String(maxSize));
el.style.setProperty('--used', String(dataRow.resourceBytes - dataRow.unusedBytes));
el.style.setProperty('--unused', String(dataRow.unusedBytes));

TreemapUtil.createChildOf(el, 'div', 'lh-coverage-bar--used');
TreemapUtil.createChildOf(el, 'div', 'lh-coverage-bar--unused');

return el;
}},
],
});
}

toggleTable() {
const mainEl = TreemapUtil.find('main');
mainEl.classList.toggle('lh-main--show-table');
const buttonEl = TreemapUtil.find('.lh-button--toggle-table');
buttonEl.classList.toggle('lh-button--active');
}

resize() {
if (!this.treemap) throw new Error('must call .render() first');

this.treemap.layout(this.currentTreemapRoot, this.el);
this.updateColors();
if (this.table) this.table.redraw();
}

/**
Expand Down
65 changes: 59 additions & 6 deletions lighthouse-treemap/app/styles/treemap.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
--color-gray-600: #757575;
--color-gray-900: #212121;

--control-background-color: #e7f1fe;
--text-color-secondary: var(--color-gray-600);
--text-color: var(--color-gray-900);
--view-mode-label-color-active: #2a67ce;
--view-mode-sublabel-color-active: #4484f3c7;
--text-color-active: #2a67ce;
--text-color-active-secondary: #4484f3c7;
}

body {
Expand All @@ -26,6 +27,15 @@ body {
flex-direction: column;
}

.lh-button {
background: none;
color: var(--text-color-active);
border: solid 1px #e1e2e5;
}
.lh-button--active {
background-color: var(--control-background-color);
}

.lh-text-dim {
color: var(--text-color-secondary);
}
Expand All @@ -36,8 +46,14 @@ body {
grid-template-rows: 0.1fr 1fr 0fr;
grid-column-gap: 0px;
grid-row-gap: 0px;
transition: grid-template-rows 0.2s;
animation: 0.7s curtain cubic-bezier(0.86, 0, 0.07, 1) 0.4s both;
}
.lh-main--show-table {
grid-template-rows: 0.1fr 1fr 0.3fr;
}

/* TODO: BEM is backwards here and many other places */
.lh-header--section {
display: flex;
align-items: center;
Expand All @@ -53,6 +69,15 @@ body {
overflow: hidden;
white-space: nowrap;
}
.lh-header__inputs {
display: flex;
justify-content: flex-end;
}

.bundle-selector {
width: 50%;
padding: 2px;
}

.bundle-selector {
width: 50%;
Expand All @@ -71,7 +96,35 @@ body {
}

.lh-treemap {
margin: 10px;
margin: 2px;
contain: content;
}

.lh-table {
overflow-y: hidden;
}

.tabulator {
/* Better default for unloaded portions of table. */
background-color: #f3f3f3;
contain: strict;
}

.lh-coverage-bar {
display: flex;
align-items: center;
height: 100%;
}
.lh-coverage-bar--used {
background-color: #63acbe;
width: calc(100% * var(--used) / var(--max));
height: 7px;
}
.lh-coverage-bar--unused {
background-color: #ee442f;
width: calc(100% * var(--unused) / var(--max));
height: 7px;
margin-left: 2px;
}

.view-mode {
Expand All @@ -90,14 +143,14 @@ body {
border-bottom-right-radius: 2px;
}
.view-mode--active {
background-color: #e7f1fe;
background-color: var(--control-background-color);
border-color: #d2e3fc;
}
.view-mode--active .view-mode__label {
color: var(--view-mode-label-color-active);
color: var(--text-color-active);
}
.view-mode--active .view-mode__sublabel {
color: var(--view-mode-sublabel-color-active);
color: var(--text-color-active-secondary);
}

.view-mode__button {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"@types/raven": "^2.5.1",
"@types/resize-observer-browser": "^0.1.1",
"@types/semver": "^5.5.0",
"@types/tabulator-tables": "^4.9.1",
"@types/update-notifier": "^4.1.0",
"@types/ws": "^4.0.1",
"@types/yargs": "^15.0.11",
Expand Down Expand Up @@ -147,6 +148,7 @@
"prettier": "^1.14.3",
"pretty-json-stringify": "^0.0.2",
"puppeteer": "^1.19.0",
"tabulator-tables": "^4.9.3",
"terser": "^5.3.8",
"typed-query-selector": "^2.4.0",
"typescript": "4.2.3",
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==

"@types/tabulator-tables@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@types/tabulator-tables/-/tabulator-tables-4.9.1.tgz#4fbc5465960598308260f50f840d610bcc8ffa3a"
integrity sha512-BfHDeVDfLF5H0HYFQ3ZEfv7J43sD8dw8fhci8juRB5uc0xQWboDd6f1hgLOwQioYhO9vWZEAWfuD6c1wl9qzmw==

"@types/through@*":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93"
Expand Down Expand Up @@ -7544,6 +7549,11 @@ table@^6.0.4:
slice-ansi "^4.0.0"
string-width "^4.2.0"

tabulator-tables@^4.9.3:
version "4.9.3"
resolved "https://registry.yarnpkg.com/tabulator-tables/-/tabulator-tables-4.9.3.tgz#89ea8f9bffc11ba9a789369b5165ac82da26f4f0"
integrity sha512-iwwQqAEGGxlgrBpcmJJvMJrfjGLcCXOB3AOb/DGkXqBy1YKoYA36hIl7qXGp6Jo8dSkzFAlDT6pKLZgyhs9OnQ==

tar-stream@^1.5.0:
version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
Expand Down

0 comments on commit a1f994e

Please sign in to comment.