This is terribly janky, but I think demonstrates the value of a heatmap visualization.
BCGZCHJBLMJWKXOX3H6WROCXQ2OOHN6GYO2YIHOADIKHMSAMFKVAC <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cargo timings experiments</title><style>span {display: flex;text-align: center;}.crate {display: flex;flex-direction: column;flex-grow: 1;}.container {flex-grow: 1;margin: 5px;}.row,.column {display: flex;}.row {flex-direction: row;/* writing-mode: sideways-lr;text-orientation: upright; */}.column {flex-direction: column;/* writing-mode: vertical-lr;text-orientation: mixed; */}#heatmap {height: 30rem;width: 38rem;}</style></head><body><div id="heatmap" class="row"></div><script src="layout.js"></script></body></html>
const UNIT_DATA = [{"i": 0,"name": "proc-macro2","version": "1.0.70","mode": "todo","target": " build script","start": 0.07,"duration": 0.53,"rmeta_time": null,"unlocked_units": [24],"unlocked_rmeta_units": []},{"i": 1,"name": "unicode-ident","version": "1.0.12","mode": "todo","target": "","start": 0.07,"duration": 0.22,"rmeta_time": 0.1,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 2,"name": "serde","version": "1.0.193","mode": "todo","target": " build script","start": 0.07,"duration": 0.48,"rmeta_time": null,"unlocked_units": [23],"unlocked_rmeta_units": []},{"i": 3,"name": "utf8parse","version": "0.2.1","mode": "todo","target": "","start": 0.07,"duration": 0.43,"rmeta_time": 0.08,"unlocked_units": [],"unlocked_rmeta_units": [10]},{"i": 4,"name": "thiserror","version": "1.0.50","mode": "todo","target": " build script","start": 0.07,"duration": 0.45,"rmeta_time": null,"unlocked_units": [21],"unlocked_rmeta_units": []},{"i": 5,"name": "anstyle-query","version": "1.0.1","mode": "todo","target": "","start": 0.07,"duration": 0.16,"rmeta_time": 0.07,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 6,"name": "anstyle","version": "1.0.4","mode": "todo","target": "","start": 0.07,"duration": 0.66,"rmeta_time": 0.6,"unlocked_units": [],"unlocked_rmeta_units": [27]},{"i": 7,"name": "camino","version": "1.1.6","mode": "todo","target": " build script","start": 0.07,"duration": 0.54,"rmeta_time": null,"unlocked_units": [25],"unlocked_rmeta_units": []},{"i": 8,"name": "colorchoice","version": "1.0.0","mode": "todo","target": "","start": 0.1,"duration": 0.35,"rmeta_time": 0.1,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 9,"name": "semver","version": "1.0.20","mode": "todo","target": " build script","start": 0.13,"duration": 0.42,"rmeta_time": null,"unlocked_units": [22],"unlocked_rmeta_units": []},{"i": 10,"name": "anstyle-parse","version": "0.2.3","mode": "todo","target": "","start": 0.16,"duration": 0.51,"rmeta_time": 0.43,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 11,"name": "serde_json","version": "1.0.108","mode": "todo","target": " build script","start": 0.2,"duration": 0.32,"rmeta_time": null,"unlocked_units": [20],"unlocked_rmeta_units": []},{"i": 12,"name": "strsim","version": "0.10.0","mode": "todo","target": "","start": 0.24,"duration": 0.77,"rmeta_time": 0.5,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 13,"name": "itoa","version": "1.0.9","mode": "todo","target": "","start": 0.28,"duration": 0.31,"rmeta_time": 0.19,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 14,"name": "equivalent","version": "1.0.1","mode": "todo","target": "","start": 0.3,"duration": 0.31,"rmeta_time": 0.04,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 15,"name": "anyhow","version": "1.0.75","mode": "todo","target": " build script","start": 0.42,"duration": 0.47,"rmeta_time": null,"unlocked_units": [28],"unlocked_rmeta_units": []},{"i": 16,"name": "ryu","version": "1.0.15","mode": "todo","target": "","start": 0.45,"duration": 0.55,"rmeta_time": 0.43,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 17,"name": "clap_lex","version": "0.6.0","mode": "todo","target": "","start": 0.48,"duration": 0.44,"rmeta_time": 0.28,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 18,"name": "hashbrown","version": "0.14.3","mode": "todo","target": "","start": 0.48,"duration": 0.87,"rmeta_time": 0.81,"unlocked_units": [],"unlocked_rmeta_units": [32]},{"i": 19,"name": "fixedbitset","version": "0.4.2","mode": "todo","target": "","start": 0.5,"duration": 0.38,"rmeta_time": 0.3,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 20,"name": "serde_json","version": "1.0.108","mode": "run-custom-build","target": " build script (run)","start": 0.52,"duration": 0.01,"rmeta_time": null,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 21,"name": "thiserror","version": "1.0.50","mode": "run-custom-build","target": " build script (run)","start": 0.52,"duration": 0.14,"rmeta_time": null,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 22,"name": "semver","version": "1.0.20","mode": "run-custom-build","target": " build script (run)","start": 0.55,"duration": 0.02,"rmeta_time": null,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 23,"name": "serde","version": "1.0.193","mode": "run-custom-build","target": " build script (run)","start": 0.55,"duration": 0.03,"rmeta_time": null,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 24,"name": "proc-macro2","version": "1.0.70","mode": "run-custom-build","target": " build script (run)","start": 0.6,"duration": 0.02,"rmeta_time": null,"unlocked_units": [26],"unlocked_rmeta_units": []},{"i": 25,"name": "camino","version": "1.1.6","mode": "run-custom-build","target": " build script (run)","start": 0.61,"duration": 0.02,"rmeta_time": null,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 26,"name": "proc-macro2","version": "1.0.70","mode": "todo","target": "","start": 0.63,"duration": 0.8,"rmeta_time": 0.56,"unlocked_units": [],"unlocked_rmeta_units": [31]},{"i": 27,"name": "anstream","version": "0.6.4","mode": "todo","target": "","start": 0.68,"duration": 0.46,"rmeta_time": 0.39,"unlocked_units": [],"unlocked_rmeta_units": [29]},{"i": 28,"name": "anyhow","version": "1.0.75","mode": "run-custom-build","target": " build script (run)","start": 0.89,"duration": 0.19,"rmeta_time": null,"unlocked_units": [30],"unlocked_rmeta_units": []},{"i": 29,"name": "clap_builder","version": "4.4.11","mode": "todo","target": "","start": 1.07,"duration": 3.31,"rmeta_time": 2.41,"unlocked_units": [],"unlocked_rmeta_units": [35]},{"i": 30,"name": "anyhow","version": "1.0.75","mode": "todo","target": "","start": 1.08,"duration": 0.44,"rmeta_time": 0.31,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 31,"name": "quote","version": "1.0.33","mode": "todo","target": "","start": 1.2,"duration": 0.42,"rmeta_time": 0.27,"unlocked_units": [],"unlocked_rmeta_units": [33]},{"i": 32,"name": "indexmap","version": "2.1.0","mode": "todo","target": "","start": 1.3,"duration": 0.76,"rmeta_time": 0.64,"unlocked_units": [],"unlocked_rmeta_units": [34]},{"i": 33,"name": "syn","version": "2.0.39","mode": "todo","target": "","start": 1.48,"duration": 2.29,"rmeta_time": 1.75,"unlocked_units": [37,36],"unlocked_rmeta_units": []},{"i": 34,"name": "petgraph","version": "0.6.4","mode": "todo","target": "","start": 1.97,"duration": 1.1,"rmeta_time": 1.05,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 35,"name": "clap","version": "4.4.11","mode": "todo","target": "","start": 3.48,"duration": 0.08,"rmeta_time": 0.07,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 36,"name": "serde_derive","version": "1.0.193","mode": "todo","target": "","start": 3.77,"duration": 2.21,"rmeta_time": null,"unlocked_units": [39],"unlocked_rmeta_units": []},{"i": 37,"name": "thiserror-impl","version": "1.0.50","mode": "todo","target": "","start": 3.77,"duration": 1.11,"rmeta_time": null,"unlocked_units": [38],"unlocked_rmeta_units": []},{"i": 38,"name": "thiserror","version": "1.0.50","mode": "todo","target": "","start": 4.88,"duration": 0.04,"rmeta_time": 0.03,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 39,"name": "serde","version": "1.0.193","mode": "todo","target": "","start": 5.98,"duration": 1.52,"rmeta_time": 1.42,"unlocked_units": [],"unlocked_rmeta_units": [42,41,40,43]},{"i": 40,"name": "semver","version": "1.0.20","mode": "todo","target": "","start": 7.4,"duration": 0.3,"rmeta_time": 0.24,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 41,"name": "serde_json","version": "1.0.108","mode": "todo","target": "","start": 7.4,"duration": 0.82,"rmeta_time": 0.66,"unlocked_units": [],"unlocked_rmeta_units": [44]},{"i": 42,"name": "camino","version": "1.1.6","mode": "todo","target": "","start": 7.4,"duration": 0.43,"rmeta_time": 0.33,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 43,"name": "cargo-platform","version": "0.1.5","mode": "todo","target": "","start": 7.4,"duration": 0.26,"rmeta_time": 0.17,"unlocked_units": [],"unlocked_rmeta_units": []},{"i": 44,"name": "cargo_metadata","version": "0.18.1","mode": "todo","target": "","start": 8.06,"duration": 0.88,"rmeta_time": 0.59,"unlocked_units": [45],"unlocked_rmeta_units": []},{"i": 45,"name": "cargo-depgraph","version": "1.6.0","mode": "todo","target": " bin \"cargo-depgraph\"","start": 8.94,"duration": 0.61,"rmeta_time": null,"unlocked_units": [],"unlocked_rmeta_units": []}];const EDGES = {0: [1, 2, 3, 4], 2: [5, 6, 7, 8, 9, 10], 3: [11], 4: [12, 13], 6: [8], 9: [14, 15, 8], 11: [17, 18], 13: [20, 21]};const NODES = ["cargo-depgraph","anyhow","cargo_metadata","clap","petgraph","camino","cargo-platform","semver","serde","serde_json","thiserror","clap_builder","fixedbitset","indexmap","itoa","ryu","anstream","anstyle","clap_lex","strsim","equivalent","hashbrown","anstyle-parse","anstyle-query","colorchoice","utf8parse"];class Crate {constructor(name) {this.name = name;for (const [index, node] of NODES.entries()) {if (node == name) {this.node_index = index;break;}}console.assert(this.node_index !== undefined);let unit_indexes = [];for (const [index, element] of UNIT_DATA.entries()) {if (element.name === name) {unit_indexes.push(index);}}let dependencies = [];if (EDGES[this.node_index] !== undefined) {for (const dependency of EDGES[this.node_index]) {dependencies.push(new Crate(NODES[dependency]));}}let total_unlocked = 0;for (const index of unit_indexes) {total_unlocked += UNIT_DATA[index].unlocked_units.length;total_unlocked += UNIT_DATA[index].unlocked_rmeta_units.length;}this.total_unlocked = total_unlocked;this.unit_indexes = unit_indexes;this.dependencies = dependencies;}sum_duration() {let total = 0;for (const index of this.unit_indexes) {total += UNIT_DATA[index].duration;}return total;}total_sum_duration() {let total = this.sum_duration();for (const child of this.dependencies) {total += child.total_sum_duration();}return total;}}let max_unlocked = 0;for (const node of NODES) {const crate = new Crate(node);max_unlocked = Math.max(max_unlocked, crate.total_unlocked)}function max_sum(items) {let largest = 0;for (const item of items) {largest = Math.max(largest, item.total_sum_duration());}console.assert(largest > 0);return largest;}function largest_in(items) {let largest = [undefined, 0];for (const [index, item] of items.entries()) {total_duration = item.total_sum_duration();if (total_duration > largest[1]) {largest = [index, total_duration];}}console.assert(largest[0] !== undefined);return largest[0];}function running_sum(items) {let sum = 0;for (const item of items) {sum += item.total_sum_duration();}return sum;}// From https://stackoverflow.com/questions/12875486/what-is-the-algorithm-to-create-colors-for-a-heatmapfunction heatMapColorforValue(value){var h = (1.0 - value) * 60return "hsl(" + h + ", 100%, 50%)";}function recurse(list, parent) {for (const item of list) {const child = document.createElement("div");child.classList.add(item.name);child.classList.add("crate");const container = document.createElement("div");container.classList.add("container")const text = document.createElement("span");text.innerHTML = item.name;child.appendChild(text);if (parent.classList.contains("column")) {container.classList.add("row");} else {container.classList.add("column");}// container.style.flexGrow = item.total_sum_duration();child.style.backgroundColor = heatMapColorforValue(item.total_unlocked / max_unlocked);child.appendChild(container);parent.appendChild(child);if (item.dependencies.length > 0) {split(item.dependencies, container);} else {// not sure yet}}}// Algorithm from "Treemapping via balanced partitioning" paper (algorithm 2: variance-minimizing solution for balanced partition)function split(list, parent) {let height = running_sum(list) / 2;let l_1 = [];let l_2 = list.map((item) => new Crate(item.name));let x = largest_in(l_2);while (Math.abs(running_sum(l_1) - height) > Math.abs(running_sum(l_1.concat([l_2[x]])) - height)) {l_1.push(l_2[x]);l_2.splice(x, 1);x = largest_in(l_2);}console.log(l_1, l_2);recurse(l_1, parent);recurse(l_2, parent);}const heatmap = document.getElementById("heatmap");// const ctx = heatmap.getContext("2d");// for (const item of UNIT_DATA) {// ctx.beginPath();// ctx.arc(item.duration * 100, (item.unlocked_units,length + item.unlocked_rmeta_units.length) * 100, 5, 0, 2 * Math.PI);// ctx.stroke();// }const root = new Crate("cargo-depgraph");split(root.dependencies, heatmap)
.git.DS_Store