2023-03-21 15:05:20 +06:00

216 lines
7.7 KiB
Plaintext

<script>
const getCssVar = (variable) => getComputedStyle(document.body).getPropertyValue(variable);
function htmlDecode(input) {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
window.graphData = null;
window.maxGraphDepth = 1;
function getNextLevelNeighbours(existing, remaining) {
const keys = Object.values(existing).map((n) => n.neighbors).flat();
const n_remaining = Object.keys(remaining).reduce((acc, key) => {
if (keys.indexOf(key) != -1) {
if (!remaining[key].hide) {
existing[key] = remaining[key];
}
} else {
acc[key] = remaining[key];
}
return acc;
}, {});
return existing, n_remaining;
}
function filterToDepth(data) {
let remaining = JSON.parse(JSON.stringify(data.nodes));
let currentLink = decodeURI(window.location.pathname);
let currentNode = remaining[currentLink] || Object.values(remaining).find((v) => v.home);
delete remaining[currentNode.url];
if (!currentNode.home) {
let home = Object.values(remaining).find((v) => v.home);
delete remaining[home.url];
}
currentNode.current = true;
let existing = {};
existing[currentNode.url] = currentNode;
for (let i = 0; i < window.maxGraphDepth; i++) {
existing, remaining = getNextLevelNeighbours(existing, remaining);
}
nodes = Object.values(existing);
if (!currentNode.home) {
nodes = nodes.filter(n => !n.home);
}
let ids = nodes.map((n) => n.id);
let graphData = {
nodes,
links: data.links.filter((con) => ids.indexOf(con.target) > -1 && ids.indexOf(con.source) > -1),
}
return graphData;
}
var Graph;
function renderGraph(graphData, id, delay) {
const el = document.getElementById(id);
width = el.offsetWidth;
height = el.offsetHeight;
const highlightNodes = new Set();
let hoverNode = null;
const color = getCssVar("--graph-main");
const mutedColor = getCssVar("--graph-muted");
let Graph = ForceGraph()
(el)
.graphData(graphData)
.nodeId('id')
.nodeLabel('title')
.linkSource('source')
.linkTarget('target')
.d3AlphaDecay(0.10)
.width(width)
.height(height)
.linkDirectionalArrowLength(2)
.linkDirectionalArrowRelPos(0.5)
.autoPauseRedraw(false)
.linkColor((link) => {
if (hoverNode == null) {
return color;
}
if (link.source.id == hoverNode.id || link.target.id == hoverNode.id) {
return color;
} else {
return mutedColor;
}
})
.nodeCanvasObject((node, ctx) => {
const numberOfNeighbours = (node.neighbors && node.neighbors.length) || 2;
const nodeR = Math.min(7, Math.max(numberOfNeighbours / 2, 2));
ctx.beginPath();
ctx.arc(node.x, node.y, nodeR, 0, 2 * Math.PI, false);
if (hoverNode == null) {
ctx.fillStyle = color;
} else {
if (node == hoverNode || highlightNodes.has(node.url)) {
ctx.fillStyle = color;
} else {
ctx.fillStyle = mutedColor;
}
}
ctx.fill();
if (node.current) {
ctx.beginPath();
ctx.arc(node.x, node.y, nodeR + 1, 0, 2 * Math.PI, false);
ctx.lineWidth = 0.5;
ctx.strokeStyle = color;
ctx.stroke();
}
const label = htmlDecode(node.title)
const fontSize = 3.5;
ctx.font = `${fontSize}px Sans-Serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(label, node.x, node.y + nodeR + 2);
})
.onNodeClick(node => {
window.location = node.url;
})
.onNodeHover(node => {
highlightNodes.clear();
if (node) {
highlightNodes.add(node);
node.neighbors.forEach(neighbor => highlightNodes.add(neighbor));
}
hoverNode = node || null;
});
if (delay != null && graphData.nodes.length > 4) {
setTimeout(() => {
Graph.zoomToFit(5, 75);
}, delay);
}
return Graph;
}
function fetchGraphData() {
fetch('/graph.json').then(res => res.json()).then(data => {
window.graphData = data;
Graph = renderGraph(filterToDepth(JSON.parse(JSON.stringify(data))), "link-graph", 1);
});
}
fetchGraphData();
window.document.getElementById('graph-depth').value = window.maxGraphDepth;
window.document.getElementById('depth-display').innerText = window.maxGraphDepth;
window.document.getElementById('graph-depth').addEventListener('input', (evt) => {
window.maxGraphDepth = evt.target.value;
window.document.getElementById('depth-display').innerText = window.maxGraphDepth;
if (Graph != null) {
Graph._destructor();
Graph = null;
}
Graph = renderGraph(filterToDepth(JSON.parse(JSON.stringify(window.graphData))), "link-graph", 1);
setTimeout(() => {
Graph.zoomToFit(5, 75);
}, 1);
});
window.fullGraph = null;
function renderFullGraph() {
if (!window.fullGraph) {
const graphData = {
links: JSON.parse(JSON.stringify(window.graphData.links)),
nodes: [...Object.values(window.graphData.nodes)]
}
let g = document.createElement('div');
g.id = 'full-graph';
g.classList.add('show');
document.body.appendChild(g);
g.innerHTML = '<span id="full-graph-close"><i icon-name="x" aria-hidden="true"></i></span><div id="full-graph-container"></div>';
lucide.createIcons({
attrs: {
class: ["svg-icon"]
}
});
window.fullGraph = renderGraph(graphData, "full-graph-container", 200);
document.getElementById('full-graph-close').addEventListener('click', (evt) => {
g.classList.remove('show');
window.fullGraph._destructor();
window.fullGraph = null;
document.getElementById('full-graph').remove()
});
}
}
document.getElementById('graph-fs-btn').addEventListener('click', (evt) => {
const el = document.querySelector('.graph');
if (el.classList.contains('graph-fs')) {
el.classList.remove('graph-fs');
Graph.width(el.offsetWidth).height(el.offsetWidth);
} else {
el.classList.add('graph-fs');
Graph.width(el.offsetWidth).height(el.offsetWidth);
}
setTimeout(() => {
Graph.zoomToFit(5, 75);
}, 1);
});
document.getElementById('global-graph-btn').addEventListener('click', (evt) => {
if (!window.fullGraph) {
renderFullGraph();
}
});
</script>