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 positioncamera.animate({ x: 0.5, y: 0.5, ratio: 0.5 });
// With custom durationcamera.animate({ x: 0.3, y: 0.7, ratio: 0.2 }, { duration: 1000 });
// Reset to default viewcamera.animatedReset();
// Zoom in / out by a factorcamera.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 pixelsconst screenPos = renderer.graphToViewport({ x: 0, y: 0 });
// Screen pixels -> graph coordinatesconst 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 nodesfitViewportToNodes(renderer, ["node1", "node2", "node3"], { animate: true });
// Get all nodes currently visible on screenconst 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 namesconst 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 communitieslouvain.assign(graph, { nodeCommunityAttribute: "community" });const communities = new Set<string>();graph.forEachNode((_, attrs) => communities.add(attrs.community));const communitiesArray = Array.from(communities);
// Assign colors per communityconst 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 viewportconst 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-leftconst 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, },});