<div id="sigma-container"></div>
<style>
#sigma-container {
width: 100%;
height: 100%;
}
</style>
<script>
import Graph from "graphology";
import Sigma from "sigma";
import { DEFAULT_STYLES } from "sigma/types";
interface Dataset {
nodes: { key: string; label: string; tag: string; cluster: string; x: number; y: number; score: number }[];
edges: [string, string][];
clusters: { key: string; color: string; clusterLabel: string }[];
}
const response = await fetch("/data/wikipedia.json");
const dataset: Dataset = await response.json();
const clusterColors = Object.fromEntries(dataset.clusters.map((c) => [c.key, c.color]));
const graph = new Graph();
const scoreExtents = { min: Infinity, max: -Infinity };
for (const node of dataset.nodes) {
scoreExtents.min = Math.min(scoreExtents.min, node.score);
scoreExtents.max = Math.max(scoreExtents.max, node.score);
graph.addNode(node.key, {
label: node.label,
x: node.x,
y: node.y,
cluster: node.cluster,
score: node.score,
});
}
for (const [source, target] of dataset.edges) {
if (graph.hasNode(source) && graph.hasNode(target) && !graph.hasEdge(source, target)) {
graph.addEdge(source, target);
}
}
const renderer = new Sigma(graph, document.getElementById("sigma-container") as HTMLElement, {
customNodeState: {
isActive: false,
},
customEdgeState: {
isActive: false,
},
customGraphState: {
hasActiveSubgraph: false,
},
primitives: {
depthLayers: [
// Base graph
"edges",
"nodes",
// Active subgraph
"activeEdges",
"activeNodes",
// Hovered node
"topNodes",
],
},
styles: {
nodes: [
DEFAULT_STYLES.nodes,
{
color: {
attribute: "cluster",
dict: clusterColors,
defaultValue: "#999",
},
size: { attribute: "score", min: 10, max: 50, minValue: scoreExtents.min, maxValue: scoreExtents.max },
label: { attribute: "label" },
},
{
when: (_attrs, state, graphState) => graphState.hasActiveSubgraph && !state.isActive && !state.isHovered,
then: { color: "#eee", label: "" },
},
{
whenState: "isActive",
then: { depth: "activeNodes" },
},
{
whenState: "isHovered",
then: { depth: "topNodes" },
},
],
edges: [
DEFAULT_STYLES.edges,
{ color: "#ccc", size: 5 },
{
when: (_attrs, state, graphState) => graphState.hasActiveSubgraph && !state.isActive,
then: { color: "#eee" },
},
{
whenState: "isActive",
then: { depth: "activeEdges" },
},
],
},
});
function setActiveSubgraph(activeNodes: Set<string> | null) {
graph.forEachNode((node) => {
renderer.setNodeState(node, { isActive: activeNodes?.has(node) });
});
graph.forEachEdge((edge) => {
const [source, target] = graph.extremities(edge);
renderer.setEdgeState(edge, { isActive: activeNodes?.has(source) && activeNodes?.has(target) });
});
renderer.setGraphState({ hasActiveSubgraph: !!activeNodes });
// Trigger a renderer update:
renderer.refresh({ skipIndexation: true });
}
renderer.on("enterNode", ({ node }) => {
const neighbors = new Set(graph.neighbors(node));
neighbors.add(node);
setActiveSubgraph(neighbors);
});
renderer.on("leaveNode", () => {
setActiveSubgraph(null);
});
</script>