-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
misc(treemap): root node selector #12360
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,19 +55,25 @@ class TreemapViewer { | |
this.el = el; | ||
this.getHueForKey = TreemapUtil.stableHasher(TreemapUtil.COLOR_HUES); | ||
|
||
// TODO: make "DataSelector" to switch between different groups or specific d1 nodes | ||
const group = 'scripts'; | ||
const depthOneNodes = this.depthOneNodesByGroup[group]; | ||
this.currentTreemapRoot = this.wrapNodesInNewRootNode(depthOneNodes); | ||
TreemapUtil.walk(this.currentTreemapRoot, (node, path) => this.nodeToPathMap.set(node, path)); | ||
|
||
this.viewModes = this.createViewModes(); | ||
this.currentViewMode = this.viewModes[0]; | ||
/* eslint-disable no-unused-expressions */ | ||
/** @type {LH.Treemap.Node} */ | ||
this.currentTreemapRoot; | ||
/** @type {LH.Treemap.ViewMode} */ | ||
this.currentViewMode; | ||
/** @type {LH.Treemap.Selector} */ | ||
this.selector; | ||
/** @type {LH.Treemap.ViewMode[]} */ | ||
this.viewModes; | ||
/** @type {RenderState=} */ | ||
this.previousRenderState; | ||
/** @type {WebTreeMap} */ | ||
this.treemap; | ||
/* eslint-enable no-unused-expressions */ | ||
|
||
renderViewModeButtons(this.viewModes); | ||
this.createHeader(); | ||
this.render(); | ||
this.initListeners(); | ||
this.setSelector({type: 'group', value: 'scripts'}); | ||
this.render(); | ||
} | ||
|
||
createHeader() { | ||
|
@@ -77,6 +83,50 @@ class TreemapViewer { | |
|
||
const bytes = this.wrapNodesInNewRootNode(this.depthOneNodesByGroup.scripts).resourceBytes; | ||
TreemapUtil.find('.lh-header--size').textContent = TreemapUtil.formatBytes(bytes); | ||
|
||
this.createBundleSelector(); | ||
} | ||
|
||
createBundleSelector() { | ||
paulirish marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const bundleSelectorEl = TreemapUtil.find('select.bundle-selector'); | ||
bundleSelectorEl.innerHTML = ''; // Clear just in case document was saved with Ctrl+S. | ||
|
||
/** @type {LH.Treemap.Selector[]} */ | ||
const selectors = []; | ||
|
||
/** | ||
* @param {LH.Treemap.Selector} selector | ||
* @param {string} text | ||
*/ | ||
function makeOption(selector, text) { | ||
const optionEl = TreemapUtil.createChildOf(bundleSelectorEl, 'option'); | ||
optionEl.value = String(selectors.length); | ||
selectors.push(selector); | ||
optionEl.textContent = text; | ||
} | ||
|
||
for (const [group, depthOneNodes] of Object.entries(this.depthOneNodesByGroup)) { | ||
makeOption({type: 'group', value: group}, `All ${group}`); | ||
for (const depthOneNode of depthOneNodes) { | ||
// Only add bundles. | ||
if (!depthOneNode.children) continue; | ||
|
||
makeOption({type: 'depthOneNode', value: depthOneNode.name}, depthOneNode.name); | ||
} | ||
} | ||
|
||
const currentSelectorIndex = selectors.findIndex(s => { | ||
return this.selector && | ||
s.type === this.selector.type && | ||
s.value === this.selector.value; | ||
}); | ||
bundleSelectorEl.value = String(currentSelectorIndex !== -1 ? currentSelectorIndex : 0); | ||
bundleSelectorEl.addEventListener('change', () => { | ||
const index = Number(bundleSelectorEl.value); | ||
const selector = selectors[index]; | ||
this.setSelector(selector); | ||
this.render(); | ||
}); | ||
} | ||
|
||
initListeners() { | ||
|
@@ -173,32 +223,88 @@ class TreemapViewer { | |
return viewModes; | ||
} | ||
|
||
render() { | ||
TreemapUtil.walk(this.currentTreemapRoot, node => { | ||
// @ts-ignore: webtreemap will store `dom` on the data to speed up operations. | ||
// However, when we change the underlying data representation, we need to delete | ||
// all the cached DOM elements. Otherwise, the rendering will be incorrect when, | ||
// for example, switching between "All JavaScript" and a specific bundle. | ||
delete node.dom; | ||
|
||
// @ts-ignore: webtreemap uses `size` to partition the treemap. | ||
node.size = node[this.currentViewMode.partitionBy || 'resourceBytes'] || 0; | ||
}); | ||
webtreemap.sort(this.currentTreemapRoot); | ||
/** | ||
* @param {LH.Treemap.Selector} selector | ||
*/ | ||
setSelector(selector) { | ||
this.selector = selector; | ||
|
||
if (selector.type === 'group') { | ||
this.currentTreemapRoot = | ||
this.wrapNodesInNewRootNode(this.depthOneNodesByGroup[selector.value]); | ||
} else if (selector.type === 'depthOneNode') { | ||
let node; | ||
outer: for (const depthOneNodes of Object.values(this.depthOneNodesByGroup)) { | ||
for (const depthOneNode of depthOneNodes) { | ||
if (depthOneNode.name === selector.value) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if there any any duplicates, then this can match incorrectly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is just depth one nodes, so for a duplicate to be possible that would mean there are multiple instances of network requests with the same URL. |
||
node = depthOneNode; | ||
break outer; | ||
} | ||
} | ||
} | ||
|
||
this.treemap = new webtreemap.TreeMap(this.currentTreemapRoot, { | ||
padding: [16, 3, 3, 3], | ||
spacing: 10, | ||
caption: node => this.makeCaption(node), | ||
}); | ||
if (!node) { | ||
throw new Error('unknown depthOneNode: ' + selector.value); | ||
} | ||
|
||
this.el.innerHTML = ''; | ||
this.treemap.render(this.el); | ||
this.currentTreemapRoot = node; | ||
} else { | ||
throw new Error('unknown selector: ' + JSON.stringify(selector)); | ||
} | ||
|
||
applyActiveClass(this.currentViewMode.id); | ||
TreemapUtil.find('.webtreemap-node').classList.add('webtreemap-node--root'); | ||
this.viewModes = this.createViewModes(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doing this inside of setSelector seems odd. why not create viewmodes in constructor? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the labels are based on the bundle selected. also could be that some view modes are not relevant for a selected bundle (like the upcoming duplicated modules) |
||
if (!this.currentViewMode) this.currentViewMode = this.viewModes[0]; | ||
} | ||
|
||
this.updateColors(); | ||
/** | ||
* @param {LH.Treemap.ViewMode} viewMode | ||
*/ | ||
setViewMode(viewMode) { | ||
this.currentViewMode = viewMode; | ||
} | ||
|
||
render() { | ||
const rootChanged = | ||
!this.previousRenderState || this.previousRenderState.root !== this.currentTreemapRoot; | ||
const viewChanged = | ||
!this.previousRenderState || this.previousRenderState.viewMode !== this.currentViewMode; | ||
|
||
if (rootChanged) { | ||
this.nodeToPathMap = new Map(); | ||
TreemapUtil.walk(this.currentTreemapRoot, (node, path) => this.nodeToPathMap.set(node, path)); | ||
renderViewModeButtons(this.viewModes); | ||
|
||
TreemapUtil.walk(this.currentTreemapRoot, node => { | ||
// @ts-ignore: webtreemap will store `dom` on the data to speed up operations. | ||
// However, when we change the underlying data representation, we need to delete | ||
// all the cached DOM elements. Otherwise, the rendering will be incorrect when, | ||
// for example, switching between "All JavaScript" and a specific bundle. | ||
delete node.dom; | ||
|
||
// @ts-ignore: webtreemap uses `size` to partition the treemap. | ||
node.size = node[this.currentViewMode.partitionBy || 'resourceBytes'] || 0; | ||
}); | ||
webtreemap.sort(this.currentTreemapRoot); | ||
|
||
this.treemap = new webtreemap.TreeMap(this.currentTreemapRoot, { | ||
padding: [16, 3, 3, 3], | ||
spacing: 10, | ||
caption: node => this.makeCaption(node), | ||
}); | ||
this.el.innerHTML = ''; | ||
this.treemap.render(this.el); | ||
TreemapUtil.find('.webtreemap-node').classList.add('webtreemap-node--root'); | ||
} | ||
|
||
if (rootChanged || viewChanged) { | ||
this.updateColors(); | ||
applyActiveClass(this.currentViewMode.id); | ||
} | ||
|
||
this.previousRenderState = { | ||
root: this.currentTreemapRoot, | ||
viewMode: this.currentViewMode, | ||
}; | ||
} | ||
|
||
resize() { | ||
|
@@ -292,7 +398,7 @@ function renderViewModeButtons(viewModes) { | |
}); | ||
|
||
inputEl.addEventListener('click', () => { | ||
treemapViewer.currentViewMode = viewMode; | ||
treemapViewer.setViewMode(viewMode); | ||
treemapViewer.render(); | ||
}); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cant this also be initialized with the same shape? it'd mean you can remove the awkward
!this.previousRenderState ||
bits below, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did try, but it made things more awkward.