Skip to content

Instantly share code, notes, and snippets.

@CyberShadow
Last active September 13, 2021 19:08
Show Gist options
  • Select an option

  • Save CyberShadow/6b6ecfde854ec7d991f8774bc35bbce5 to your computer and use it in GitHub Desktop.

Select an option

Save CyberShadow/6b6ecfde854ec7d991f8774bc35bbce5 to your computer and use it in GitHub Desktop.
Illustrations for btdu
/draw
/illustrations.svg
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
import std.algorithm.comparison;
import std.algorithm.iteration;
import std.algorithm.searching;
import std.array;
import std.conv;
import std.format;
import std.stdio : toFile;
import std.traits : EnumMembers;
import ae.utils.meta : enumLength;
import ae.utils.text : doubleToString;
import ae.utils.xmlbuild;
void main()
{
struct Illustration
{
string name;
struct File
{
string name;
int start, end;
}
File[] files;
int size() const { return files.map!(f => f.end).reduce!max; }
}
static immutable Illustration[] illustrations = [
{
"single",
[
{ "a", 0, 1 },
],
},
{
"clone",
[
{ "a", 0, 1 },
{ "b", 0, 1 },
],
},
{
"overlap",
[
{ "a", 0, 2 },
{ "b", 1, 3 },
],
},
];
enum maxDiskSize = illustrations.map!(i => i.size).reduce!max;
enum maxFileCount = illustrations.map!(i => i.files.length).reduce!max;
enum SampleType { representative, exclusive, shared_, distributed }
enum margin = 5;
enum spaceUnitSize = 50;
enum infoColumnWidth = 50;
enum numInfoColumns = 1 + enumLength!SampleType;
enum headerHeight = 50; // Table headers, space ruler
enum footerHeight = 50; // Totals
enum diskMargin = 25;
enum fileMargin = 4;
enum fileHeader = 40;
enum fileFooter = 40;
enum fileDataHeight = 1 * spaceUnitSize;
enum fileHeight = fileHeader + fileDataHeight + fileFooter;
foreach (ref illustration; illustrations)
{
auto svg = newXml().svg();
svg.xmlns = "http://www.w3.org/2000/svg";
svg["version"] = "1.1";
// svg.width = text(
// margin +
// diskMargin +
// spaceUnitSize * maxDiskSize +
// diskMargin +
// infoColumnWidth * 4 +
// margin
// );
svg.style = "font-family: Arial, sans-serif";
auto defs = svg.defs();
defs.style()[] = q"EOF
svg {
--bg: white;
--fg: black;
color-scheme: light dark;
}
@media (prefers-color-scheme:dark) {
svg {
--bg: #0d1117;
--fg: #aaa;
}
}
svg {
background-color: var(--bg);
}
EOF";
auto tableHeight =
headerHeight +
illustration.files.length * fileHeight +
footerHeight;
auto g = svg.g();
g.transform = format!"translate(%d %d)"(
0 + margin,
0 + margin,
);
auto diskColumnWidth =
diskMargin +
spaceUnitSize * illustration.size +
diskMargin;
auto tableWidth =
diskColumnWidth +
infoColumnWidth * numInfoColumns;
string tableLines = "M 0 0 v %1$d h %2$d v -%1$d z".format(tableHeight, tableWidth);
{
int x;
void col(string title, int width, int size)
{
auto lines = title.split("\n");
auto lineHeight = size;
auto y = (headerHeight - (lines.length - 1) * lineHeight) / 2 + (size / 3);
foreach (line; lines)
{
g.text([
"x" : text(x + width / 2),
"y" : text(y),
"font-size" : text(size) ~ "px",
"font-weight" : "bold",
"text-anchor" : "middle",
"fill" : "var(--fg)",
])[] = line;
y += lineHeight;
}
x += width;
tableLines ~= " M %d 0 v %d".format(x, tableHeight - (x == width ? footerHeight : 0));
}
col("Disk layout\n\n" , diskColumnWidth, headerHeight / 4);
col("File" , infoColumnWidth, headerHeight / 4);
col("Repr.\nsize" , infoColumnWidth, headerHeight / 4);
col("Dist.\nsize" , infoColumnWidth, headerHeight / 4);
col("Excl.\nsize" , infoColumnWidth, headerHeight / 4);
col("Shared\nsize", infoColumnWidth, headerHeight / 4);
}
string formatSize(double o)
{
if (o == 0) return "0";
return doubleToString(o) ~ "M";
}
// Disk ruler
{
auto ruler = g.g();
ruler.transform = "translate(%d, %d)".format(diskMargin, headerHeight * 7 / 8);
string path = "M 0 0 h %d".format(illustration.size * spaceUnitSize);
enum rulerBip = spaceUnitSize / 10;
foreach (o; 0 .. illustration.size + 1)
{
path ~= " M %d 0 v -%d".format(o * spaceUnitSize, rulerBip);
ruler.text([
"x" : text(o * spaceUnitSize),
"y" : text(-rulerBip * 3 / 2),
"font-size" : text(spaceUnitSize / 4) ~ "px",
"text-anchor" : "middle",
"font-family" : "monospace",
"fill" : "#888",
])[] = formatSize(o);
}
ruler.path([
"d" : path,
"stroke" : "#888",
"stroke-width" : "2px",
"fill" : "none",
]);
}
// Dimensions:
// - File index
// - Sample type
// - Offset of 1M block within file
// - Offset within 1M block (# of segments == # of files)
auto allLayers = new bool[maxFileCount][maxDiskSize][enumLength!SampleType][illustration.files.length];
auto ty = headerHeight;
tableLines ~= " M 0 %d h %d".format(ty, tableWidth);
foreach (fileIndex, ref file; illustration.files)
{
auto fileLayers = allLayers[fileIndex][];
foreach (o; file.start .. file.end)
{
// Representative - only if we are the first file using it
fileLayers[SampleType.representative][o][0 .. illustration.files.length] = true;
foreach (otherFile; illustration.files[0 .. fileIndex])
if (o >= otherFile.start && o < otherFile.end)
fileLayers[SampleType.representative][o] = false;
// Exclusive - only if no other file is using it
fileLayers[SampleType.exclusive][o][0 .. illustration.files.length] = true;
foreach (otherFileIndex, otherFile; illustration.files)
if (otherFileIndex != fileIndex && o >= otherFile.start && o < otherFile.end)
fileLayers[SampleType.exclusive][o] = false;
// Shared - always counted
fileLayers[SampleType.shared_][o][0 .. illustration.files.length] = true;
// Distributed - only this file's share is counted
fileLayers[SampleType.distributed][o][0 .. illustration.files.length] = true;
foreach (otherFileIndex, otherFile; illustration.files)
if (otherFileIndex != fileIndex && o >= otherFile.start && o < otherFile.end)
fileLayers[SampleType.distributed][o][otherFileIndex] = false;
}
g.rect([
"x" : text(diskMargin + file.start * spaceUnitSize),
"y" : text(ty + fileHeader),
"width" : text((file.end - file.start) * spaceUnitSize),
"height" : text(fileDataHeight ),
"fill" : "none",
"stroke" : "var(--fg)",
"stroke-width" : "4px",
]);
foreach (o; file.start .. file.end)
{
g.rect([
"x" : text(diskMargin + o * spaceUnitSize + fileMargin),
"y" : text(ty + fileHeader + fileMargin),
"width" : text(spaceUnitSize - fileMargin * 2),
"height" : text(fileDataHeight - fileMargin * 2),
"fill" : "var(--fg)",
]);
foreach (row; 0 .. 2)
{
g.text([
"x" : text(diskMargin + o * spaceUnitSize + spaceUnitSize / 2),
"y" : text(ty + fileHeader + fileMargin + spaceUnitSize * 3 / 8 + spaceUnitSize * 3 / 8 * row),
"font-size" : text(spaceUnitSize / 3) ~ "px",
"font-weight" : "bold",
"text-anchor" : "middle",
"font-family" : "monospace",
"fill" : "var(--bg)",
])[] = "%1$d%1$d%1$d%1$d".format(1 + o);
}
}
int x;
void col(string title, int width, int size, string color = "var(--fg)")
{
auto y = ty + (fileHeight / 2) + (size / 3);
g.text([
"x" : text(x + width / 2),
"y" : text(y),
"font-size" : text(size) ~ "px",
"font-weight" : "bold",
"text-anchor" : "middle",
"fill" : color,
])[] = title;
x += width;
}
col(null , diskColumnWidth, infoColumnWidth / 2);
col(file.name , infoColumnWidth, infoColumnWidth / 3 * 2);
void doType(SampleType type, string color, int y1, int dy)
{
string typePath;
auto o = 0;
foreach (gr; fileLayers[type][]
.map!((ref chunk) => chunk[0 .. illustration.files.length])
.joiner
.group())
{
auto start = o;
auto end = start + gr[1];
o = end;
if (!gr[0])
continue;
typePath ~= " M %d %d".format(diskMargin + start * spaceUnitSize / illustration.files.length, ty + y1);
typePath ~= " v -%1$s l %1$s %1$s l -%1$s %1$s v -%1$s".format(spaceUnitSize / 100.0);
typePath ~= " h %d".format((end - start) * spaceUnitSize / illustration.files.length);
typePath ~= " v -%1$s l -%1$s %1$s l %1$s %1$s v -%1$s".format(spaceUnitSize / 100.0);
typePath ~= " M %d %d v %d H %d V %d".format(
diskMargin + (start + end) * spaceUnitSize / illustration.files.length / 2, ty + y1,
fileHeight * 4 / 64 * dy,
x + infoColumnWidth / 2,
ty + fileHeight / 2 + (fileHeight * 5 / 32 * dy),
);
enum arrowSize = infoColumnWidth / 8;
typePath ~= " m 0 0 l -%d %d h %d l -%d %d z".format(
arrowSize, arrowSize * +dy,
arrowSize * 2,
arrowSize, arrowSize * -dy,
);
}
g.path([
"d" : typePath,
"stroke" : color,
"stroke-width" : "4px",
"fill" : "none",
]);
auto size = fileLayers[type][].map!(fileBits => fileBits[].count!(b => b)).sum / double(illustration.files.length);
col(formatSize(size), infoColumnWidth, infoColumnWidth * 2 / 5, color);
}
doType(SampleType.representative, "#44f", fileHeight * 8 / 32, -1);
doType(SampleType.distributed , "#777", fileHeight * 4 / 32, -1);
doType(SampleType.exclusive , "#f00", fileHeight * 24 / 32, 1);
doType(SampleType.shared_ , "#0c0", fileHeight * 28 / 32, 1);
ty += fileHeight;
tableLines ~= " M 0 %d h %d".format(ty, tableWidth);
}
// Total
{
int x;
void col(string title, int width, int size, string color = "var(--fg)")
{
auto y = ty + (footerHeight / 2) + (size / 3);
g.text([
"x" : text(x + width / 2),
"y" : text(y),
"font-size" : text(size) ~ "px",
"font-weight" : "bold",
"text-anchor" : "middle",
"fill" : color,
])[] = title;
x += width;
tableLines ~= " M %d 0 v %d".format(x, tableHeight);
}
col("Total" , diskColumnWidth + infoColumnWidth, infoColumnWidth / 2);
void doType(SampleType type, string color)
{
auto size = allLayers.map!(fileLayers => fileLayers[type][].map!(fileBits => fileBits[].count!(b => b)).sum / double(illustration.files.length)).sum;
col(formatSize(size), infoColumnWidth, infoColumnWidth / 2, color);
}
doType(SampleType.representative, "#44f");
doType(SampleType.distributed , "#888");
doType(SampleType.exclusive , "#f00");
doType(SampleType.shared_ , "#0c0");
}
g.path([
"d" : tableLines,
"stroke" : "var(--fg)",
"stroke-width" : "4px",
"fill" : "none",
]);
svg.width = text(
margin +
tableWidth +
margin
);
svg.height = text(
margin +
tableHeight +
margin
);
svg.toPrettyString().toFile(illustration.name ~ ".svg");
}
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment