123 lines
3.7 KiB
HTML
123 lines
3.7 KiB
HTML
<!-- graph.html -->
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Flow Graph Viewer</title>
|
|
<style>
|
|
body { margin: 0; font-family: sans-serif; display: flex; height: 100vh; }
|
|
#sidebar {
|
|
width: 240px; background: #f4f4f4; padding: 12px; overflow-y: auto;
|
|
border-right: 1px solid #ccc;
|
|
}
|
|
#network { flex: 1; }
|
|
h3 { margin-top: 0; }
|
|
.node-info { font-size: 12px; white-space: pre-wrap; }
|
|
</style>
|
|
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
<script src="graph-data.js"></script>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="sidebar">
|
|
<h3>Filters</h3>
|
|
<label><input type="checkbox" id="flow" checked> Flow edges</label><br>
|
|
<label><input type="checkbox" id="link" checked> Link edges</label><br>
|
|
<label><input type="checkbox" id="form" checked> Form edges</label><br>
|
|
<label><input type="checkbox" id="dead" checked> Show dead-ends</label><br>
|
|
<label><input type="checkbox" id="cycles"> Highlight cycles</label><br>
|
|
<label><input type="checkbox" id="rank"> Highlight top-ranked</label><br>
|
|
<label><input type="checkbox" id="broken"> Highlight broken links</label><br>
|
|
|
|
<h3>Node</h3>
|
|
<div id="info" class="node-info"></div>
|
|
</div>
|
|
|
|
<div id="network"></div>
|
|
|
|
<script>
|
|
const container = document.getElementById("network");
|
|
|
|
const data = {
|
|
nodes: new vis.DataSet(NODES),
|
|
edges: new vis.DataSet(EDGES)
|
|
};
|
|
|
|
const network = new vis.Network(container, data, {
|
|
layout: { improvedLayout: true },
|
|
physics: { stabilization: false }
|
|
});
|
|
|
|
function applyFilters() {
|
|
const showFlow = document.getElementById("flow").checked;
|
|
const showLink = document.getElementById("link").checked;
|
|
const showForm = document.getElementById("form").checked;
|
|
const showDead = document.getElementById("dead").checked;
|
|
const highlightCycles = document.getElementById("cycles").checked;
|
|
const highlightRank = document.getElementById("rank").checked;
|
|
const highlightBroken = document.getElementById("broken").checked;
|
|
|
|
data.nodes.update(NODES.map(n => {
|
|
const isDead = DEAD_ENDS.includes(n.id);
|
|
const inCycle = CYCLES_SET.has(n.id);
|
|
const rank = PAGE_RANK[n.id] || 0;
|
|
|
|
// -----------------------------
|
|
// PATCH 1 — compare normalized IDs
|
|
// -----------------------------
|
|
const isBroken = BROKEN_LINKS.some(b => b.url === n.id);
|
|
|
|
return {
|
|
id: n.id,
|
|
hidden: !showDead && isDead,
|
|
color:
|
|
highlightBroken && isBroken ? "#ff0000" :
|
|
highlightCycles && inCycle ? "#ff00aa" :
|
|
highlightRank && rank > TOP_RANK_THRESHOLD ? "#ff8800" :
|
|
n.baseColor
|
|
};
|
|
}));
|
|
|
|
data.edges.update(EDGES.map(e => ({
|
|
id: e.id,
|
|
hidden:
|
|
(e.type === "flow" && !showFlow) ||
|
|
(e.type === "link" && !showLink) ||
|
|
(e.type === "form" && !showForm)
|
|
})));
|
|
}
|
|
|
|
document.querySelectorAll("#sidebar input").forEach(cb =>
|
|
cb.addEventListener("change", applyFilters)
|
|
);
|
|
|
|
network.on("click", params => {
|
|
if (!params.nodes.length) {
|
|
document.getElementById("info").textContent = "";
|
|
return;
|
|
}
|
|
const id = params.nodes[0];
|
|
const info = NODE_INFO[id];
|
|
|
|
// -----------------------------
|
|
// PATCH 2 — compare normalized IDs
|
|
// -----------------------------
|
|
const broken = BROKEN_LINKS.find(b => b.url === id);
|
|
|
|
document.getElementById("info").textContent =
|
|
`URL: ${info.fullUrl}
|
|
Cluster: ${info.cluster}
|
|
Types: ${info.types.join(", ")}
|
|
PageRank: ${PAGE_RANK[id].toFixed(5)}
|
|
Dead-end: ${DEAD_ENDS.includes(id)}
|
|
In cycle: ${CYCLES_SET.has(id)}
|
|
Broken: ${broken ? broken.status : "no"}`;
|
|
});
|
|
|
|
applyFilters();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|