Skip to content

Commit 899472c

Browse files
author
Denis Bardadym
committed
Revert "Remove flamegraph, it does not bring any difference comparing to treemap or sunburst"
This reverts commit 112ac38.
1 parent 739c254 commit 899472c

19 files changed

+94587
-47002
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 5.14.0
4+
5+
* Return `flamegraph`
6+
37
## 5.13.0
48

59
* Remove `flamegraph` template

plugin/render-template.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ const TEMPLATE_TYPE_RENDERED: Record<
123123
treemap: buildHtml("treemap"),
124124
"raw-data": async ({ data }) => outputRawData(data),
125125
list: async ({ data }) => outputPlainTextList(data),
126+
flamegraph: buildHtml("flamegraph"),
126127
};
127128

128129
export const renderTemplate = (templateType: TemplateType, options: RenderTemplateOptions) => {

plugin/template-types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"use strict";
22

3-
export type TemplateType = "sunburst" | "treemap" | "network" | "raw-data" | "list";
3+
export type TemplateType = "sunburst" | "treemap" | "network" | "raw-data" | "list" | "flamegraph";
44

55
const templates: ReadonlyArray<TemplateType> = [
66
"sunburst",
77
"treemap",
88
"network",
99
"list",
1010
"raw-data",
11+
"flamegraph",
1112
];
1213

1314
export default templates;

rollup.config-dev.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const postcssUrl = require("postcss-url");
66

77
const { visualizer } = require(".");
88

9-
const HTML_TEMPLATE = ["treemap", "sunburst", "network"];
9+
const HTML_TEMPLATE = ["treemap", "sunburst", "network", "flamegraph"];
1010
const PLAIN_TEMPLATE = ["raw-data", "list"];
1111
const ALL_TEMPLATE = [...HTML_TEMPLATE, ...PLAIN_TEMPLATE];
1212

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const typescript = require("@rollup/plugin-typescript");
44
const postcss = require("rollup-plugin-postcss");
55
const postcssUrl = require("postcss-url");
66

7-
const HTML_TEMPLATE = ["treemap", "sunburst", "network"];
7+
const HTML_TEMPLATE = ["treemap", "sunburst", "network", "flamegraph"];
88

99
/** @type {import('rollup').RollupOptions} */
1010
module.exports = HTML_TEMPLATE.map((templateType) => ({

src/flamegraph/chart.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { FunctionalComponent } from "preact";
2+
import { useState, useEffect } from "preact/hooks";
3+
import { HierarchyRectangularNode } from "d3-hierarchy";
4+
5+
import { ModuleTree, ModuleTreeLeaf, SizeKey } from "../../shared/types";
6+
import { FlameGraph } from "./flamegraph";
7+
import { Tooltip } from "./tooltip";
8+
9+
export interface ChartProps {
10+
root: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>;
11+
sizeProperty: SizeKey;
12+
selectedNode: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined;
13+
setSelectedNode: (
14+
node: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined,
15+
) => void;
16+
}
17+
18+
export const Chart: FunctionalComponent<ChartProps> = ({
19+
root,
20+
sizeProperty,
21+
selectedNode,
22+
setSelectedNode,
23+
}) => {
24+
const [showTooltip, setShowTooltip] = useState<boolean>(false);
25+
const [tooltipNode, setTooltipNode] = useState<
26+
HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined
27+
>(undefined);
28+
29+
useEffect(() => {
30+
const handleMouseOut = () => {
31+
setShowTooltip(false);
32+
};
33+
34+
document.addEventListener("mouseover", handleMouseOut);
35+
return () => {
36+
document.removeEventListener("mouseover", handleMouseOut);
37+
};
38+
}, []);
39+
40+
return (
41+
<>
42+
<FlameGraph
43+
root={root}
44+
onNodeHover={(node) => {
45+
setTooltipNode(node);
46+
setShowTooltip(true);
47+
}}
48+
selectedNode={selectedNode}
49+
onNodeClick={(node) => {
50+
setSelectedNode(selectedNode === node ? undefined : node);
51+
}}
52+
/>
53+
<Tooltip visible={showTooltip} node={tooltipNode} root={root} sizeProperty={sizeProperty} />
54+
</>
55+
);
56+
};

src/flamegraph/color.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { scaleSequential, scaleLinear } from "d3-scale";
2+
import { hsl, RGBColor } from "d3-color";
3+
4+
import { HierarchyNode } from "d3-hierarchy";
5+
import { COLOR_BASE, CssColor } from "../color";
6+
import { ModuleTree, ModuleTreeLeaf } from "../../shared/types";
7+
8+
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
9+
const rc = 0.2126;
10+
const gc = 0.7152;
11+
const bc = 0.0722;
12+
// low-gamma adjust coefficient
13+
const lowc = 1 / 12.92;
14+
15+
function adjustGamma(p: number) {
16+
return Math.pow((p + 0.055) / 1.055, 2.4);
17+
}
18+
19+
function relativeLuminance(o: RGBColor) {
20+
const rsrgb = o.r / 255;
21+
const gsrgb = o.g / 255;
22+
const bsrgb = o.b / 255;
23+
24+
const r = rsrgb <= 0.03928 ? rsrgb * lowc : adjustGamma(rsrgb);
25+
const g = gsrgb <= 0.03928 ? gsrgb * lowc : adjustGamma(gsrgb);
26+
const b = bsrgb <= 0.03928 ? bsrgb * lowc : adjustGamma(bsrgb);
27+
28+
return r * rc + g * gc + b * bc;
29+
}
30+
31+
export interface NodeColor {
32+
backgroundColor: CssColor;
33+
fontColor: CssColor;
34+
}
35+
36+
export type NodeColorGetter = (node: HierarchyNode<ModuleTree | ModuleTreeLeaf>) => NodeColor;
37+
38+
const createRainbowColor = (root: HierarchyNode<ModuleTree | ModuleTreeLeaf>): NodeColorGetter => {
39+
const colorParentMap = new Map<HierarchyNode<ModuleTree | ModuleTreeLeaf>, CssColor>();
40+
colorParentMap.set(root, COLOR_BASE);
41+
42+
if (root.children != null) {
43+
const colorScale = scaleSequential([0, root.children.length], (n) => hsl(360 * n, 0.3, 0.85));
44+
root.children.forEach((c, id) => {
45+
colorParentMap.set(c, colorScale(id).toString());
46+
});
47+
}
48+
49+
const colorMap = new Map<HierarchyNode<ModuleTree | ModuleTreeLeaf>, NodeColor>();
50+
51+
const lightScale = scaleLinear().domain([0, root.height]).range([0.9, 0.3]);
52+
53+
const getBackgroundColor = (node: HierarchyNode<ModuleTree | ModuleTreeLeaf>) => {
54+
const parents = node.ancestors();
55+
const colorStr =
56+
parents.length === 1
57+
? colorParentMap.get(parents[0])
58+
: colorParentMap.get(parents[parents.length - 2]);
59+
60+
const hslColor = hsl(colorStr as string);
61+
hslColor.l = lightScale(node.depth);
62+
63+
return hslColor;
64+
};
65+
66+
return (node: HierarchyNode<ModuleTree | ModuleTreeLeaf>): NodeColor => {
67+
if (!colorMap.has(node)) {
68+
const backgroundColor = getBackgroundColor(node);
69+
const l = relativeLuminance(backgroundColor.rgb());
70+
const fontColor = l > 0.19 ? "#000" : "#fff";
71+
colorMap.set(node, {
72+
backgroundColor: backgroundColor.toString(),
73+
fontColor,
74+
});
75+
}
76+
77+
return colorMap.get(node) as NodeColor;
78+
};
79+
};
80+
81+
export default createRainbowColor;

src/flamegraph/const.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const TOP_PADDING = 20;
2+
export const PADDING = 2;

src/flamegraph/flamegraph.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { FunctionalComponent } from "preact";
2+
import { useContext } from "preact/hooks";
3+
import { HierarchyRectangularNode } from "d3-hierarchy";
4+
5+
import { ModuleTree, ModuleTreeLeaf } from "../../shared/types";
6+
import { Node } from "./node";
7+
import { StaticContext } from "./index";
8+
9+
export interface FlameGraphProps {
10+
root: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>;
11+
onNodeHover: (event: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>) => void;
12+
selectedNode: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf> | undefined;
13+
onNodeClick: (node: HierarchyRectangularNode<ModuleTree | ModuleTreeLeaf>) => void;
14+
}
15+
16+
export const FlameGraph: FunctionalComponent<FlameGraphProps> = ({
17+
root,
18+
onNodeHover,
19+
selectedNode,
20+
onNodeClick,
21+
}) => {
22+
const { width, height, getModuleIds } = useContext(StaticContext);
23+
24+
return (
25+
<svg xmlns="http://www.w3.org/2000/svg" viewBox={`0 0 ${width} ${height}`}>
26+
{root.descendants().map((node) => {
27+
return (
28+
<Node
29+
key={getModuleIds(node.data).nodeUid.id}
30+
node={node}
31+
onMouseOver={onNodeHover}
32+
selected={selectedNode === node}
33+
onClick={onNodeClick}
34+
/>
35+
);
36+
})}
37+
</svg>
38+
);
39+
};

src/flamegraph/index.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { createContext, render } from "preact";
2+
import { hierarchy, HierarchyNode, partition, PartitionLayout } from "d3-hierarchy";
3+
import {
4+
isModuleTree,
5+
ModuleLengths,
6+
ModuleTree,
7+
ModuleTreeLeaf,
8+
SizeKey,
9+
VisualizerData,
10+
} from "../../shared/types";
11+
12+
import { generateUniqueId, Id } from "../uid";
13+
import { getAvailableSizeOptions } from "../sizes";
14+
import { Main } from "./main";
15+
import createRainbowColor, { NodeColorGetter } from "./color";
16+
17+
import "../style/style-flamegraph.scss";
18+
import { PADDING } from "./const";
19+
20+
export interface StaticData {
21+
data: VisualizerData;
22+
availableSizeProperties: SizeKey[];
23+
width: number;
24+
height: number;
25+
}
26+
27+
export interface ModuleIds {
28+
nodeUid: Id;
29+
clipUid: Id;
30+
}
31+
32+
export interface ChartData {
33+
layout: PartitionLayout<ModuleTree | ModuleTreeLeaf>;
34+
rawHierarchy: HierarchyNode<ModuleTree | ModuleTreeLeaf>;
35+
getModuleSize: (node: ModuleTree | ModuleTreeLeaf, sizeKey: SizeKey) => number;
36+
getModuleIds: (node: ModuleTree | ModuleTreeLeaf) => ModuleIds;
37+
getModuleColor: NodeColorGetter;
38+
}
39+
40+
export type Context = StaticData & ChartData;
41+
42+
export const StaticContext = createContext<Context>({} as unknown as Context);
43+
44+
const drawChart = (
45+
parentNode: Element,
46+
data: VisualizerData,
47+
width: number,
48+
height: number,
49+
): void => {
50+
const availableSizeProperties = getAvailableSizeOptions(data.options);
51+
52+
console.time("layout create");
53+
54+
const layout = partition<ModuleTree | ModuleTreeLeaf>()
55+
.size([width, height])
56+
.padding(PADDING)
57+
.round(true);
58+
59+
console.timeEnd("layout create");
60+
61+
console.time("rawHierarchy create");
62+
const rawHierarchy = hierarchy<ModuleTree | ModuleTreeLeaf>(data.tree);
63+
console.timeEnd("rawHierarchy create");
64+
65+
const nodeSizesCache = new Map<ModuleTree | ModuleTreeLeaf, ModuleLengths>();
66+
67+
const nodeIdsCache = new Map<ModuleTree | ModuleTreeLeaf, ModuleIds>();
68+
69+
const getModuleSize = (node: ModuleTree | ModuleTreeLeaf, sizeKey: SizeKey) =>
70+
nodeSizesCache.get(node)?.[sizeKey] ?? 0;
71+
72+
console.time("rawHierarchy eachAfter cache");
73+
rawHierarchy.eachAfter((node) => {
74+
const nodeData = node.data;
75+
76+
nodeIdsCache.set(nodeData, {
77+
nodeUid: generateUniqueId("node"),
78+
clipUid: generateUniqueId("clip"),
79+
});
80+
81+
const sizes: ModuleLengths = { renderedLength: 0, gzipLength: 0, brotliLength: 0 };
82+
if (isModuleTree(nodeData)) {
83+
for (const sizeKey of availableSizeProperties) {
84+
sizes[sizeKey] = nodeData.children.reduce(
85+
(acc, child) => getModuleSize(child, sizeKey) + acc,
86+
0,
87+
);
88+
}
89+
} else {
90+
for (const sizeKey of availableSizeProperties) {
91+
sizes[sizeKey] = data.nodeParts[nodeData.uid][sizeKey] ?? 0;
92+
}
93+
}
94+
nodeSizesCache.set(nodeData, sizes);
95+
});
96+
console.timeEnd("rawHierarchy eachAfter cache");
97+
98+
const getModuleIds = (node: ModuleTree | ModuleTreeLeaf) => nodeIdsCache.get(node) as ModuleIds;
99+
100+
console.time("color");
101+
const getModuleColor = createRainbowColor(rawHierarchy);
102+
console.timeEnd("color");
103+
104+
render(
105+
<StaticContext.Provider
106+
value={{
107+
data,
108+
availableSizeProperties,
109+
width,
110+
height,
111+
getModuleSize,
112+
getModuleIds,
113+
getModuleColor,
114+
rawHierarchy,
115+
layout,
116+
}}
117+
>
118+
<Main />
119+
</StaticContext.Provider>,
120+
parentNode,
121+
);
122+
};
123+
124+
export default drawChart;

0 commit comments

Comments
 (0)