197 lines
5.2 KiB
HTML
Raw Normal View History

2020-07-21 21:26:28 -04:00
<style>
.links line {
stroke: #ccc;
opacity: 0.5;
}
.nodes circle {
cursor: pointer;
fill: blue;
transition: all 0.15s ease-out;
}
.text text {
cursor: pointer;
fill: #333;
text-shadow: -1px -1px 0 #fafafabb, 1px -1px 0 #fafafabb, -1px 1px 0 #fafafabb, 1px 1px 0 #fafafabb;
}
.nodes [active],
.text [active] {
cursor: pointer;
fill: red;
}
.nodes circle[active] {
r: 6;
}
#graph-wrapper {
background: #fafafa;
border-radius: 4px;
height: auto;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"
integrity="sha512-FHsFVKQ/T1KWJDGSbrUhTJyS1ph3eRrxI228ND0EGaEp6v4a/vGwPWd3Dtd/+9cI7ccofZvl/wulICEurHN1pg=="
crossorigin="anonymous"></script>
<div id="graph-wrapper">
<script>
const RADIUS = 4;
const STROKE = 1;
const FONT_SIZE = 15;
const TICKS = 100;
const FONT_BASELINE = 15;
const MAX_LABEL_LENGTH = 50;
const graphData = {% include notes_graph.json %}
let nodesData = graphData.nodes;
let linksData = graphData.edges;
const onClick = (d) => {
window.location = d.path
};
const element = document.createElementNS(
"http://www.w3.org/2000/svg",
"svg"
);
const graphWrapper = document.getElementById('graph-wrapper')
element.setAttribute("width", graphWrapper.getBoundingClientRect().width);
graphWrapper.setAttribute("height", window.innerHeight * 0.8);
element.setAttribute("height", window.innerHeight * 0.8);
graphWrapper.appendChild(element);
const reportWindowSize = () => {
element.setAttribute("width", graphWrapper.getBoundingClientRect().width);
graphWrapper.setAttribute("height", window.innerHeight * 0.8);
element.setAttribute("height", window.innerHeight * 0.8);
};
window.onresize = reportWindowSize;
const svg = d3.select("svg");
const width = Number(svg.attr("width"));
const height = Number(svg.attr("height"));
let zoomLevel = 1;
const simulation = d3
.forceSimulation(nodesData)
.force("charge", d3
.forceManyBody()
.strength(-4000)
)
.force(
"link",
d3
.forceLink(linksData)
.id((d) => d.id)
.distance(150)
)
.force("center", d3.forceCenter(width / 2, height / 2))
.stop();
const g = svg.append("g");
let link = g.append("g").attr("class", "links").selectAll(".link");
let node = g.append("g").attr("class", "nodes").selectAll(".node");
let text = g.append("g").attr("class", "text").selectAll(".text");
const zoomActions = () => {
const scale = d3.event.transform;
zoomLevel = scale.k;
g.attr("transform", scale);
const zoomOrKeep = (value) => (scale.k >= 1 ? value / scale.k : value);
const font = Math.max(Math.round(zoomOrKeep(FONT_SIZE)), 1);
text.attr("font-size", `${font}px`);
text.attr("y", (d) => d.y - zoomOrKeep(FONT_BASELINE));
link.attr("stroke-width", zoomOrKeep(STROKE));
node.attr("r", zoomOrKeep(RADIUS));
};
const ticked = () => {
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
text
.attr("x", (d) => d.x)
.attr("y", (d) => d.y - FONT_BASELINE / zoomLevel);
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);
};
const restart = () => {
node = node.data(nodesData, (d) => d.id);
node.exit().remove();
node = node
.enter()
.append("circle")
.attr("r", RADIUS)
// .attr("fill", (d) => getNodeColor(d))
.on("click", onClick)
.merge(node);
link = link.data(linksData, (d) => `${d.source.id}-${d.target.id}`);
link.exit().remove();
link = link
.enter()
.append("line")
.attr("stroke-width", STROKE)
.merge(link);
node.attr("active", (d) => isCurrentPath(d.path) ? true : null);
text.attr("active", (d) => isCurrentPath(d.path) ? true : null);
text = text.data(nodesData, (d) => d.label);
text.exit().remove();
text = text
.enter()
.append("text")
.text((d) => shorten(d.label.replace(/_*/g, ""), MAX_LABEL_LENGTH))
.attr("font-size", `${FONT_SIZE}px`)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "central")
.on("click", onClick)
.merge(text);
simulation.nodes(nodesData);
simulation.force("link").links(linksData);
simulation.alpha(1).restart();
simulation.stop();
for (let i = 0; i < TICKS; i++) {
simulation.tick();
}
ticked();
};
const zoomHandler = d3
.zoom()
.scaleExtent([0.2, 3])
//.translateExtent([[0,0], [width, height]])
//.extent([[0, 0], [width, height]])
.on("zoom", zoomActions);
zoomHandler(svg);
restart();
function isCurrentPath(notePath) {
return window.location.pathname.includes(notePath)
}
function shorten(str, maxLen, separator = ' ') {
if (str.length <= maxLen) return str;
return str.substr(0, str.lastIndexOf(separator, maxLen)) + '...';
}
</script>
</div>