<div id="sigma-container"></div>
<style>
#sigma-container {
width: 100%;
height: 100%;
}
</style>
<script>
import { layerBorder } from "@sigma/node-border";
import Graph from "graphology";
import Sigma from "sigma";
import { DEFAULT_STYLES } from "sigma/types";
import { registerControls } from "../_controls";
const container = document.getElementById("sigma-container") as HTMLElement;
const { bgColor, useColorAlpha, nodeOpacity, edgeOpacity, edgeSize } = registerControls({
bgColor: {
type: "select",
label: "Background",
default: "#ffffff",
options: [
{ label: "White", value: "#ffffff" },
{ label: "Light gray", value: "#e0e0e0" },
{ label: "Dark gray", value: "#333333" },
{ label: "Black", value: "#000000" },
],
},
useColorAlpha: { type: "boolean", label: "Use color alpha (not opacity)", default: false },
nodeOpacity: { type: "number", label: "Node opacity", default: 0.8, min: 0, max: 1, step: 0.05 },
edgeOpacity: { type: "number", label: "Edge opacity", default: 0.1, min: 0, max: 1, step: 0.05 },
edgeSize: { type: "number", label: "Edge size", default: 25, min: 0, max: 100, step: 2.5 },
});
container.style.backgroundColor = bgColor;
const isDarkBg = bgColor === "#333333" || bgColor === "#000000";
// Load data
const res = await fetch("/data/wikipedia.json");
const data: {
nodes: { key: string; label: string; cluster: string; x: number; y: number; score: number }[];
edges: [string, string][];
clusters: { key: string; color: string; clusterLabel: string }[];
} = await res.json();
// Build cluster color map
const clusterColors: Record<string, string> = {};
for (const c of data.clusters) {
clusterColors[c.key] = c.color;
}
// Brighten a hex color by mixing it with white
function brighten(hex: string, amount = 0.5): string {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const mix = (c: number) => Math.round(c + (255 - c) * amount);
return `#${mix(r).toString(16).padStart(2, "0")}${mix(g).toString(16).padStart(2, "0")}${mix(b).toString(16).padStart(2, "0")}`;
}
// Convert hex color to rgba with given alpha
function withAlpha(hex: string, alpha: number): string {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
const graph = new Graph();
// Add nodes
for (const node of data.nodes) {
const color = clusterColors[node.cluster] || "#999";
const nodeColor = useColorAlpha ? withAlpha(color, nodeOpacity) : color;
const fillColor = brighten(color, 0.5);
graph.addNode(node.key, {
x: node.x,
y: node.y,
score: node.score,
label: node.label,
color: nodeColor,
fillColor: useColorAlpha ? withAlpha(fillColor, nodeOpacity) : fillColor,
});
}
// Add edges, colored by source node
for (const [source, target] of data.edges) {
if (graph.hasNode(source) && graph.hasNode(target)) {
const sourceColor = clusterColors[data.nodes.find((n) => n.key === source)?.cluster ?? ""] || "#999";
graph.addEdge(source, target, {
color: useColorAlpha ? withAlpha(sourceColor, edgeOpacity) : sourceColor,
});
}
}
new Sigma(graph, container, {
primitives: {
nodes: {
variables: {
fillColor: { type: "color", default: "#ccc" },
},
layers: [
layerBorder({
borders: [
{ size: 0.15, color: { attribute: "color" }, mode: "relative" },
{ size: 0, color: { attribute: "fillColor" }, fill: true },
],
}),
],
},
},
styles: {
nodes: [
DEFAULT_STYLES.nodes,
{
opacity: useColorAlpha ? 1 : nodeOpacity,
size: { attribute: "score", min: 25, max: 100, minValue: 0, maxValue: 0.1 },
...(isDarkBg ? { labelColor: { whenState: "isHovered", then: "#000", else: "#fff" } } : {}),
},
],
edges: [DEFAULT_STYLES.edges, { opacity: useColorAlpha ? 1 : edgeOpacity, size: edgeSize }],
},
});
</script>