Progress on treemap

This commit is contained in:
Tom McLaughlin 2024-02-03 03:24:53 -08:00
parent a6319c05e2
commit 34e9ddccaf
6 changed files with 556 additions and 498 deletions

810
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,20 +20,30 @@
},
"homepage": "https://github.com/codedownio/time-ghc-modules#readme",
"devDependencies": {
"@types/d3-hierarchy": "^3.1.6",
"@types/d3-interpolate": "^3.0.4",
"@types/d3-scale": "^4.0.8",
"@types/d3-scale-chromatic": "^3.0.3",
"@types/d3-selection": "^3.0.10",
"@types/lodash": "^4.14.171",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"process": "^0.11.10",
"rimraf": "^3.0.2"
},
"dependencies": {
"@material-ui/core": "^4.12.3",
"apexcharts": "^3.27.3",
"d3-hierarchy": "^3.1.2",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.0.0",
"d3-selection": "^3.0.0",
"lodash": "^4.17.21",
"parcel": "^2.11.0",
"react": "^17.0.2",
"react-apexcharts": "^1.3.9",
"react-dom": "^17.0.2",
"react-vis": "^1.12.1",
"tachyons": "^4.12.0",
"typescript": "^4.3.5"
},

155
src/D3TreeMap.tsx Normal file
View File

@ -0,0 +1,155 @@
import { type TreemapLayout, hierarchy, treemap, treemapBinary } from "d3-hierarchy";
import { interpolateRgb } from "d3-interpolate";
import { scaleOrdinal } from "d3-scale";
import { schemeCategory10 } from "d3-scale-chromatic";
import { select } from "d3-selection";
import { isEqual, keys } from "lodash";
import { PureComponent } from "react";
interface ITreeMapProps<D> {
data: Tree<D>;
width: number;
height: number;
labelFn: (d: D) => string;
subLabelFn: (d: D) => string;
valueFn: (d: D) => number;
}
interface ITreeMapState<D> {
cell: any;
treemap: TreemapLayout<D>;
}
export default class TreeMap<D> extends PureComponent<ITreeMapProps<D>, ITreeMapState<D>> {
svg: SVGSVGElement;
constructor(props: ITreeMapProps<D>) {
super(props);
this.state = {
cell: null,
treemap: null,
};
}
componentDidMount() {
const width = this.props.width;
const height = this.props.height;
select(this.svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${Math.min(width, height)} ${Math.min(width, height)}`)
.attr("preserveAspectRatio", "xMinYMin");
const tm = treemap<D>()
.tile(treemapBinary) // treemapSquarify, treemapSliceDice, treemapSlice, treemapDice
.size([width, height])
.round(true)
.paddingInner(1);
this.setState({ ...this.state, treemap: tm });
this.refresh(this.svg, tm);
}
componentDidUpdate(prevProps: ITreeMapProps<D>, _prevState: ITreeMapState<D>) {
if (prevProps.data !== this.props.data) {
this.refresh(this.svg, this.state.treemap, isEqual(keys(prevProps.data), keys(this.props.data)));
}
}
refresh(svg: SVGSVGElement, treemap: TreemapLayout<D>, sameNodes = true) {
if (!this.state.cell || !sameNodes) {
select(svg).selectAll("*").remove();
const cell = this.drawData(svg, treemap, this.props.data);
this.setState({ ...this.state, cell });
} else {
const root = hierarchy(this.props.data)
.eachBefore((d) => (d.data.id = (d.parent ? d.parent.name + "." : "") + d.name))
.sum((d) => this.props.valueFn(d))
.sort((a, b) => b.height - a.height || b.value - a.value);
treemap(root);
this.state.cell.data(root.leaves());
this.state.cell
.transition()
.duration(750)
.attr("transform", (d) => "translate(" + d.x0 + "," + d.y0 + ")")
.select("rect")
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0);
}
}
drawData(svg: SVGSVGElement, treemap, data) {
const fader = (color) => interpolateRgb(color, "#fff")(0.2);
const color = scaleOrdinal(schemeCategory10.map(fader));
const root = hierarchy(data)
.eachBefore((d) => (d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name))
.sum((d) => this.props.valueFn(d))
.sort((a, b) => b.height - a.height || b.value - a.value);
treemap(root);
console.log("root", root);
const cell = select(svg)
.selectAll("g")
.data(root.leaves())
.enter()
.append("g")
.attr("transform", (d: any) => "translate(" + d.x0 + "," + d.y0 + ")");
// Add labels
cell
.append("rect")
.attr("id", (d) => d.data.id)
.attr("width", (d: any) => d.x1 - d.x0)
.attr("height", (d: any) => d.y1 - d.y0)
.attr("fill", (d) => color(this.props.labelFn(d.data)));
// Add clip paths to ensure text doesn't overflow
cell
.append("clipPath")
.attr("id", (d) => "clip-" + d.data.id)
.append("use")
.attr("xlink:href", (d) => "#" + d.data.id);
// Append multiline text. The last line shows the value and has a specific formatting.
cell
.append("text")
.attr("clip-path", (d) => "url(#clip-" + d.data.id + ")")
.filter((d) => {
console.log("d: ", d);
return true;
})
.selectAll("tspan")
.data((d) => [this.props.labelFn(d.data) + " | " + this.props.subLabelFn(d.data)])
.enter()
.append("tspan")
.attr("x", 4)
.attr("y", (d, i) => 10 + i * 10)
.text((d: string) => d)
.style("font-size", "8px");
cell.append("title")
.text((d) => this.props.labelFn(d.data) + ` (${this.props.subLabelFn(d.data)})`);
return cell;
}
render() {
return (
<div className="tree-map">
<svg ref={(c) => (this.svg = c)}></svg>
</div>
);
}
}

View File

@ -1,9 +1,8 @@
import {size} from "lodash";
import {forEach, size} from "lodash";
import {useMemo} from "react";
import {Treemap as RVTreeMap} from "react-vis";
import * as styles from "react-vis/dist/style.css";
import D3TreeMap from "./D3TreeMap";
// import {formatBytes, formatTime} from "./Util";
@ -30,8 +29,9 @@ export default function TreeMap({aggregate, data}: Props) {
let current = ret;
let soFar = "";
const parts = module.module.split(".");
for (const part of parts) {
soFar += "." + part;
for (let i = 0; i < parts.length; i += 1) {
const part = parts[i];
soFar += (i === 0 ? part : "." + part);
current.children[part] = current.children[part] || {
title: soFar,
value: 0,
@ -41,53 +41,70 @@ export default function TreeMap({aggregate, data}: Props) {
current.value += (aggregate === "time" ? module["time"]: module["alloc"]);
}
}
forEach(ret.children, (x) => {
ret.value += x.value;
});
console.log("Built nestedData", ret);
return ret;
}, [modulesList]);
const myData = {
"title": "analytics",
const myData: Tree<TreeNode> = {
"name": "analytics",
"color": "#12939A",
"size": 48716,
"children": [
{
"title": "cluster",
"name": "cluster",
"color": "#12939A",
"size": 15207,
"children": [
{"title": "AgglomerativeCluster", "color": "#12939A", "size": 3938},
{"title": "CommunityStructure", "color": "#12939A", "size": 3812},
{"title": "HierarchicalCluster", "color": "#12939A", "size": 6714},
{"title": "MergeEdge", "color": "#12939A", "size": 743}
{"name": "AgglomerativeCluster", "color": "#12939A", "size": 3938},
{"name": "CommunityStructure", "color": "#12939A", "size": 3812},
{"name": "HierarchicalCluster", "color": "#12939A", "size": 6714},
{"name": "MergeEdge", "color": "#12939A", "size": 743}
]
},
{
"title": "graph",
"name": "graph",
"color": "#12939A",
"size": 26435,
"children": [
{"title": "BetweennessCentrality", "color": "#12939A", "size": 3534},
{"title": "LinkDistance", "color": "#12939A", "size": 5731},
{"title": "MaxFlowMinCut", "color": "#12939A", "size": 7840},
{"title": "ShortestPaths", "color": "#12939A", "size": 5914},
{"title": "SpanningTree", "color": "#12939A", "size": 3416}
{"name": "BetweennessCentrality", "color": "#12939A", "size": 3534},
{"name": "LinkDistance", "color": "#12939A", "size": 5731},
{"name": "MaxFlowMinCut", "color": "#12939A", "size": 7840},
{"name": "ShortestPaths", "color": "#12939A", "size": 5914},
{"name": "SpanningTree", "color": "#12939A", "size": 3416}
]
},
{
"title": "optimization",
"name": "optimization",
"color": "#12939A",
"size": 7074,
"children": [
{"title": "AspectRatioBanker", "color": "#12939A", "size": 7074}
{"name": "AspectRatioBanker", "color": "#12939A", "size": 7074}
]
}
]
}
};
// Modes: squarify, resquarify, slice, dice, slicedice, binary, circlePack, partition, partition-pivot
return (
<RVTreeMap
title={'My New Treemap'}
<D3TreeMap
width={500}
height={500}
data={myData}
mode="squarify"
labelFn={(x) => {
return x.name;
}}
subLabelFn={(x) => {
return x.size + " UNITS";
}}
valueFn={(x) => {
return x.size;
}}
/>
);
}

11
src/types.d.ts vendored
View File

@ -17,3 +17,14 @@ interface ModuleData {
modules: Array<{module: string;}>;
data: Array<Event>;
}
type Tree<D> = D & {
children?: Tree<D>[];
id?: string;
}
interface TreeNode {
name: string;
color: string;
size: number;
}

View File

@ -1,5 +1,6 @@
{
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"moduleResolution": "node",
"noImplicitAny": false,
"removeComments": true,