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.

254 lines
6.4 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;
var links = [],
nodes = [],
groups = [];
let zoomLvl = 1;
let lastK = 0;
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",
});
}
});
});
// 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 rScale = d3.scaleLinear().domain([0, 30000000]).range([2, 15]);
const simulation = d3
.forceSimulation(nodes)
.force("center", d3.forceCenter(width / 2, height / 2))
.force(
"collision",
d3
.forceCollide()
// .radius((d) => rScale(d.size) + 1)
.iterations(3)
)
.force(
"link",
d3
.forceLink(links)
.id((d) => d.id)
.strength((d) => 0.2)
// .distance((d) => (d.source.type === "directory" ? 80 : 20))
)
.force("charge", d3.forceManyBody().strength(-1));
// .alphaTarget(0.3); // stay hot
const svg = d3
.create("svg") //
.attr("width", width)
.attr("height", height);
const g = svg.append("g");
const labels = g
.append("g")
.selectAll("text")
.data(nodes)
.join("text")
.attr("font-size", (d) => (d.type === "directory" ? 12 : 3))
.text((d) => d.id)
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y);
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.size))
.attr("r", 3)
.attr("stroke", "black")
.attr("fill", (d) => {
return d.FileType === "directory" ? "orange" : "white";
})
// .attr("fill","rgba(255,255,255,.5)")
.attr("label", (d) => d.id)
.attr("path", (d) => d.id)
.attr("group", (d) => d.group);
// 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);
});
// add zoom behavior
svg.call(
d3
.zoom()
.extent([
[0, 0],
[width, height],
])
.scaleExtent([1, 100])
.on("zoom", zoomed)
);
// add some mouse behaviors
svg
.selectAll("circle")
.on("mouseenter", function (e) {
if (d3.select(this).attr("fill") !== "black") {
d3.select(this).attr("fill", "grey");
}
})
.on("click", function () {
d3.selectAll("circle").attr("fill", "rgba(255,255,255,.5)");
d3.select(this).attr("fill", "black");
var path = this.getAttribute("path");
// do things based on the file type
if (path.match(/\.(?:wav|mp3|flac)$/i)) {
playAudio(path);
}
})
.on("mouseleave", function () {
if (d3.select(this).attr("fill") !== "black") {
d3.select(this).attr("fill", "rgba(255,255,255,.5)");
}
});
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 zoomed(e) {
if (e.transform.k > 2 && lastK != e.transform.k) {
lastK = e.transform.k;
console.log("zoomed");
zoomLvl = Math.log2(e.transform.k);
globalNode.attr("stroke-width", 1 / zoomLvl);
link.attr("stroke-width", 1 / zoomLvl);
labels.attr("font-size", (d) => (d.type === "directory" ? 12 : 3) / zoomLvl);
}
g.attr("transform", e.transform);
}
}
function playAudio(src) {
console.log("play audio: ", src);
audioEl.src = src;
audioEl.currentTime = 0;
audioEl.play();
}
window.onload = function () {
// TODO: add resize function
width = window.innerWidth;
height = window.innerHeight;
parseExifData(dataset);
createGraph();
// clear loading screen
document.querySelector(".loading").style.display = "none";
};