Attachments
Label attachments let you display custom SVG content anchored to a node’s label. This is useful for tooltips, info cards, or interactive UI. The way it works is that, when sigma needs to render an attachment for a given node, it will call a function that will return the content as an SVG string of a Canvas element directly. It will then draw it in a texture, and render it directly with the WebGL engine.
How it works
- Declare an attachment renderer in
primitives.nodes.labelAttachments. Each renderer is a function that receives context and returns the content. - Assign it to nodes via the
labelAttachmentstyle property, typically with a conditional to show it on hover. - Position it with
labelAttachmentPlacement.
The renderer function receives a LabelAttachmentContext with attributes, state, and graphState. It returns a LabelAttachmentContent object with type: "svg" and an svg string.
import Graph from "graphology";import Sigma from "sigma";import { layerFill, sdfCircle } from "sigma/rendering";import { DEFAULT_STYLES } from "sigma/types";import type { LabelAttachmentContent, LabelAttachmentContext } from "sigma/types";
import { registerControls } from "../_controls";import { nodeExtent } from "graphology-metrics/graph/extent";
const container = document.getElementById("sigma-container") as HTMLElement;
// Used to measure text width so the SVG is sized to fit its content.const measureCtx = document.createElement("canvas").getContext("2d")!;
function drawInfoCard({ attributes }: LabelAttachmentContext): LabelAttachmentContent { const tag = attributes.tag as string; const clusterLabel = attributes.clusterLabel as string; const color = attributes.color as string;
measureCtx.font = "11px sans-serif"; const width = Math.ceil(Math.max(measureCtx.measureText(tag).width, measureCtx.measureText(clusterLabel).width)) + 8;
return { type: "svg", svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="34"> <text x="4" y="13" font-family="sans-serif" font-size="11" fill="darkgrey">${tag}</text> <text x="4" y="29" font-family="sans-serif" font-size="11" fill="${color}">${clusterLabel}</text> </svg>`, };}
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 res = await fetch("/data/wikipedia.json");const dataset: Dataset = await res.json();
const clustersByKey = Object.fromEntries(dataset.clusters.map((c) => [c.key, c]));
const graph = new Graph();
dataset.nodes.forEach((node) => { const cluster = clustersByKey[node.cluster]; graph.addNode(node.key, { ...node, clusterLabel: cluster?.clusterLabel, color: cluster?.color, });});
dataset.edges.forEach(([source, target]) => { graph.addEdge(source, target);});
const [minScore, maxScore] = nodeExtent(graph, "score");
new Sigma(graph, container, { primitives: { nodes: { shapes: [sdfCircle()], layers: [layerFill()], labelAttachments: { hoverInfo: drawInfoCard, }, }, }, styles: { nodes: [ DEFAULT_STYLES.nodes, { size: { attribute: "score", min: 15, max: 100, minValue: minScore, maxValue: maxScore, }, labelAttachment: { whenState: "isHovered", then: "hoverInfo", }, }, ], }, settings: { labelRenderedSizeThreshold: 6, },});