Skip to main content

Cytoscape.js vs vis-network vs Sigma.js: Graph Visualization (2026)

·PkgPulse Team

TL;DR

Cytoscape.js is the full-featured graph library — graph theory algorithms, multiple layouts, styling, analysis, used in bioinformatics and network analysis. vis-network is the interactive network visualization — drag/drop nodes, physics simulation, clustering, part of the vis.js suite. Sigma.js is the WebGL graph renderer — renders large graphs (100K+ nodes) with GPU acceleration, designed for performance. In 2026: Cytoscape.js for graph analysis + visualization, vis-network for interactive network diagrams, Sigma.js for large-scale graph rendering.

Key Takeaways

  • Cytoscape.js: ~500K weekly downloads — graph algorithms, layouts, bioinformatics
  • vis-network: ~200K weekly downloads — interactive, physics, drag/drop, clustering
  • Sigma.js: ~50K weekly downloads — WebGL, large graphs, 100K+ nodes, performance
  • Cytoscape.js has the richest algorithm library (shortest path, centrality, etc.)
  • vis-network has the best out-of-box interactivity (physics, drag, zoom)
  • Sigma.js handles the largest graphs (WebGL rendering)

Cytoscape.js

Cytoscape.js — graph theory + visualization:

Basic graph

import cytoscape from "cytoscape"

const cy = cytoscape({
  container: document.getElementById("graph"),
  elements: [
    // Nodes:
    { data: { id: "react", label: "React" } },
    { data: { id: "next", label: "Next.js" } },
    { data: { id: "remix", label: "Remix" } },
    { data: { id: "vue", label: "Vue" } },
    { data: { id: "nuxt", label: "Nuxt" } },

    // Edges:
    { data: { source: "react", target: "next" } },
    { data: { source: "react", target: "remix" } },
    { data: { source: "vue", target: "nuxt" } },
  ],
  style: [
    {
      selector: "node",
      style: {
        "background-color": "#3b82f6",
        "label": "data(label)",
        "color": "#fff",
        "text-valign": "center",
        "font-size": "12px",
      },
    },
    {
      selector: "edge",
      style: {
        "width": 2,
        "line-color": "#64748b",
        "target-arrow-color": "#64748b",
        "target-arrow-shape": "triangle",
        "curve-style": "bezier",
      },
    },
  ],
  layout: { name: "cose" },  // Force-directed layout
})

Graph algorithms

// Shortest path:
const dijkstra = cy.elements().dijkstra({
  root: cy.getElementById("react"),
  weight: (edge) => edge.data("weight") || 1,
})

const pathToVue = dijkstra.pathTo(cy.getElementById("vue"))
console.log("Distance:", dijkstra.distanceTo(cy.getElementById("vue")))

// Centrality:
const centrality = cy.elements().betweennessCentrality()
cy.nodes().forEach((node) => {
  console.log(`${node.id()}: ${centrality.betweenness(node)}`)
})

// Community detection:
const clusters = cy.elements().markovClustering()
clusters.forEach((cluster, i) => {
  cluster.style("background-color", colors[i])
})

// BFS/DFS:
cy.elements().bfs({
  root: cy.getElementById("react"),
  visit(v) { console.log("Visited:", v.id()) },
})

Layouts

// Built-in layouts:
cy.layout({ name: "cose" }).run()          // Force-directed
cy.layout({ name: "circle" }).run()         // Circular
cy.layout({ name: "grid" }).run()           // Grid
cy.layout({ name: "breadthfirst" }).run()   // Hierarchical
cy.layout({ name: "concentric" }).run()     // Concentric circles
cy.layout({ name: "random" }).run()         // Random

// Force-directed with options:
cy.layout({
  name: "cose",
  idealEdgeLength: 100,
  nodeOverlap: 20,
  gravity: 80,
  numIter: 1000,
  animate: true,
}).run()

vis-network

vis-network — interactive networks:

Basic network

import { Network, DataSet } from "vis-network/standalone"

const nodes = new DataSet([
  { id: 1, label: "React", color: "#61dafb" },
  { id: 2, label: "Next.js", color: "#000000" },
  { id: 3, label: "Remix", color: "#121212" },
  { id: 4, label: "Vue", color: "#42b883" },
  { id: 5, label: "Nuxt", color: "#00dc82" },
])

const edges = new DataSet([
  { from: 1, to: 2, label: "powers" },
  { from: 1, to: 3, label: "powers" },
  { from: 4, to: 5, label: "powers" },
])

const container = document.getElementById("network")!
const network = new Network(container, { nodes, edges }, {
  physics: {
    forceAtlas2Based: {
      gravitationalConstant: -50,
      centralGravity: 0.01,
      springLength: 200,
    },
    solver: "forceAtlas2Based",
  },
  nodes: {
    shape: "dot",
    size: 25,
    font: { size: 14, color: "#ffffff" },
    borderWidth: 2,
  },
  edges: {
    width: 2,
    arrows: { to: { enabled: true, scaleFactor: 0.5 } },
    font: { size: 10, align: "middle" },
  },
})

Dynamic updates

// Add nodes/edges dynamically:
nodes.add({ id: 6, label: "Svelte", color: "#ff3e00" })
edges.add({ from: 6, to: 7, label: "powers" })

// Remove:
nodes.remove(6)

// Update:
nodes.update({ id: 1, label: "React 19", color: "#00d8ff" })

// Physics — stabilize then freeze:
network.once("stabilized", () => {
  network.setOptions({ physics: false })
})

Events

