zotero-better-notes/addon/chrome/content/relationGraph.html

211 lines
6.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Force-Directed Tree</title>
<script src="chrome://__addonRef__/content/lib/js/d3.v7.min.js"></script>
<link rel="stylesheet" href="chrome://zotero-platform/content/zotero.css" />
<style>
html,
body {
overflow: hidden;
background: var(--material-background);
}
.tooltip {
position: absolute;
left: 0;
top: 0;
margin: 10px;
}
.node:hover {
cursor: pointer;
}
@media (prefers-color-scheme: light) {
:root {
--text-color: black;
}
}
@media (prefers-color-scheme: dark) {
:root {
--text-color: white;
}
}
</style>
<script>
window.addEventListener("message", (ev) => {
if (ev.data.type === "render") {
render(ev.data.graph);
}
});
function render(data) {
// Define the data for the nodes and links
// Specify the dimensions of the chart.
var width = window.innerWidth;
var height = window.innerHeight;
// 2: 100; 22: 50
function distance() {
if (data.nodes.length > 22) {
return 50;
}
return 105 - 2.5 * data.nodes.length;
}
// Specify the color scale.
const color = d3.scaleOrdinal([1, 2], ["grey", "#e8af59"]);
// The force simulation mutates links and nodes, so create a copy
// so that re-evaluating this cell produces the same result.
const links = data.links.map((d) => ({ ...d }));
const nodes = data.nodes.map((d) => ({ ...d }));
// Create a simulation with several forces.
const simulation = d3
.forceSimulation(nodes)
.force(
"link",
d3
.forceLink(links)
.id((d) => d.id)
.distance(distance()),
)
.force("charge", d3.forceManyBody().strength(-400))
.force("x", d3.forceX())
.force("y", d3.forceY());
// Create the SVG container.
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Add a line for each link, and a circle for each node.
const link = svg
.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width", (d) => Math.sqrt(d.value));
const node = svg
.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("g") // Append a 'g' element for each node
.attr("class", "node"); // Assign a class for styling if needed
node
.append("circle")
.attr("r", 7)
.attr("fill", (d) => color(d.group));
node
.append("text")
.attr("x", 0) // Center the text horizontally
.attr("y", 18) // Position the text below the circle
.attr("text-anchor", "middle") // Ensure the text is centered
.attr("fill", "var(--text-color)")
.attr("stroke", "none")
.text((d) => d.title);
node.append("title").text((d) => d.title);
// Add a drag behavior.
node.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended),
);
// Set the position attributes of links and nodes each time the simulation ticks.
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("transform", (d) => `translate(${d.x},${d.y})`);
});
// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
// Update the subject (dragged node) position during drag.
function dragged(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 dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
// Add the hover interaction
node
.on("mouseover", function (event, d) {
// Enlarge the node circle
d3.select(this)
.select("circle")
.transition()
.duration(200)
.attr("r", 10); // New, larger radius
d3.select(".tooltip").style("display", "").html(d.title);
})
.on("mouseout", function (event, d) {
// Shrink the node circle back to original size
d3.select(this)
.select("circle")
.transition()
.duration(500)
.attr("r", 7); // Original radius
d3.select(".tooltip").style("display", "none");
})
.on("click", function (event, d) {
window.postMessage(
{ type: "openNote", isShift: !!event.shiftKey, id: d.id },
"*",
);
});
document.body.replaceChildren(
svg.node(),
d3
.create("div")
.attr("class", "tooltip")
.style("display", "none")
.node(),
);
}
d3.select(window).on("resize", function () {
width = window.innerWidth;
height = window.innerHeight;
d3.select(document.querySelector("svg"))
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height]);
});
</script>
</head>
<body></body>
</html>