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

Camera and viewport

Sigma’s camera controls what portion of the graph is visible. You can constrain user interactions, animate the camera, and convert between coordinate systems.

Zoom and pan boundaries

Constrain how far users can zoom:

new Sigma(graph, container, {
settings: {
minCameraRatio: 0.1, // Maximum zoom in (10x)
maxCameraRatio: 2, // Maximum zoom out (0.5x)
},
});

Constrain panning:

new Sigma(graph, container, {
settings: {
cameraPanBoundaries: { tolerance: 0.2 },
},
});

The tolerance value controls how far past the graph bounding box the user can pan, as a fraction of the viewport. Set cameraPanBoundaries: true for the default tolerance, or null (default) to disable.

Animated transitions

Use renderer.getCamera().animate() to smoothly transition the camera:

const camera = renderer.getCamera();
// Pan and zoom to a specific position
camera.animate({ x: 0.5, y: 0.5, ratio: 0.5 });
// With custom duration
camera.animate({ x: 0.3, y: 0.7, ratio: 0.2 }, { duration: 1000 });
// Reset to default view
camera.animatedReset();
// Zoom in / out by a factor
camera.animatedZoom(2);
camera.animatedUnzoom(2);

animate() returns a Promise that resolves when the animation completes.

Instant updates

For immediate (non-animated) changes:

camera.setState({ x: 0.5, y: 0.5, ratio: 1, angle: 0 });

Coordinate conversion

Convert between graph space and screen space:

// Graph coordinates -> screen pixels
const screenPos = renderer.graphToViewport({ x: 0, y: 0 });
// Screen pixels -> graph coordinates
const graphPos = renderer.viewportToGraph({ x: 100, y: 200 });

Viewport utilities

The @sigma/utils package provides helpers for working with the viewport:

import { fitViewportToNodes, getNodesInViewport } from "@sigma/utils";
// Animate the camera to fit a set of nodes
fitViewportToNodes(renderer, ["node1", "node2", "node3"], { animate: true });
// Get all nodes currently visible on screen
const visibleNodes = getNodesInViewport(renderer);
import { fitViewportToNodes, getNodesInViewport } from "@sigma/utils";
import Graph from "graphology";
import louvain from "graphology-communities-louvain";
import Sigma from "sigma";
import { registerControls } from "../_controls";
import data from "../_data/data.json";
// Hardcoded color palette with readable names
const COMMUNITY_COLORS: { color: string; name: string }[] = [
{ color: "#e63946", name: "red" },
{ color: "#457b9d", name: "blue" },
{ color: "#2a9d8f", name: "teal" },
{ color: "#e9c46a", name: "yellow" },
{ color: "#f4a261", name: "orange" },
{ color: "#6a4c93", name: "purple" },
{ color: "#8ac926", name: "green" },
{ color: "#ff6b6b", name: "pink" },
{ color: "#4cc9f0", name: "cyan" },
{ color: "#b5838d", name: "mauve" },
];
const graph = new Graph();
graph.import(data);
// Detect communities
louvain.assign(graph, { nodeCommunityAttribute: "community" });
const communities = new Set<string>();
graph.forEachNode((_, attrs) => communities.add(attrs.community));
const communitiesArray = Array.from(communities);
// Assign colors per community
const palette: Record<string, { color: string; name: string }> = {};
communitiesArray.forEach((c, i) => (palette[c] = COMMUNITY_COLORS[i % COMMUNITY_COLORS.length]));
graph.forEachNode((node, attrs) => graph.setNodeAttribute(node, "color", palette[attrs.community].color));
const container = document.getElementById("sigma-container") as HTMLElement;
const renderer = new Sigma(graph, container);
// Register a button per community to fit viewport
const controls: Record<string, { type: "button"; label: string; action: () => void }> = {};
communitiesArray.forEach((community) => {
const { name } = palette[community];
controls[`community-${name}`] = {
type: "button",
label: `Fit ${name}`,
action: () => {
fitViewportToNodes(
renderer,
graph.filterNodes((_, attrs) => attrs.community === community),
{ animate: true },
);
},
};
});
controls["fit-all"] = {
type: "button",
label: "Fit all",
action: () => {
renderer.getCamera().animate({ x: 0.5, y: 0.5, ratio: 1 }, { duration: 300 });
},
};
registerControls(controls);
// Display live count of visible nodes in the bottom-left
const infoEl = document.getElementById("visible-nodes-info") as HTMLDivElement;
setInterval(() => {
const count = getNodesInViewport(renderer).length;
infoEl.textContent = count === 0 ? "No visible nodes" : count === 1 ? "1 visible node" : `${count} visible nodes`;
}, 100);

Updating settings at runtime

renderer.setSettings({ enableCameraZooming: false });
renderer.setSettings({ minCameraRatio: 0.5, maxCameraRatio: 3 });
import Graph from "graphology";
import { parse } from "graphology-gexf/browser";
import Sigma from "sigma";
import { registerControls } from "../_controls";
const res = await fetch("/data/arctic.gexf");
const gexf = await res.text();
const graph = parse(Graph, gexf);
const container = document.getElementById("sigma-container") as HTMLElement;
const { enablePanning, enableZooming, enableRotation, boundCamera, minRatio, maxRatio, tolerance } = registerControls(
{
enablePanning: { type: "boolean", label: "Enable panning", default: true },
enableZooming: { type: "boolean", label: "Enable zooming", default: true },
enableRotation: { type: "boolean", label: "Enable rotation", default: true },
boundCamera: { type: "boolean", label: "Bound camera", default: true },
minRatio: { type: "number", label: "Min zoom ratio", default: 0.08, min: 0.001, max: 1, step: 0.001 },
maxRatio: { type: "number", label: "Max zoom ratio", default: 3, min: 0.1, max: 10, step: 0.1 },
tolerance: { type: "number", label: "Pan tolerance (px)", default: 500, min: 0, max: 2000, step: 50 },
},
);
const renderer = new Sigma(graph, container, {
settings: {
enableCameraPanning: enablePanning,
enableCameraZooming: enableZooming,
enableCameraRotation: enableRotation,
minCameraRatio: enableZooming ? minRatio : null,
maxCameraRatio: enableZooming ? maxRatio : null,
cameraPanBoundaries: boundCamera ? { tolerance } : null,
},
});