Development of Network Graph Visualization on a Website
Note: when the number of dependencies in a microservice architecture exceeds a hundred, standard lists stop working. Visualization of relationship graphs (Network Graph) is the only way to see which services depend on an outdated database or where cycles form. In practice, a microservice graph often contains 10,000+ nodes — without force-directed layout and WebGL, the render would hang the browser. The problem worsens when data is dynamically updated: each API request rebuilds the graph, and without optimization, interactivity becomes impossible. On one client project, we reduced graph load time from 12 to 2 seconds through node virtualization and LOD rendering, saving the client 200,000 rubles per month in error detection. Without such approaches, the graph remains unreadable and useless for analysis.
Choosing the right layout algorithm is a critical decision that determines UX. Force-directed layout provides natural clustering, allowing quick identification of isolated nodes and dense groups. As a result, the team can quickly find circular dependencies and prevent cascading failures.
Why force-directed layout is the foundation of Network Graph?
Force-directed layout physically simulates nodes: they repel each other, and links attract. The result is a natural arrangement where connected nodes are close and unconnected ones are far apart. This is the key to readability. Without such an algorithm, the graph turns into chaotic spaghetti.
Which library to choose for graph visualization?
| Library | Performance (nodes) | Complexity | When to choose |
|---|---|---|---|
| D3 Force (recommended) | up to 10k | High | Full control needed, custom renders |
| react-force-graph | up to 5k (2D/3D) | Medium | React project, quick start |
| Sigma.js | 50k+ | Medium | Large graphs with WebGL |
| Cytoscape.js | up to 10k | Low | Biological/medical data |
D3 Force vs Sigma.js: comparative analysis
Sigma.js processes 50k nodes 5 times faster than D3 Force due to WebGL. For projects with >10k nodes, Sigma.js is the optimal choice, although D3 Force offers more flexibility in customization. Sigma.js official documentation confirms that the library renders up to 100k nodes on an ordinary laptop.
Comparison of 2D and 3D rendering
| Parameter | 2D | 3D |
|---|---|---|
| Performance | Higher (fewer calculations) | Lower (Z-coordinate) |
| Intuitiveness | Familiar view | Requires orbit control |
| Application | Service dependencies, social networks | Geodata, molecular structures |
The choice between 2D and 3D depends on the task. For service dependency visualization, 2D is usually sufficient. 3D is needed for geodata or molecular structures. The visualization method is determined by the data type and interactivity requirements.
How we do it: stack and case study
For visualizing microservice dependencies, we use a JS graph based on react-force-graph. We prefer react-force-graph — it provides a ready React component with WebGL acceleration. For a large project from our practice (5000+ nodes), we used Sigma.js: raw data loaded via graphology, layout configured via force-atlas2.
In practice, we often use a hybrid approach: at the exploration stage, we build a prototype on D3 Force, then transfer to Sigma.js or react-force-graph for production. This allows quick hypothesis validation without deep optimization. For a fintech client, we developed a transaction monitoring graph where each node is an account and links are transfers. The graph updated in real time and withstood up to 1000 changes per second. Another client reduced dependency search time from 4 hours to 15 minutes, saving over 150,000 rubles monthly. If you want to assess whether your stack is suitable for Network Graph, contact us for a free audit.
Example of a basic graph with react-force-graph
npm install react-force-graph-2d
import ForceGraph2D from 'react-force-graph-2d';
import { useCallback, useRef, useState } from 'react';
interface Node {
id: string;
name: string;
type: 'service' | 'database' | 'external';
group: number;
}
interface Link {
source: string;
target: string;
label?: string;
weight?: number;
}
function ServiceDependencyGraph({ nodes, links }) {
const graphRef = useRef();
const [selectedNode, setSelectedNode] = useState(null);
const [hoveredNode, setHoveredNode] = useState(null);
// Highlight links of selected node
const highlightedLinks = useMemo(() => {
if (!selectedNode) return new Set();
return new Set(
links.filter(l => l.source === selectedNode.id || l.target === selectedNode.id)
.map(l => `${l.source}-${l.target}`)
);
}, [selectedNode, links]);
const nodeColor = useCallback((node) => {
const colors = { service: '#3b82f6', database: '#22c55e', external: '#f59e0b' };
if (selectedNode && node.id !== selectedNode.id) {
const isRelated = links.some(l =>
(l.source === selectedNode.id && l.target === node.id) ||
(l.target === selectedNode.id && l.source === node.id)
);
return isRelated ? colors[node.type] : '#d1d5db';
}
return colors[node.type] ?? '#6b7280';
}, [selectedNode, links]);
const nodeCanvasObject = useCallback((node, ctx, globalScale) => {
const label = node.name;
const fontSize = 12 / globalScale;
const r = 6;
// Node
ctx.beginPath();
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI);
ctx.fillStyle = nodeColor(node);
ctx.fill();
if (selectedNode?.id === node.id) {
ctx.strokeStyle = '#1d4ed8';
ctx.lineWidth = 2 / globalScale;
ctx.stroke();
}
// Label
ctx.font = `${fontSize}px Sans-Serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#374151';
ctx.fillText(label, node.x, node.y + r + fontSize);
}, [nodeColor, selectedNode]);
const linkColor = useCallback((link) => {
const key = `${link.source.id ?? link.source}-${link.target.id ?? link.target}`;
return highlightedLinks.has(key) ? '#3b82f6' : '#e5e7eb';
}, [highlightedLinks]);
return (
<div className="relative border border-gray-200 rounded-xl overflow-hidden" style={{ height: 600 }}>
<ForceGraph2D
ref={graphRef}
graphData={{ nodes, links }}
nodeId="id"
nodeLabel="name"
nodeCanvasObject={nodeCanvasObject}
nodeCanvasObjectMode={() => 'replace'}
linkColor={linkColor}
linkWidth={link => (link.weight ?? 1) * 0.5}
linkDirectionalArrowLength={3}
linkDirectionalArrowRelPos={1}
linkLabel="label"
onNodeClick={(node) => {
setSelectedNode(prev => prev?.id === node.id ? null : node);
}}
onNodeHover={setHoveredNode}
onBackgroundClick={() => setSelectedNode(null)}
d3AlphaDecay={0.02}
d3VelocityDecay={0.3}
warmupTicks={50}
cooldownTicks={200}
/>
{/* Info panel */}
{selectedNode && (
<div className="absolute top-4 right-4 bg-white border rounded-lg shadow-lg p-4 w-64">
<h3 className="font-semibold text-gray-800">{selectedNode.name}</h3>
<p className="text-sm text-gray-500 capitalize">{selectedNode.type}</p>
<div className="mt-3">
<p className="text-xs text-gray-500">Incoming links:</p>
<ul className="text-sm">
{links.filter(l => (l.target.id ?? l.target) === selectedNode.id)
.map(l => <li key={l.source.id ?? l.source}>← {l.source.name ?? l.source}</li>)}
</ul>
</div>
</div>
)}
{/* Legend */}
<div className="absolute bottom-4 left-4 bg-white/90 rounded-lg p-3 text-xs">
<div className="flex items-center gap-2 mb-1">
<div className="w-3 h-3 rounded-full bg-blue-500" /> Service
</div>
<div className="flex items-center gap-2 mb-1">
<div className="w-3 h-3 rounded-full bg-green-500" /> Database
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-yellow-500" /> External API
</div>
</div>
</div>
);
}
3D graph
import ForceGraph3D from 'react-force-graph-3d';
function Graph3D({ data }) {
return (
<ForceGraph3D
graphData={data}
nodeAutoColorBy="group"
nodeLabel="name"
linkDirectionalParticles={2}
linkDirectionalParticleSpeed={0.006}
backgroundColor="#0f172a"
/>
);
}
Large graphs with Sigma.js
npm install sigma graphology @react-sigma/core
Sigma.js renders via WebGL — handles 50k+ nodes. Use Sigma.js if maximum performance is needed.
import { SigmaContainer, useLoadGraph, useRegisterEvents } from '@react-sigma/core';
import Graph from 'graphology';
function LargeGraph({ graphData }) {
return (
<SigmaContainer style={{ height: 600 }} settings={{ nodeProgramClasses: {} }}>
<GraphLoader data={graphData} />
</SigmaContainer>
);
}
Process of work
- Analytics — study data sources for graph visualization, clarify interactivity requirements (zoom, highlight, filters).
- Design — choose layout (force-directed, circular, hierarchical), define color scheme and sizes.
- Development — implement component, optimize rendering (node virtualization, LOD).
- Testing — check on real data, measure FPS, fix bugs.
- Deployment — embed into the site, configure CDN for heavy assets.
Deadlines and cost
Deadlines: from 1.5 weeks (basic graph) to 1 month (comprehensive solution with Sigma.js + 3D). Cost is calculated individually after data audit. Basic graph development starts from 150,000 rubles ($1,800). Typical project budget: from $2,000 to $10,000 depending on complexity and data volume. As practice shows, the graph pays for itself within a month of implementation by reducing error search time. For example, one client reduced dependency search time from 4 hours to 15 minutes, saving over 150,000 rubles per month. We guarantee stable operation on mobile devices and older browsers. Order an audit — we will determine the optimal solution for your data.
What is included in the work
- Adaptation of the graph to your stack (React, Vue, Angular)
- Source code as an npm package or component
- Documentation: API reference and customization guide
- Access: source code repository (GitHub)
- Training: 1-hour online session for your team
- Support: 2 weeks of technical support including bug fixes and consultation
- Test integration with your data
Typical mistakes when developing a Network Graph
- Ignoring performance: without WebGL, the graph lags at 1000 nodes.
- No event throttling: each hover triggers a repaint.
- Too small click target areas — user cannot select a node.
We have many years of experience in data visualization — we have implemented over 50 projects, including graphs for banks and logistics systems. Order a graph development tailored to your data, or get a consultation from an engineer. Optimization via WebGL and clustering allows rendering graphs with 100,000+ nodes on an ordinary laptop. Check if your current stack is suitable for Network Graph by ordering a free audit. Contact us to discuss your project.







