Skip to content
This is the alpha v4 version website. Looking for the v3 documentation?

Styles and positioning

Sigma renders labels for both nodes and edges on the GPU using SDF text. This page covers font styles, positioning, and visibility for both.

Node labels

Positioning

Labels can be placed relative to their node using the labelPosition style property:

styles: {
nodes: [
{
label: { attribute: "label" },
labelPosition: "right", // "right" | "left" | "above" | "below" | "over"
},
],
}

You can also set labelAngle for rotated labels.

Font and color

styles: {
nodes: [
{
labelColor: "#333",
labelSize: 14,
labelFont: "Georgia, serif",
},
],
}

Visibility

By default, sigma uses density-based culling to avoid overlapping labels. You can override this per-node:

  • "auto" (default): density-based, only the most important labels are shown, and depending on the zoom level
  • "visible": always displayed
  • "hidden": never displayed
styles: {
nodes: [
{ labelVisibility: "auto" },
{
whenState: "isHovered",
then: { labelVisibility: "visible" },
},
],
}

The labelRenderedSizeThreshold and labelDensity settings control the density-based culling behavior (see Settings).

This example demonstrates label rendering at various font sizes and styles:

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 NODE_COUNT = 8;
const SPACING = 80;
const { fontFamily, baseLabelSize, labelSizeIncrement, labelAngle } = registerControls({
fontFamily: {
type: "select",
label: "Font family",
default: "sans-serif",
options: [
{ label: "Sans-serif", value: "sans-serif" },
{ label: "Serif", value: "serif" },
],
},
baseLabelSize: { type: "number", label: "Base label size", default: 8, min: 4, max: 48, step: 1 },
labelSizeIncrement: { type: "number", label: "Label size increment", default: 6, min: 0, max: 20, step: 1 },
labelAngle: { type: "number", label: "Label angle (°)", default: 0, min: -180, max: 180, step: 5 },
});
const graph = new Graph();
for (let i = 0; i < NODE_COUNT; i++) {
const size = baseLabelSize + i * labelSizeIncrement;
graph.addNode(`node-${i}`, {
x: 0,
y: -i * SPACING,
size: 10,
label: `Size ${size}`,
labelSize: size,
});
}
new Sigma(graph, container, {
styles: {
nodes: [
DEFAULT_STYLES.nodes,
{
labelVisibility: "visible",
backdropVisibility: "visible",
labelSize: { attribute: "labelSize" },
labelFont: fontFamily,
labelAngle: () => (labelAngle * Math.PI) / 180,
},
],
},
settings: {
itemSizesReference: "positions",
zoomToSizeRatioFunction: (x: number) => x,
autoRescale: true,
},
});

This example shows how to play with label colors, backgrounds and positions:

import Graph from "graphology";
import Sigma from "sigma";
import { layerFill, sdfCircle, sdfDiamond, sdfSquare, sdfTriangle } from "sigma/rendering";
import { DEFAULT_STYLES } from "sigma/types";
import type { LabelPosition } from "sigma/types";
import { registerControls } from "../_controls";
const container = document.getElementById("sigma-container") as HTMLElement;
const ROW_POSITIONS: LabelPosition[] = ["right", "left", "above", "below", "over"];
const COLORS = ["#9242D5", "#5B8FF9", "#5AD8A6", "#F6BD16", "#E8684A"];
const SHAPES = ["circle", "square", "triangle", "diamond"] as const;
const SPACING = 50;
const NODE_SIZE = 15;
const graph = new Graph();
for (let row = 0; row < ROW_POSITIONS.length; row++) {
const position = ROW_POSITIONS[row];
for (let col = 0; col < SHAPES.length; col++) {
const shape = SHAPES[col];
graph.addNode(`node-${row}-${col}`, {
x: col * SPACING,
y: -row * SPACING,
size: NODE_SIZE,
color: COLORS[row],
label: `${shape} / ${position}`,
shape,
labelPosition: position,
});
}
}
const { labelAngle, labelMargin, rotateWithCamera } = registerControls({
labelAngle: { type: "number", label: "Angle (°)", default: 0, min: -180, max: 180, step: 5 },
labelMargin: { type: "number", label: "Margin (px)", default: 0, min: 0, max: 50, step: 1 },
rotateWithCamera: { type: "boolean", label: "Rotate with camera", default: false },
});
new Sigma(graph, container, {
primitives: {
nodes: {
shapes: [sdfCircle(), sdfSquare(), sdfTriangle(), sdfDiamond()],
layers: [layerFill()],
rotateWithCamera,
label: { margin: labelMargin },
},
},
styles: {
nodes: [
DEFAULT_STYLES.nodes,
{
shape: { attribute: "shape" },
labelColor: { attribute: "color" },
labelBackgroundColor: "#ffffff66",
labelPosition: { attribute: "labelPosition", defaultValue: "right" },
labelAngle: () => (labelAngle * Math.PI) / 180,
labelVisibility: "visible",
},
],
},
settings: {
itemSizesReference: "positions",
zoomToSizeRatioFunction: (x: number) => x,
autoRescale: true,
},
});

Edge labels

Edge labels are disabled by default. Enable them with the renderEdgeLabels setting:

new Sigma(graph, container, {
settings: { renderEdgeLabels: true },
});

