-
-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Denis Bardadym
committed
Dec 3, 2023
1 parent
f9a8724
commit 6eb3d19
Showing
18 changed files
with
73,751 additions
and
36,430 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
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,42 @@ | ||
import { FunctionalComponent } from "preact"; | ||
import { useContext, useMemo } from "preact/hooks"; | ||
import { group } from "d3-array"; | ||
import { HierarchyNode, 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,129 @@ | ||
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.