Frontend Development 22 min read

Building a Resource Topology Diagram with d3.js

This article explains how to use d3.js to create interactive resource topology diagrams, covering SVG basics, d3 selections, data binding, enter/update/exit patterns, force‑directed layout, path calculations, and various optimizations such as text centering, arrow markers, and intersection handling.

NetEase Game Operations Platform
NetEase Game Operations Platform
NetEase Game Operations Platform
Building a Resource Topology Diagram with d3.js

Background

As business resources grow, visualizing the global relationships between assets becomes essential. A resource topology graph provides a clear way to view these connections. This article simplifies the business context and focuses on using d3.js to draw such a graph.

Why Choose d3?

d3.js (Data‑Driven Documents) is a front‑end JavaScript library for data visualization. Unlike chart‑oriented libraries such as highcharts or echarts , d3 excels at custom visualizations like topology graphs. The article also compares d3 with go.js and AntV G6 , highlighting d3’s high extensibility and free license (using the latest v5).

SVG Overview

SVG (Scalable Vector Graphics) is an XML‑based language for describing two‑dimensional vector graphics. All SVG elements must be nested inside an <svg></svg> container, which acts as a canvas with its own coordinate system.

Common SVG Elements

circle – attributes: cx , cy , r

<svg width="500px" height="500px">
    <circle cx="60" cy="60" r="30"></circle>
</svg>

line – attributes: x1 , y1 , x2 , y2

<svg width="500px" height="500px">
    <line x1="10" y1="10" x2="200" y2="200"></line>
</svg>

text – attributes: x , y , plus styling via stroke (line color) and fill (text color).

path – a versatile element defined by the d attribute, which contains a series of drawing commands.

Getting Started with d3

d3’s API resembles jQuery: d3.select and d3.selectAll return a selection object that wraps DOM nodes. Data binding is performed with selection.data , and the three core phases are enter , update , and exit .

d3.select('svg')
    .append('circle')
    .attr('cx', 60)
    .attr('cy', 60)
    .attr('r', 30)
    .append('text')
    .text('资源节点');

Using selectAll together with data allows batch creation of elements:

const circleData = [
    {cx: 30, cy: 30, r: 30, nodeName: '节点1'},
    {cx: 30, cy: 100, r: 30, nodeName: '节点2'}
];

d3.select('svg')
    .selectAll('circle')
    .data(circleData)
    .enter()
    .append('circle')
    .attr('cx', d => d.cx)
    .attr('cy', d => d.cy)
    .attr('r', d => d.r)
    .append('text')
    .text(d => d.nodeName);

The enter phase creates missing elements, exit removes surplus elements, and update modifies existing ones. The newer selection.join method merges these steps:

d3.select('svg')
    .selectAll('circle')
    .data(circleData)
    .join(
        enter => enter.append('circle')
            .attr('cx', d => d.cx)
            .attr('cy', d => d.cy)
            .append('text')
            .text(d => d.nodeName),
        update => update,
        exit => exit.remove()
    )
    .attr('r', d => d.r);

Force‑Directed Layout

For complex graphs, manually positioning nodes is impractical. d3 provides a force‑directed simulation that applies five forces: centering, collision, link (spring), many‑body (charge), and positioning.

const simulation = d3.forceSimulation(nodes)
    .force('link', d3.forceLink(links))
    .force('collision', d3.forceCollide().radius(RADIUS * 2))
    .force('center', d3.forceCenter(containerWidth / 2, containerHeight / 2))
    .stop();

After stopping the automatic ticking, the layout is manually advanced to a stable state:

for (let i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; i++) {
    simulation.tick();
}

Drawing the Topology

With node coordinates computed, the graph is rendered:

const svg = d3.select('#svg');

let nodeSelection = svg.selectAll('circle')
    .data(nodes)
    .join('circle')
    .attr('cx', d => d.x)
    .attr('cy', d => d.y)
    .attr('r', d => d.radius)
    .attr('stroke', d => d.strokeColor)
    .attr('fill', d => d.fillColor);

let textSelection = svg.selectAll('text')
    .data(nodes)
    .join('text')
    .attr('x', d => d.x)
    .attr('y', d => d.y)
    .attr('stroke', d => d.strokeColor)
    .attr('fill', d => d.fillColor)
    .text(d => d.text);

let linkSelection = svg.selectAll('path')
    .data(links)
    .join('path')
    .attr('stroke-width', d => d.strokeWidth)
    .attr('stroke', d => d.strokeColor)
    .attr('d', d => `M ${d.source.x} ${d.source.y} L ${d.target.x} ${d.target.y}`);

Optimizations

Text centering – Instead of SVG text , HTML div elements with flexbox can be used for perfect centering.

.text {
    position: 'absolute';
    display: 'flex';
    align-items: 'center';
    justify-content: 'center';
}

Arrow markers – Adding a marker-end attribute to paths draws directional arrows.

d3.select('body')
    .append('svg')
    .append('marker')
    .attr('id', 'arrow')
    .attr('viewBox', '-10 -10 20 20')
    .attr('markerWidth', '20')
    .attr('markerHeight', '20')
    .attr('orient', 'auto')
    .append('path')
    .attr('d', 'M-4,-3 L 0,0 L -4, 3')
    .attr('fill', '#333');

linkSelection.attr('marker-end', 'url(#arrow)');

Intersection handling – To avoid arrows being covered by node circles, the line is trimmed to the points where it intersects the source and target circles using the kld‑intersections library.

import { Intersection, ShapeInfo } from 'kld-intersections';

linkSelection.attr('d', d => {
    const { source, target } = d;
    const sourceCircle = ShapeInfo.circle({ center: [source.x, source.y], radius: source.r });
    const targetCircle = ShapeInfo.circle({ center: [target.x, target.y], radius: target.r });
    const path = ShapeInfo.path(`M ${source.x} ${source.y} L ${target.x} ${target.y}`);
    const sourceIntersection = Intersection.intersect(sourceCircle, path).points[0];
    const targetIntersection = Intersection.intersect(targetCircle, path).points[0];
    return `M ${sourceIntersection.x} ${sourceIntersection.y} L ${targetIntersection.x} ${targetIntersection.y}`;
});

Conclusion

d3.js is a powerful, data‑driven visualization library. The main challenges lie in assembling and maintaining the underlying data. This tutorial provides a foundation for building resource topology graphs; further enhancements such as drag‑and‑drop, zoom, or Web‑Worker‑based layout can be added as needed.

References

SVG Element Reference – https://g.126.fm/0440kH6

d3.js API – https://g.126.fm/0309ow8

d3.js Force‑Directed Graph – https://g.126.fm/01xjKTu

kld‑intersections – https://g.126.fm/019trY4

Web Worker for Force Layout – https://g.126.fm/02cbZCM

Using d3.js for Resource Topology – https://g.126.fm/00SFXeo

frontendJavaScriptSVGData Visualizationd3.jsForce-Directed Graph
NetEase Game Operations Platform
Written by

NetEase Game Operations Platform

The NetEase Game Automated Operations Platform delivers stable services for thousands of NetEase titles, focusing on efficient ops workflows, intelligent monitoring, and virtualization.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.