By default, label text comes from the edge’s label attribute:

graph.addEdge("a", "b", { label: "connects to" });

However, label is also an edge style property, that can be rebound to another attribute, for instance.

Position

Edge labels can be placed relative to the edge path:

PositionDescription
"over"Centered directly on the edge path (default)
"above"Offset above the edge path
"below"Offset below the edge path
"auto"GPU picks above or below based on which node is leftmost
styles: {
edges: [{ labelPosition: "above" }],
}

Font size modes

Edge labels support two font size modes, configured in primitives.edges.label:

  • "fixed" (default): constant pixel size regardless of zoom
  • "scaled": scales with camera zoom
primitives: {
edges: {
label: { fontSizeMode: "scaled" },
},
},

Text border

When labels are positioned "over" the edge, they can be hard to read. The textBorder option adds an outline around each character:

primitives: {
edges: {
label: {
textBorder: { width: 2, color: "#ffffff" },
},
},
},

This example shows how to manipulate edge label positioning, font size modes, and text borders:

import Graph from "graphology";
import Sigma from "sigma";
import { extremityArrow, layerPlain, pathCurved, pathLine, pathStepCurved } from "sigma/rendering";
import { DEFAULT_STYLES } from "sigma/types";
import type { EdgeLabelFontSizeMode, EdgeLabelPosition } from "sigma/types";
import { registerControls } from "../_controls";
const container = document.getElementById("sigma-container") as HTMLElement;
// Each column demonstrates one label styling variation. The first four use
// text borders for readability; the last one uses a label background
// (ribbon) instead — both styles live side by side.
const COLS = [
{ position: "over" as EdgeLabelPosition, background: false, label: "over / border" },
{ position: "above" as EdgeLabelPosition, background: false, label: "above / border" },
{ position: "below" as EdgeLabelPosition, background: false, label: "below / border" },
{ position: "auto" as EdgeLabelPosition, background: false, label: "auto / border" },
{ position: "over" as EdgeLabelPosition, background: true, label: "over / background" },
];
const PATHS = [
{ name: "straight", label: "Straight" },
{ name: "curved", label: "Curved" },
{ name: "stepCurved", label: "Step Curved" },
] as const;
const COL_SPACING = 100;
const ROW_SPACING = 80;
const NODE_SPACING = 50;
const EDGE_SIZE = 5;
const EDGE_COLOR = "#5B8FF9";
const LABEL_BACKGROUND_COLOR = "#c3d2f1";
const { fontSizeMode, headType, tailType } = registerControls({
fontSizeMode: {
type: "select",
label: "Font size mode",
default: "fixed",
options: [
{ label: "Fixed", value: "fixed" },
{ label: "Scaled", value: "scaled" },
],
},
headType: {
type: "select",
label: "Head extremity",
default: "arrow",
options: [
{ label: "None", value: "none" },
{ label: "Arrow", value: "arrow" },
],
},
tailType: {
type: "select",
label: "Tail extremity",
default: "none",
options: [
{ label: "None", value: "none" },
{ label: "Arrow", value: "arrow" },
],
},
});
const graph = new Graph();
for (let row = 0; row < PATHS.length; row++) {
for (let col = 0; col < COLS.length; col++) {
const pathInfo = PATHS[row];
const colInfo = COLS[col];
const cellX = col * COL_SPACING;
const cellY = -row * ROW_SPACING;
const sourceId = `${pathInfo.name}-${col}-source`;
graph.addNode(sourceId, {
x: cellX - NODE_SPACING / 2,
y: cellY,
});
const targetId = `${pathInfo.name}-${col}-target`;
graph.addNode(targetId, {
x: cellX + NODE_SPACING / 2,
y: cellY + NODE_SPACING / 2,
});
graph.addEdge(sourceId, targetId, {
size: EDGE_SIZE,
color: EDGE_COLOR,
label: `${pathInfo.label} / ${colInfo.label}`,
path: pathInfo.name,
labelPosition: colInfo.position,
curvature: pathInfo.name === "curved" ? 0.3 : 0,
labelBackground: colInfo.background,
});
}
}
new Sigma(graph, container, {
primitives: {
edges: {
paths: [pathLine(), pathCurved(), pathStepCurved({ cornerRadius: 1 })],
extremities: [extremityArrow()],
layers: [layerPlain()],
defaultHead: headType === "none" ? undefined : headType,
defaultTail: tailType === "none" ? undefined : tailType,
variables: { curvature: { type: "number", default: 0 } },
label: {
fontSizeMode: fontSizeMode as EdgeLabelFontSizeMode,
color: "#041a47",
textBorder: { width: 12, color: "#ffffff" },
},
},
},
styles: {
nodes: [DEFAULT_STYLES.nodes, { size: 6, color: "darkgrey", label: "" }],
edges: [
DEFAULT_STYLES.edges,
{
path: { attribute: "path" },
labelVisibility: "visible",
labelPosition: { attribute: "labelPosition", defaultValue: "over" },
},
{
whenData: "labelBackground",
then: {
labelBackgroundColor: LABEL_BACKGROUND_COLOR,
labelBackgroundPadding: 4,
},
},
],
},
settings: {
renderEdgeLabels: true,
itemSizesReference: "positions",
autoRescale: true,
},
});