Something went wrong. Try again.
The bmannconsulting.com website
Something went wrong. Try again.
9.4 kB · 311 lines
HTML
at commit 94de6376
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312<style> .links line { stroke: #ccc; opacity: 0.5; }
.nodes circle { cursor: pointer; fill: #8b88e6; 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: black; }
.inactive { opacity: 0.1; transition: all 0.15s ease-out; }
#graph-wrapper { background: #fcfcfc; border-radius: 4px; height: auto; } #graph-wrapper > svg { max-width: 100%; display: block; }</style>
<div id="graph-wrapper"> <script> window.addEventListener("load", loadGraph);
function loadGraph() { var oScript = document.createElement("script"); oScript.src = "https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"; oScript.crossOrigin = 'anonymous'; oScript.integrity = "sha512-FHsFVKQ/T1KWJDGSbrUhTJyS1ph3eRrxI228ND0EGaEp6v4a/vGwPWd3Dtd/+9cI7ccofZvl/wulICEurHN1pg=="; document.body.appendChild(oScript); oScript.onload = () => { const MINIMAL_NODE_SIZE = 8; const MAX_NODE_SIZE = 12; const ACTIVE_RADIUS_FACTOR = 1.5; const STROKE = 1; const FONT_SIZE = 16; const TICKS = 200; const FONT_BASELINE = 40; const MAX_LABEL_LENGTH = 50;
const graphData = {% include notes_graph.json %} let nodesData = graphData.nodes; let linksData = graphData.edges;
const nodeSize = {};
const updateNodeSize = () => { nodesData.forEach((el) => { let weight = 3 * Math.sqrt( linksData.filter((l) => l.source.id === el.id || l.target.id === el.id) .length + 1 ); if (weight < MINIMAL_NODE_SIZE) { weight = MINIMAL_NODE_SIZE; } else if (weight > MAX_NODE_SIZE) { weight = MAX_NODE_SIZE; } nodeSize[el.id] = weight; }); };
const onClick = (d) => { window.location = d.path };
const onMouseover = function (d) { const relatedNodesSet = new Set(); linksData .filter((n) => n.target.id == d.id || n.source.id == d.id) .forEach((n) => { relatedNodesSet.add(n.target.id); relatedNodesSet.add(n.source.id); });
node.attr("class", (node_d) => { if (node_d.id !== d.id && !relatedNodesSet.has(node_d.id)) { return "inactive"; } return ""; });
link.attr("class", (link_d) => { if (link_d.source.id !== d.id && link_d.target.id !== d.id) { return "inactive"; } return ""; });
link.attr("stroke-width", (link_d) => { if (link_d.source.id === d.id || link_d.target.id === d.id) { return STROKE * 4; } return STROKE; }); text.attr("class", (text_d) => { if (text_d.id !== d.id && !relatedNodesSet.has(text_d.id)) { return "inactive"; } return ""; }); };
const onMouseout = function (d) { node.attr("class", ""); link.attr("class", ""); text.attr("class", ""); link.attr("stroke-width", STROKE); };
const sameNodes = (previous, next) => { if (next.length !== previous.length) { return false; }
const map = new Map(); for (const node of previous) { map.set(node.id, node.label); }
for (const node of next) { const found = map.get(node.id); if (!found || found !== node.title) { return false; } }
return true; };
const sameEdges = (previous, next) => { if (next.length !== previous.length) { return false; }
const set = new Set(); for (const edge of previous) { set.add(`${edge.source.id}-${edge.target.id}`); }
for (const edge of next) { if (!set.has(`${edge.source.id}-${edge.target.id}`)) { return false; } }
return true; };
const graphWrapper = document.getElementById('graph-wrapper') const element = document.createElementNS("http://www.w3.org/2000/svg", "svg"); element.setAttribute("width", graphWrapper.getBoundingClientRect().width); element.setAttribute("height", window.innerHeight * 0.8); graphWrapper.appendChild(element);
const reportWindowSize = () => { element.setAttribute("width", window.innerWidth); element.setAttribute("height", window.innerHeight); };
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("forceX", d3.forceX().x(width / 2)) .force("forceY", d3.forceY().y(height / 2)) .force("charge", d3.forceManyBody()) .force( "link", d3 .forceLink(linksData) .id((d) => d.id) .distance(70) ) .force("center", d3.forceCenter(width / 2, height / 2)) .force("collision", d3.forceCollide().radius(80)) .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 resize = () => { if (d3.event) { const scale = d3.event.transform; zoomLevel = scale.k; g.attr("transform", scale); }
const zoomOrKeep = (value) => (zoomLevel >= 1 ? value / zoomLevel : value);
const font = Math.max(Math.round(zoomOrKeep(FONT_SIZE)), 1);
text.attr("font-size", (d) => font); text.attr("y", (d) => d.y - zoomOrKeep(FONT_BASELINE) + 8); link.attr("stroke-width", zoomOrKeep(STROKE)); node.attr("r", (d) => { return zoomOrKeep(nodeSize[d.id]); }); svg .selectAll("circle") .filter((_d, i, nodes) => d3.select(nodes[i]).attr("active")) .attr("r", (d) => zoomOrKeep(ACTIVE_RADIUS_FACTOR * nodeSize[d.id])); };
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 - nodeSize[d.id]) / 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 = () => { updateNodeSize(); node = node.data(nodesData, (d) => d.id); node.exit().remove(); node = node .enter() .append("circle") .attr("r", (d) => { return nodeSize[d.id]; }) .on("click", onClick) .on("mouseover", onMouseover) .on("mouseout", onMouseout) .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);
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) .on("mouseover", onMouseover) .on("mouseout", onMouseout) .merge(text);
node.attr("active", (d) => isCurrentPath(d.path) ? true : null); text.attr("active", (d) => isCurrentPath(d.path) ? true : null);
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]).on("zoom", resize);
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>