Skip to content

Commit

Permalink
Add df notebook
Browse files Browse the repository at this point in the history
  • Loading branch information
lwindolf committed Sep 6, 2023
1 parent 4900edf commit 1b503c7
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 0 deletions.
File renamed without changes.
File renamed without changes.
331 changes: 331 additions & 0 deletions frontend/notebooks/df.nb
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
# %% [markdown]
## Disk Usage Debugging
# %% [markdown]
### Directory Usage Overview

The following command lists directory sorted by size. You might want to modify the `-maxdepth 2` parameter for different details. **Note that this command can run for quite some time!**
# %%--- [shell]
# properties:
# bottom_hidden: true
# ---%%
cd /usr/share; (find . -maxdepth 3 -type d ! -name "." -print0 | xargs -0 -n1 du -sm | sort -nr ) 2>/dev/null
# %%--- [html]
# properties:
# run_on_load: true
# top_hidden: true
# ---%%
Visualize:
<button onclick="createRadialGraph(2 /* 3rd cell as input */, 'graph')">Radial Graph</button>
<button onclick="createTreeMap(2 /* 3rd cell as input */, 'graph')">Tree Map</button>
<div id='graph'/>
# %%--- [esm]
# properties:
# run_on_load: true
# ---%%
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

function createTreeMap(nr, id) {
const data = getData(nr);

// Specify the charts dimensions.
const width = 928;
const height = 924;

// This custom tiling function adapts the built-in binary tiling function
// for the appropriate aspect ratio when the treemap is zoomed-in.
function tile(node, x0, y0, x1, y1) {
d3.treemapBinary(node, 0, 0, width, height);
for (const child of node.children) {
child.x0 = x0 + child.x0 / width * (x1 - x0);
child.x1 = x0 + child.x1 / width * (x1 - x0);
child.y0 = y0 + child.y0 / height * (y1 - y0);
child.y1 = y0 + child.y1 / height * (y1 - y0);
}
}

// Compute the layout.
const hierarchy = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
const root = d3.treemap().tile(tile)(hierarchy);

// Create the scales.
const x = d3.scaleLinear().rangeRound([0, width]);
const y = d3.scaleLinear().rangeRound([0, height]);

// Formatting utilities.
const format = d3.format(",d");
const name = d => d.ancestors().reverse().map(d => d.data.name).join("/");

// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [0.5, -30.5, width, height + 30])
.attr("width", width)
.attr("height", height + 30)
.attr("style", "max-width: 100%; height: auto;")
.style("font", "10px sans-serif");

// Display the root.
let group = svg.append("g")
.call(render, root);

function render(group, root) {
const node = group
.selectAll("g")
.data(root.children.concat(root))
.join("g");

node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("click", (event, d) => d === root ? zoomout(root) : zoomin(d));

node.append("title")
.text(d => `${name(d)}\n${format(d.value)}`);

node.append("rect")
.attr("id", d => (d.leafUid = uid("leaf")).id)
.attr("fill", d => d === root ? "#fff" : d.children ? "#ccc" : "#ddd")
.attr("stroke", "#fff");

node.append("clipPath")
.attr("id", d => (d.clipUid = uid("clip")).id)
.append("use")
.attr("xlink:href", d => d.leafUid.href);

node.append("text")
.attr("clip-path", d => d.clipUid)
.attr("font-weight", d => d === root ? "bold" : null)
.selectAll("tspan")
.data(d => (d === root ? name(d) : d.data.name).split(/(?=[A-Z][^A-Z])/g).concat(format(d.value)))
.join("tspan")
.attr("x", 3)
.attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
.attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null)
.attr("font-weight", (d, i, nodes) => i === nodes.length - 1 ? "normal" : null)
.text(d => d);

group.call(position, root);
}

function position(group, root) {
group.selectAll("g")
.attr("transform", d => d === root ? `translate(0,-30)` : `translate(${x(d.x0)},${y(d.y0)})`)
.select("rect")
.attr("width", d => d === root ? width : x(d.x1) - x(d.x0))
.attr("height", d => d === root ? 30 : y(d.y1) - y(d.y0));
}

// When zooming in, draw the new nodes on top, and fade them in.
function zoomin(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.append("g").call(render, d);

x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);

svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.call(position, d.parent))
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d));
}

// When zooming out, draw the old nodes on top, and fade them out.
function zoomout(d) {
const group0 = group.attr("pointer-events", "none");
const group1 = group = svg.insert("g", "*").call(render, d.parent);

x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);

svg.transition()
.duration(750)
.call(t => group0.transition(t).remove()
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d))
.call(t => group1.transition(t)
.call(position, d.parent));
}

document.getElementById(id).replaceChildren(svg.node());
}

function createRadialGraph(nr, id) {
const data = getData(nr);

// Specify the charts dimensions.
const width = 928;
const height = width;
const radius = width / 6;

// Create the color scale.
const color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, data.children.length + 1));

// Compute the layout.
const hierarchy = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
const root = d3.partition()
.size([2 * Math.PI, hierarchy.height + 1])
(hierarchy);
root.each(d => d.current = d);

// Create the arc generator.
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))

// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, width])
.style("font", "10px sans-serif");

// Append the arcs.
const path = svg.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", d => { while (d.depth > 1) d = d.parent; return color(d.data.name); })
.attr("fill-opacity", d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.current) ? "auto" : "none")

.attr("d", d => arc(d.current));

// Make them clickable if they have children.
path.filter(d => d.children)
.style("cursor", "pointer")
.on("click", clicked);

const format = d3.format(",d");
path.append("title")
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join("/")}\n${format(d.value)}`);

const label = svg.append("g")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.selectAll("text")
.data(root.descendants().slice(1))
.join("text")
.attr("dy", "0.35em")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.text(d => d.data.name);

const parent = svg.append("circle")
.datum(root)
.attr("r", radius)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("click", clicked);

// Handle zoom on click.
function clicked(event, p) {
parent.datum(p.parent || root);

root.each(d => d.target = {
x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
y0: Math.max(0, d.y0 - p.depth),
y1: Math.max(0, d.y1 - p.depth)
});

const t = svg.transition().duration(750);

// Transition the data on all arcs, even the ones that arent visible,
// so that if this transition is interrupted, entering arcs will start
// the next transition from the desired position.
path.transition(t)
.tween("data", d => {
const i = d3.interpolate(d.current, d.target);
return t => d.current = i(t);
})
.filter(function(d) {
return +this.getAttribute("fill-opacity") || arcVisible(d.target);
})
.attr("fill-opacity", d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.target) ? "auto" : "none")

.attrTween("d", d => () => arc(d.current));

label.filter(function(d) {
return +this.getAttribute("fill-opacity") || labelVisible(d.target);
}).transition(t)
.attr("fill-opacity", d => +labelVisible(d.target))
.attrTween("transform", d => () => labelTransform(d.current));
}

function arcVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}

function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}

function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}

document.getElementById(id).replaceChildren(svg.node());
};

function uid(prefix) {
return { id: prefix + Date.now().toString(36) + Math.random().toString(36).substring(2) };
}

function getData(nr) {
// input data will be lines of "<path> <size in MB>"
let data = {name: ".", children: []};
let pathByName = {}; // lookup hash for insertion
getCellOutput(nr).split(/\n/).forEach((line) => {
function createParents(dir) {
let d, name;

if(pathByName[dir])
return pathByName[dir];

if(-1 == dir.indexOf('/')) {
d = data;
name = dir;
} else {
let m, parentDir;
[m, parentDir, name] = dir.match(/(.+)\/([^\/]+)/);
d = createParents(parentDir);
}

return pathByName[dir] = d.children[d.children.push({name: name, children: []}) - 1];
}

try {
let path = line.split(/\s+/, 2)[1];
if("" !== path) {
let d, name, m = path.match(/\.\/(.+)\/([^\/]+)/);
if(m) {
d = createParents(m[1]);
name = m[2];
} else {
d = data;
name = path.splice(2, path.length); // FIXME: broken
}

d.children.push({ name: name, value: line.split(/\s+/, 2)[0]});
}
} catch(e) {
console.error(`Invalid line: ${line} (${e})`);
};
});

return data;
}

window.createRadialGraph = createRadialGraph;
window.createTreeMap = createTreeMap;
File renamed without changes.

0 comments on commit 1b503c7

Please sign in to comment.