-
-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revert "Remove flamegraph, it does not bring any difference comparing…
… to treemap or sunburst" This reverts commit 112ac38.
- Loading branch information
Denis Bardadym
committed
Jan 6, 2025
1 parent
739c254
commit 899472c
Showing
19 changed files
with
94,587 additions
and
47,002 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
# Changelog | ||
|
||
## 5.14.0 | ||
|
||
* Return `flamegraph` | ||
|
||
## 5.13.0 | ||
|
||
* Remove `flamegraph` template | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
"use strict"; | ||
|
||
export type TemplateType = "sunburst" | "treemap" | "network" | "raw-data" | "list"; | ||
export type TemplateType = "sunburst" | "treemap" | "network" | "raw-data" | "list" | "flamegraph"; | ||
|
||
const templates: ReadonlyArray<TemplateType> = [ | ||
"sunburst", | ||
"treemap", | ||
"network", | ||
"list", | ||
"raw-data", | ||
"flamegraph", | ||
]; | ||
|
||
export default templates; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { FunctionalComponent } from "preact"; | ||
import { useState, useEffect } from "preact/hooks"; | ||
import { HierarchyRectangularNode } from "d3-hierarchy"; | ||
|
||
import { ModuleTree, ModuleTreeLeaf, SizeKey } from "../../shared/types"; | ||
import { FlameGraph } from "./flamegraph"; | ||
import { Tooltip } from "./tooltip"; | ||
|
||
export interface ChartProps { | ||
root: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>; | ||
sizeProperty: SizeKey; | ||
selectedNode: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined; | ||
setSelectedNode: ( | ||
node: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined, | ||
) => void; | ||
} | ||
|
||
export const Chart: FunctionalComponent<ChartProps> = ({ | ||
root, | ||
sizeProperty, | ||
selectedNode, | ||
setSelectedNode, | ||
}) => { | ||
const [showTooltip, setShowTooltip] = useState<boolean>(false); | ||
const [tooltipNode, setTooltipNode] = useState< | ||
HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined | ||
>(undefined); | ||
|
||
useEffect(() => { | ||
const handleMouseOut = () => { | ||
setShowTooltip(false); | ||
}; | ||
|
||
document.addEventListener("mouseover", handleMouseOut); | ||
return () => { | ||
document.removeEventListener("mouseover", handleMouseOut); | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<> | ||
<FlameGraph | ||
root={root} | ||
onNodeHover={(node) => { | ||
setTooltipNode(node); | ||
setShowTooltip(true); | ||
}} | ||
selectedNode={selectedNode} | ||
onNodeClick={(node) => { | ||
setSelectedNode(selectedNode === node ? undefined : node); | ||
}} | ||
/> | ||
<Tooltip visible={showTooltip} node={tooltipNode} root={root} sizeProperty={sizeProperty} /> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { scaleSequential, scaleLinear } from "d3-scale"; | ||
import { hsl, RGBColor } from "d3-color"; | ||
|
||
import { HierarchyNode } from "d3-hierarchy"; | ||
import { COLOR_BASE, CssColor } from "../color"; | ||
import { ModuleTree, ModuleTreeLeaf } from "../../shared/types"; | ||
|
||
// https://www.w3.org/TR/WCAG20/#relativeluminancedef | ||
const rc = 0.2126; | ||
const gc = 0.7152; | ||
const bc = 0.0722; | ||
// low-gamma adjust coefficient | ||
const lowc = 1 / 12.92; | ||
|
||
function adjustGamma(p: number) { | ||
return Math.pow((p + 0.055) / 1.055, 2.4); | ||
} | ||
|
||
function relativeLuminance(o: RGBColor) { | ||
const rsrgb = o.r / 255; | ||
const gsrgb = o.g / 255; | ||
const bsrgb = o.b / 255; | ||
|
||
const r = rsrgb <= 0.03928 ? rsrgb * lowc : adjustGamma(rsrgb); | ||
const g = gsrgb <= 0.03928 ? gsrgb * lowc : adjustGamma(gsrgb); | ||
const b = bsrgb <= 0.03928 ? bsrgb * lowc : adjustGamma(bsrgb); | ||
|
||
return r * rc + g * gc + b * bc; | ||
} | ||
|
||
export interface NodeColor { | ||
backgroundColor: CssColor; | ||
fontColor: CssColor; | ||
} | ||
|
||
export type NodeColorGetter = (node: HierarchyNode<ModuleTree | ModuleTreeLeaf>) => NodeColor; | ||
|
||
const createRainbowColor = (root: HierarchyNode<ModuleTree | ModuleTreeLeaf>): NodeColorGetter => { | ||
const colorParentMap = new Map<HierarchyNode<ModuleTree | ModuleTreeLeaf>, CssColor>(); | ||
colorParentMap.set(root, COLOR_BASE); | ||
|
||
if (root.children != null) { | ||
const colorScale = scaleSequential([0, root.children.length], (n) => hsl(360 * n, 0.3, 0.85)); | ||
root.children.forEach((c, id) => { | ||
colorParentMap.set(c, colorScale(id).toString()); | ||
}); | ||
} | ||
|
||
const colorMap = new Map<HierarchyNode<ModuleTree | ModuleTreeLeaf>, NodeColor>(); | ||
|
||
const lightScale = scaleLinear().domain([0, root.height]).range([0.9, 0.3]); | ||
|
||
const getBackgroundColor = (node: HierarchyNode<ModuleTree | ModuleTreeLeaf>) => { | ||
const parents = node.ancestors(); | ||
const colorStr = | ||
parents.length === 1 | ||
? colorParentMap.get(parents[0]) | ||
: colorParentMap.get(parents[parents.length - 2]); | ||
|
||
const hslColor = hsl(colorStr as string); | ||
hslColor.l = lightScale(node.depth); | ||
|
||
return hslColor; | ||
}; | ||
|
||
return (node: HierarchyNode<ModuleTree | ModuleTreeLeaf>): NodeColor => { | ||
if (!colorMap.has(node)) { | ||
const backgroundColor = getBackgroundColor(node); | ||
const l = relativeLuminance(backgroundColor.rgb()); | ||
const fontColor = l > 0.19 ? "#000" : "#fff"; | ||
colorMap.set(node, { | ||
backgroundColor: backgroundColor.toString(), | ||
fontColor, | ||
}); | ||
} | ||
|
||
return colorMap.get(node) as NodeColor; | ||
}; | ||
}; | ||
|
||
export default createRainbowColor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const TOP_PADDING = 20; | ||
export const PADDING = 2; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { FunctionalComponent } from "preact"; | ||
import { useContext } from "preact/hooks"; | ||
import { HierarchyRectangularNode } from "d3-hierarchy"; | ||
|
||
import { ModuleTree, ModuleTreeLeaf } from "../../shared/types"; | ||
import { Node } from "./node"; | ||
import { StaticContext } from "./index"; | ||
|
||
export interface FlameGraphProps { | ||
root: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>; | ||
onNodeHover: (event: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>) => void; | ||
selectedNode: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined; | ||
onNodeClick: (node: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>) => void; | ||
} | ||
|
||
export const FlameGraph: FunctionalComponent<FlameGraphProps> = ({ | ||
root, | ||
onNodeHover, | ||
selectedNode, | ||
onNodeClick, | ||
}) => { | ||
const { width, height, getModuleIds } = useContext(StaticContext); | ||
|
||
return ( | ||
<svg xmlns="http://www.w3.org/2000/svg" viewBox={`0 0 ${width} ${height}`}> | ||
{root.descendants().map((node) => { | ||
return ( | ||
<Node | ||
key={getModuleIds(node.data).nodeUid.id} | ||
node={node} | ||
onMouseOver={onNodeHover} | ||
selected={selectedNode === node} | ||
onClick={onNodeClick} | ||
/> | ||
); | ||
})} | ||
</svg> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { createContext, render } from "preact"; | ||
import { hierarchy, HierarchyNode, partition, PartitionLayout } from "d3-hierarchy"; | ||
import { | ||
isModuleTree, | ||
ModuleLengths, | ||
ModuleTree, | ||
ModuleTreeLeaf, | ||
SizeKey, | ||
VisualizerData, | ||
} from "../../shared/types"; | ||
|
||
import { generateUniqueId, Id } from "../uid"; | ||
import { getAvailableSizeOptions } from "../sizes"; | ||
import { Main } from "./main"; | ||
import createRainbowColor, { NodeColorGetter } from "./color"; | ||
|
||
import "../style/style-flamegraph.scss"; | ||
import { PADDING } from "./const"; | ||
|
||
export interface StaticData { | ||
data: VisualizerData; | ||
availableSizeProperties: SizeKey[]; | ||
width: number; | ||
height: number; | ||
} | ||
|
||
export interface ModuleIds { | ||
nodeUid: Id; | ||
clipUid: Id; | ||
} | ||
|
||
export interface ChartData { | ||
layout: PartitionLayout<ModuleTree | ModuleTreeLeaf>; | ||
rawHierarchy: HierarchyNode<ModuleTree | ModuleTreeLeaf>; | ||
getModuleSize: (node: ModuleTree | ModuleTreeLeaf, sizeKey: SizeKey) => number; | ||
getModuleIds: (node: ModuleTree | ModuleTreeLeaf) => ModuleIds; | ||
getModuleColor: NodeColorGetter; | ||
} | ||
|
||
export type Context = StaticData & ChartData; | ||
|
||
export const StaticContext = createContext<Context>({} as unknown as Context); | ||
|
||
const drawChart = ( | ||
parentNode: Element, | ||
data: VisualizerData, | ||
width: number, | ||
height: number, | ||
): void => { | ||
const availableSizeProperties = getAvailableSizeOptions(data.options); | ||
|
||
console.time("layout create"); | ||
|
||
const layout = partition<ModuleTree | ModuleTreeLeaf>() | ||
.size([width, height]) | ||
.padding(PADDING) | ||
.round(true); | ||
|
||
console.timeEnd("layout create"); | ||
|
||
console.time("rawHierarchy create"); | ||
const rawHierarchy = hierarchy<ModuleTree | ModuleTreeLeaf>(data.tree); | ||
console.timeEnd("rawHierarchy create"); | ||
|
||
const nodeSizesCache = new Map<ModuleTree | ModuleTreeLeaf, ModuleLengths>(); | ||
|
||
const nodeIdsCache = new Map<ModuleTree | ModuleTreeLeaf, ModuleIds>(); | ||
|
||
const getModuleSize = (node: ModuleTree | ModuleTreeLeaf, sizeKey: SizeKey) => | ||
nodeSizesCache.get(node)?.[sizeKey] ?? 0; | ||
|
||
console.time("rawHierarchy eachAfter cache"); | ||
rawHierarchy.eachAfter((node) => { | ||
const nodeData = node.data; | ||
|
||
nodeIdsCache.set(nodeData, { | ||
nodeUid: generateUniqueId("node"), | ||
clipUid: generateUniqueId("clip"), | ||
}); | ||
|
||
const sizes: ModuleLengths = { renderedLength: 0, gzipLength: 0, brotliLength: 0 }; | ||
if (isModuleTree(nodeData)) { | ||
for (const sizeKey of availableSizeProperties) { | ||
sizes[sizeKey] = nodeData.children.reduce( | ||
(acc, child) => getModuleSize(child, sizeKey) + acc, | ||
0, | ||
); | ||
} | ||
} else { | ||
for (const sizeKey of availableSizeProperties) { | ||
sizes[sizeKey] = data.nodeParts[nodeData.uid][sizeKey] ?? 0; | ||
} | ||
} | ||
nodeSizesCache.set(nodeData, sizes); | ||
}); | ||
console.timeEnd("rawHierarchy eachAfter cache"); | ||
|
||
const getModuleIds = (node: ModuleTree | ModuleTreeLeaf) => nodeIdsCache.get(node) as ModuleIds; | ||
|
||
console.time("color"); | ||
const getModuleColor = createRainbowColor(rawHierarchy); | ||
console.timeEnd("color"); | ||
|
||
render( | ||
<StaticContext.Provider | ||
value={{ | ||
data, | ||
availableSizeProperties, | ||
width, | ||
height, | ||
getModuleSize, | ||
getModuleIds, | ||
getModuleColor, | ||
rawHierarchy, | ||
layout, | ||
}} | ||
> | ||
<Main /> | ||
</StaticContext.Provider>, | ||
parentNode, | ||
); | ||
}; | ||
|
||
export default drawChart; |
Oops, something went wrong.