// Click events:
network.on("click", (params) => {
  if (params.nodes.length > 0) {
    const nodeId = params.nodes[0]
    console.log("Clicked node:", nodes.get(nodeId))
  }
})

// Hover:
network.on("hoverNode", (params) => {
  console.log("Hovering:", params.node)
})

// Drag:
network.on("dragEnd", (params) => {
  if (params.nodes.length > 0) {
    const pos = network.getPositions(params.nodes)
    console.log("New positions:", pos)
  }
})

// Double-click to edit:
network.on("doubleClick", (params) => {
  if (params.nodes.length > 0) {
    const node = nodes.get(params.nodes[0])
    const newLabel = prompt("New label:", node.label)
    if (newLabel) nodes.update({ id: node.id, label: newLabel })
  }
})

Clustering

// Cluster by property:
network.cluster({
  joinCondition: (nodeOptions) => nodeOptions.group === "frontend",
  clusterNodeProperties: {
    label: "Frontend",
    shape: "diamond",
    color: "#3b82f6",
  },
})

// Cluster by connection:
network.clusterByConnection(1)  // Cluster around node 1

// Open cluster on click:
network.on("selectNode", (params) => {
  if (network.isCluster(params.nodes[0])) {
    network.openCluster(params.nodes[0])
  }
})

Sigma.js

Sigma.js — WebGL graph renderer:

Basic graph

import Sigma from "sigma"
import Graph from "graphology"

const graph = new Graph()

// Add nodes:
graph.addNode("react", {
  label: "React",
  x: 0, y: 0,
  size: 20,
  color: "#61dafb",
})
graph.addNode("next", {
  label: "Next.js",
  x: 1, y: 1,
  size: 15,
  color: "#000000",
})
graph.addNode("vue", {
  label: "Vue",
  x: -1, y: 1,
  size: 15,
  color: "#42b883",
})

// Add edges:
graph.addEdge("react", "next", { size: 2, color: "#64748b" })
graph.addEdge("react", "vue", { size: 1, color: "#64748b" })

// Render:
const renderer = new Sigma(graph, document.getElementById("graph")!, {
  renderEdgeLabels: true,
})

Large graph (100K+ nodes)

import Sigma from "sigma"
import Graph from "graphology"
import forceAtlas2 from "graphology-layout-forceatlas2"

const graph = new Graph()

// Generate large graph:
for (let i = 0; i < 100000; i++) {
  graph.addNode(i, {
    label: `Node ${i}`,
    x: Math.random() * 1000,
    y: Math.random() * 1000,
    size: 2 + Math.random() * 5,
    color: `hsl(${Math.random() * 360}, 70%, 50%)`,
  })
}

// Add random edges:
for (let i = 0; i < 200000; i++) {
  const source = Math.floor(Math.random() * 100000)
  const target = Math.floor(Math.random() * 100000)
  if (source !== target && !graph.hasEdge(source, target)) {
    graph.addEdge(source, target, { size: 1 })
  }
}

// Apply force layout:
forceAtlas2.assign(graph, { iterations: 100 })

// Render — WebGL handles 100K+ nodes at 60fps:
const renderer = new Sigma(graph, container)

Search and highlight

import Sigma from "sigma"

const renderer = new Sigma(graph, container)

// Search nodes:
function searchNode(query: string) {
  const matching = graph.filterNodes((_, attrs) =>
    attrs.label.toLowerCase().includes(query.toLowerCase())
  )

  // Highlight matching:
  renderer.setSetting("nodeReducer", (node, data) => {
    if (matching.length > 0 && !matching.includes(node)) {
      return { ...data, color: "#333", label: "" }
    }
    return data
  })

  renderer.refresh()
}

Events

const renderer = new Sigma(graph, container)

// Click:
renderer.on("clickNode", ({ node }) => {
  console.log("Clicked:", graph.getNodeAttributes(node))
})

// Hover:
renderer.on("enterNode", ({ node }) => {
  // Highlight neighbors:
  const neighbors = new Set(graph.neighbors(node))
  renderer.setSetting("nodeReducer", (n, data) => {
    if (n === node || neighbors.has(n)) return data
    return { ...data, color: "#333", label: "" }
  })
  renderer.refresh()
})

renderer.on("leaveNode", () => {
  renderer.setSetting("nodeReducer", (_, data) => data)
  renderer.refresh()
})

Feature Comparison

FeatureCytoscape.jsvis-networkSigma.js
RendererCanvasCanvasWebGL
Max nodes (smooth)~5,000~5,000100,000+
Graph algorithms✅ (many)Via graphology
Force layout✅ (physics)Via graphology
Built-in drag/drop
ClusteringVia graphology
Edge labels
Node shapes✅ (many)✅ (many)Circle/custom
Animations✅ (physics)
React bindingsVia refVia ref@react-sigma
TypeScript
Used byBioinformaticsDashboardsLarge networks
Weekly downloads~500K~200K~50K

When to Use Each

Use Cytoscape.js if:

  • Need graph algorithms (shortest path, centrality, BFS/DFS)
  • Building bioinformatics or scientific network analysis
  • Need multiple layout algorithms
  • Want the richest styling and customization

Use vis-network if:

  • Building interactive network diagrams
  • Need physics-based layout simulation
  • Want drag-and-drop node editing
  • Need clustering and grouping features

Use Sigma.js if:

  • Rendering large graphs (10K–100K+ nodes)
  • Need WebGL performance for complex networks
  • Using graphology for graph algorithms
  • Building social network or knowledge graph visualizations

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on Cytoscape.js v3.x, vis-network v9.x, and Sigma.js v3.x.

Compare graph visualization and data libraries on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.