You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

363 lines
9.7 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Create some global variables
var width, height, audioEl, textEl, sampEl;
var mouseOverId;
var tooltip;
let currentTransform;
var links = [],
nodes = [],
groups = [];
let zoomLvl = 1;
let lastK = 0;
let svg;
var zoom;
let simulation;
const ROOT = "https://hub.xpub.nl/chopchop/archive_non-tree/Active-Archive";
function getNameFromPath(path) {
return path.split("/")[path.split("/").length - 1];
}
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function parseExifData(files) {
// loop trough all files in JSON and add each to the list of nodes
files.forEach((file) => {
file.id = file.SourceFile;
nodes.push(file);
// also include any mentioned directories in the list of groups and the list of nodes.
var paths = file.Directory.split("/");
// the directory is split up and looped trough, so for every subdirectory, a node is created
paths.forEach((p, index) => {
var path = "";
for (var i = 0; i < index + 1; i++) {
path += (i === 0 ? "" : "/") + paths[i];
}
// we check if the directory is not already stored
if (!groups.includes(path)) {
groups.push(path);
nodes.push({
id: path,
FileType: "directory",
FileName: getNameFromPath(path),
});
}
});
});
// Now that the groups are created, they need to be linked together by adding them to the array of links.
groups.forEach((group) => {
var path = group.split("/");
// link a folder to its parent when it exists
if (path.length > 1) {
var currentName = "/" + path[path.length - 1];
var parentName = group.replace(currentName, "");
var parent = groups.find((group) => {
return group === parentName;
});
// if there is a parent, create a link between the parent and the group.
if (parent) {
links.push({
source: group,
target: parentName,
});
}
}
// now, find any files of the folder and
var children = files.filter((cFile) => {
return cFile.Directory === group;
});
// for each child, add a link between the directory and the file.
children.forEach((sFile) => {
links.push({
source: group,
target: sFile.SourceFile,
});
});
});
}
function createGraph() {
// Create a scale to set the radius based on a file size (0 to 3 GB)
var max = d3.max(nodes, function (d) {
return +d.FileSize;
});
console.log(max);
var rScale = d3.scaleLinear().domain([3, max]).range([2, 40]);
currentTransform = [width / 2, height / 2, height];
simulation = d3
.forceSimulation(nodes)
.force("center", d3.forceCenter(width / 2, height / 2))
.force(
"collision",
d3.forceCollide().radius((d) => rScale(d.FileSize) + 2)
)
.force(
"link",
d3
.forceLink(links)
.id((d) => d.id)
.strength((d) => 0.82)
.distance((d) => (d.source.FileType === "directory" ? 80 : 20))
)
// .force("charge", d3.forceManyBody().strength(-1))
.force("charge", d3.forceManyBody());
// .alphaTarget(0.3); // stay hot
svg = d3
.create("svg") //
.attr("width", width)
.attr("height", height);
const g = svg.append("g");
tooltip = d3.select("body").append("div").attr("class", "tooltip");
const link = g
.append("g") //
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.join("text")
.text("aaa");
const globalNode = g.append("g");
const node = globalNode
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", (d) => rScale(d.FileSize))
.attr("stroke", "black")
.attr("fill", (d) => {
if (d.FileType === "directory") {
return "orange";
} else if (d.FileType === "TXT") {
return "blue";
} else if (d.FileType === "PNG") {
return "pink";
} else {
return "white";
}
})
.attr("label", (d) => d.id)
.attr("path", (d) => d.id)
.attr("file-type", (d) => d.FileType)
.attr("group", (d) => d.group)
.on("mouseenter", function (e, d) {
// if (d3.select(this).attr("fill") !== "black") {
// d3.select(this).attr("fill", "grey");
// }
tooltip.style("display", "block").html(tooltipContents(d));
})
.on("click", function () {
// d3.selectAll("circle").attr("fill", "rgba(255,255,255,.5)");
// d3.select(this).attr("fill", "black");
var path = this.getAttribute("path");
svg
.transition() //
.duration(300)
.ease(d3.easeCubic)
.call(zoom.translateTo, this.getAttribute("cx"), this.getAttribute("cy"));
// do things based on the file type
if (path.match(/\.(?:wav|mp3|flac)$/i)) {
playAudio(path);
}
if (path.match(/\.(?:txt|png)$/i)) {
playText(path);
}
})
.on("mouseleave", function () {
if (d3.select(this).attr("fill") !== "black") {
d3.select(this).attr("fill", "rgba(255,255,255,.5)");
}
tooltip.style("display", "none");
})
.on("mousemove", function (event, d) {
if (!event) return;
tooltip.style("transform", `translate(${event.clientX}px, ${event.clientY}px)`);
});
const labels = g
.append("g")
.selectAll("text")
.data(nodes)
.join("text")
.attr("font-size", (d) => (d.FileType === "directory" ? 15 : 3))
.text((d) => d.FileName.replaceAll("-", " "))
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.call(wrap, 50);
// Add a drag behavior.
node.call(
d3
.drag() //
.on("start", onDragStart)
.on("drag", onDrag)
.on("end", onDragEnd)
);
// On tick, animate
simulation.on("tick", () => {
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
labels.attr("x", (d) => d.x).attr("y", (d) => d.y);
// tooltip.attr("x", (d) => d.x).attr("y", (d) => d.y);
});
// add zoom behavior
var zoom = d3
.zoom()
.extent([
[0, 0],
[width, height],
])
.scaleExtent([1, 100])
.on("zoom", onZoom);
svg.call(zoom);
// add some mouse behaviors
container.append(svg.node());
// Reheat the simulation when drag starts, and fix the subject position.
function onDragStart(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
// Update the subject (onDrag node) position during drag.
function onDrag(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that its no longer being onDrag.
function onDragEnd(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
function onZoom(e) {
if (e.transform.k > 2 && lastK != e.transform.k) {
lastK = e.transform.k;
zoomLvl = Math.log2(e.transform.k);
globalNode.attr("stroke-width", 1 / zoomLvl);
link.attr("stroke-width", 1 / zoomLvl);
labels.attr("font-size", (d) => (d.FileType === "directory" ? 20 : 3) / zoomLvl);
tooltip.attr("font-size", 3 / zoomLvl);
}
g.attr("transform", e.transform);
}
}
function tooltipContents(d) {
var string = "<ul>";
for (var prop in d) {
string += `<li><b>${prop}</b>: ${d[prop]}</li>`;
}
string += "</ul>";
return string;
}
function playAudio(src) {
audioEl.src = ROOT + "/" + src;
audioEl.currentTime = 0;
audioEl.play();
}
function playText(src) {
textEl.setAttribute("active", "true");
textEl.querySelector(".fn-title").textContent = src.split("/")[src.split("/").length - 1];
textEl.querySelector("iframe").src = ROOT + "/" + src;
window.setTimeout(function () {
svg.on("click", onBodyClick);
}, 300);
}
function onBodyClick() {
textEl.setAttribute("active", "false");
svg.on("click", null);
}
function updateWindow() {
width = window.innerWidth - 10;
height = window.innerHeight - 10;
simulation.force("center", d3.forceCenter(width / 2, height / 2));
svg.attr("width", width).attr("height", height);
}
window.onload = function () {
// TODO: add resize function
width = window.innerWidth - 10;
height = window.innerHeight - 10;
audioEl = document.querySelector(".fn-audio");
textEl = document.querySelector(".fn-text");
sampEl = document.querySelector("samp");
parseExifData(dataset);
createGraph();
// clear loading screen
document.querySelector(".loading").style.display = "none";
// Add resize features
d3.select(window).on("resize.updatesvg", updateWindow);
};