<div id="sigma-container"></div>
<div id="search" class="interaction">
<input type="search" id="search-input" list="suggestions" placeholder="Try searching for a node..." />
<datalist id="suggestions"></datalist>
import Graph from "graphology";
import Sigma from "sigma";
import type { Coordinates } from "sigma/types";
import data from "../_data/data.json";
const container = document.getElementById("sigma-container") as HTMLElement;
const searchInput = document.getElementById("search-input") as HTMLInputElement;
const searchSuggestions = document.getElementById("suggestions") as HTMLDataListElement;
const graph = new Graph();
const renderer = new Sigma(graph, container, {
hasActiveSubgraph: false,
depthLayers: ["edges", "nodes", "topEdges", "topNodes", "activeNodes"],
color: { attribute: "color" },
size: { attribute: "size" },
label: { attribute: "label" },
whenState: "isHighlighted",
then: { labelVisibility: "visible" },
when: (_attrs, state, graphState) => graphState.hasActiveSubgraph && !state.isActive && !state.isHighlighted,
then: { color: GREY, label: "" },
then: { depth: "topNodes", labelVisibility: "visible" },
then: { depth: "activeNodes", labelVisibility: "visible" },
color: { attribute: "color", defaultValue: "#ccc" },
size: { attribute: "size", defaultValue: 1 },
when: (_attrs, state, graphState) => graphState.hasActiveSubgraph && !state.isActive,
then: { depth: "topEdges" },
searchSuggestions.innerHTML = graph
.map((node) => `<option value="${graph.getNodeAttribute(node, "label")}"></option>`)
let hoveredNode: string | null = null;
let activeSearchNodes: Set<string> | null = null;
function setActiveSubgraph(activeNodes: Set<string> | null) {
graph.forEachNode((node) => {
renderer.setNodeState(node, { isActive: !!activeNodes && activeNodes.has(node) });
graph.forEachEdge((edge) => {
const [source, target] = graph.extremities(edge);
renderer.setEdgeState(edge, {
isActive: !!activeNodes && activeNodes.has(source) && activeNodes.has(target),
renderer.setGraphState({ hasActiveSubgraph: !!activeNodes && activeNodes.size > 0 });
function refreshActiveSubgraph() {
const neighbors = new Set(graph.neighbors(hoveredNode));
neighbors.add(hoveredNode);
setActiveSubgraph(neighbors);
setActiveSubgraph(activeSearchNodes);
renderer.refresh({ skipIndexation: true });
function setSearchQuery(query: string) {
if (searchInput.value !== query) searchInput.value = query;
let selectedNode: string | null = null;
const lcQuery = query.toLowerCase();
const suggestions = graph
.map((n) => ({ id: n, label: graph.getNodeAttribute(n, "label") as string }))
.filter(({ label }) => label.toLowerCase().includes(lcQuery));
if (suggestions.length === 1 && suggestions[0].label === query) {
selectedNode = suggestions[0].id;
activeSearchNodes = null;
activeSearchNodes = new Set(suggestions.map(({ id }) => id));
activeSearchNodes = null;
graph.forEachNode((node) => renderer.setNodeState(node, { isHighlighted: node === selectedNode }));
const nodePosition = renderer.getNodeDisplayData(selectedNode) as Coordinates;
renderer.getCamera().animate(nodePosition, { duration: 500 });
searchInput.addEventListener("input", () => setSearchQuery(searchInput.value || ""));
searchInput.addEventListener("blur", () => setSearchQuery(""));
renderer.on("enterNode", ({ node }) => {
renderer.on("leaveNode", () => {