D3.js Advanced — Geographies, Force Layout & Hierarchies Explained
Advanced D3.js techniques let you build geographic maps, network visualizations, hierarchical layouts, and complex interactive graphics.
What You’ll Learn
By the end of this tutorial, you will be able to:
- Render geographic maps using GeoJSON data and projections
- Create choropleth maps that color regions by data values
- Build force-directed network graphs with draggable nodes
- Understand hierarchical layouts: treemap, pack, partition, and tree
- Configure force simulation parameters for realistic physics
Why Advanced D3.js Matters
Simple bar charts and line charts handle most business data. But when Durga Antivirus Pro needs to visualize a malware infection spreading across a network, it needs a force-directed graph. When DodaZIP analyzes compression ratios across file type hierarchies, a treemap shows the proportions at a glance. When a security dashboard displays threat origins on a world map, geographic projections turn coordinates into an interactive visualization. These advanced techniques differentiate basic data presentation from professional data storytelling.
Learning Path
flowchart LR
A["D3.js Basics"] --> B["D3.js SVG"]
B --> C["D3.js Scales & Axes"]
C --> D["D3.js Charts"]
D --> E["D3.js Interactions"]
D --> F["D3.js Data Handling"]
E --> G["D3.js Advanced"]
F --> G
G --> H["D3.js Reference"]
G:::current
classDef current fill:#4CAF50,color:#fff,stroke:#333,stroke-width:2px
Geographies (Maps)
D3 renders maps using GeoJSON or TopoJSON data with geographic projections that convert latitude/longitude to pixel coordinates.
Understanding Geographic Data
- GeoJSON: Standard format storing geography as coordinates (lat/lng). Each feature has a
geometry(point, line, or polygon) andproperties(data). - TopoJSON: An extension of GeoJSON that stores geometry as shared arcs, reducing file size significantly (often 80% smaller). Must be converted to GeoJSON for rendering.
Projections — Flattening the Globe
A projection maps 3D spherical coordinates to 2D screen pixels. Different projections serve different purposes:
// Common projections
d3.geoMercator() // Standard web map — familiar but distorts polar areas
d3.geoAlbersUsa() // USA-focused — composite projection
d3.geoEquirectangular() // Simple lat/lng grid — straightforward
d3.geoOrthographic() // 3D globe effect — visually impressive
d3.geoNaturalEarth1() // Aesthetic world map — good balance
Why projections matter: Without a projection, your map would use raw lat/lng as pixel coordinates — a 180° wide image that’s heavily distorted. Projections correct the distortion and center the map.
Drawing a World Map
<!DOCTYPE html>
<html>
<head>
<title>World Map</title>
</head>
<body>
<svg width="800" height="500" id="map"></svg>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3"></script>
<script>
var width = 800, height = 500;
var svg = d3.select("#map");
// Step 1: Create a projection (Mercator, scaled and centered)
var projection = d3.geoMercator()
.scale(130)
.translate([width / 2, height / 1.4]);
// Step 2: Create a path generator with the projection
var path = d3.geoPath().projection(projection);
// Step 3: Load TopoJSON world data
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json")
.then(function(world) {
// Convert TopoJSON to GeoJSON features
var countries = topojson.feature(world, world.objects.countries).features;
// Step 4: Draw each country as a path
svg.selectAll("path")
.data(countries)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "#69b3a2")
.attr("stroke", "#fff")
.attr("stroke-width", 0.5);
});
</script>
</body>
</html>Line by line:
d3.geoMercator()creates the projection function..scale(130)sets zoom level (higher = more zoomed in)..translate([width/2, height/1.4])centers the map.d3.geoPath().projection(projection)creates a function that converts GeoJSON to SVG path strings.topojson.feature(world, world.objects.countries).featuresconverts TopoJSON to GeoJSON features.
Choropleth Map — Color by Data Value
// data is a Map: country ID → value
var color = d3.scaleSequential()
.domain([0, d3.max(data.values())])
.interpolator(d3.interpolateBlues);
svg.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", function(d) {
return color(data.get(d.id) || 0); // color by value, gray if no data
})
.attr("stroke", "#fff");Real-world use: A security dashboard showing threat levels by country. Countries with more detected threats get darker colors, helping analysts spot outbreak regions instantly.
Force-Directed Graph — Network Visualization
Force layouts simulate physical forces — nodes repel each other (like magnets), edges attract connected nodes (like springs), and a centering force keeps everything on screen.
var nodes = [
{ id: "Alice" }, { id: "Bob" }, { id: "Charlie" },
{ id: "Diana" }, { id: "Eve" }
];
var links = [
{ source: "Alice", target: "Bob" },
{ source: "Bob", target: "Charlie" },
{ source: "Charlie", target: "Diana" },
{ source: "Diana", target: "Alice" },
{ source: "Eve", target: "Bob" }
];
// Create the simulation — it runs continuously!
var simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(300, 200))
.on("tick", ticked);How the simulation works:
- Initialization: Nodes are placed randomly.
- Tick loop: On every “tick” (frame), forces are computed and positions updated.
- Convergence: Over time, the system settles into a stable layout.
- Tick handler: You update the DOM on every tick to reflect new positions.
The Tick Function
function ticked() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}Force Configuration Reference
d3.forceSimulation(nodes)
// Link force: how strongly edges pull nodes together
.force("link", d3.forceLink(links).distance(50).strength(0.5))
// Many-body force: repulsion (negative) or attraction (positive)
.force("charge", d3.forceManyBody().strength(-200))
// Centering force: pulls all nodes toward center
.force("center", d3.forceCenter(width / 2, height / 2))
// Collision: prevents overlap
.force("collide", d3.forceCollide().radius(20))
// Position forces: pin nodes to x/y lines
.force("x", d3.forceX().strength(0.1))
.force("y", d3.forceY().strength(0.1));Making Nodes Draggable
node.call(d3.drag()
.on("start", function(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x; // fix node position
d.fy = d.y;
})
.on("drag", function(event, d) {
d.fx = event.x;
d.fy = event.y;
})
.on("end", function(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null; // release node
d.fy = null;
})
);Why fx/fy? Setting d.fx = value fixes the node’s x position — the simulation won’t move it. Setting to null releases it.
Hierarchical Layouts
Hierarchical (tree-structured) data powers treemaps, circle packing, and sunburst diagrams.
Data Format
Hierarchical data uses nested objects with a children array:
var data = {
name: "Root",
children: [
{ name: "A", value: 10 },
{ name: "B", children: [
{ name: "B1", value: 5 },
{ name: "B2", value: 8 }
]}
]
};Creating a Hierarchy
var root = d3.hierarchy(data)
.sum(function(d) { return d.value; }); // compute total value for each node
.sum() walks the tree and computes a value for each node by adding its own value to its children’s values. Required for size-based layouts (treemap, pack).
Treemap — Rectangular Subdivisions
Treemaps show proportions by dividing a rectangle into smaller rectangles:
// Compute treemap layout
d3.treemap()
.size([width, height])
.padding(2)(root);
// Draw leaves as rectangles
svg.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("fill", function(d) { return color(d.parent.data.name); })
.attr("stroke", "#fff");
// Add labels
svg.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", function(d) { return d.x0 + 3; })
.attr("y", function(d) { return d.y0 + 13; })
.text(function(d) { return d.data.name; })
.attr("font-size", "11px")
.attr("fill", "white");Real-world use: DodaZIP could use a treemap showing file sizes by type — large video files dominate the rectangle, tiny config files barely register. At a glance you see where storage is going.
Pack Layout (Circle Packing)
Nested circles — each parent circle contains its children:
d3.pack()
.size([width, height])
.padding(5)(root);
svg.selectAll("circle")
.data(root.descendants())
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; })
.attr("fill", function(d) {
return d.children ? "lightgray" : "steelblue";
})
.attr("stroke", "#999")
.attr("fill-opacity", 0.6);Tree Layout
Standard node-link tree diagram:
d3.tree()
.size([height, width - 200])(root);
// Draw links
svg.selectAll("line")
.data(root.links())
.enter()
.append("line")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.depth * 80; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.depth * 80; })
.attr("stroke", "#ccc");
// Draw nodes
svg.selectAll("circle")
.data(root.descendants())
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.depth * 80; })
.attr("r", 4)
.attr("fill", "steelblue");Choosing the Right Layout
| Layout | Best For | Analogy |
|---|---|---|
| Treemap | Showing proportions within a whole | A chocolate bar broken into pieces |
| Pack | Showing containment relationships | Russian nesting dolls |
| Tree | Showing hierarchical structure | A family tree |
| Partition | Showing relative proportions at each level | A stacked bar chart in hierarchy form |
| Cluster | Dendrograms, evolutionary trees | Same as tree but leaves align |
Chord Diagram — Flow Between Groups
Shows relationships between entities as arcs and ribbons:
var matrix = [
[0, 10, 20],
[10, 0, 15],
[20, 15, 0]
];
var names = ["A", "B", "C"];
var chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending)(matrix);
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var ribbon = d3.ribbon()
.radius(innerRadius);
// Draw group arcs (outer rings)
svg.selectAll("g.group")
.data(chord.groups)
.enter().append("path")
.attr("d", arc)
.attr("fill", function(d) { return color(names[d.index]); })
.attr("stroke", "#000");
// Draw ribbons (connections between groups)
svg.selectAll("path.ribbon")
.data(chord)
.enter().append("path")
.attr("d", ribbon)
.attr("fill", function(d) { return color(names[d.source.index]); })
.attr("opacity", 0.7);Common Mistakes
1. GeoJSON/TopoJSON Not Loading Due to CORS
Loading map data from external URLs requires CORS support. If you get errors in Doda Browser, use reliable CDNs or serve files from your own domain.
2. Force Simulation Not Starting
Call simulation.restart() after adding or modifying nodes. The simulation won’t automatically run if .alpha() has decayed to 0.
3. Forgetting .sum() on Hierarchy Data
d3.treemap() and d3.pack() need node values to determine sizes. If you skip .sum(), all values default to 0 and nothing renders.
4. Incorrect Projection Parameters
Geographic projections need proper .scale() and .translate() for the given map extent. Default values rarely fit the visualization area. Use .fitSize([w, h], geojson) to auto-fit.
5. Force Layout Nodes Overlapping
Without d3.forceCollide(), nodes may overlap. Always add a collision force with an appropriate radius.
6. Not Updating Force Links on Data Change
When adding/removing nodes, you must update both the nodes array and the links:
simulation.nodes(newNodes);
simulation.force("link").links(newLinks);
simulation.alpha(1).restart();Practice Questions
Question 1
What is the difference between GeoJSON and TopoJSON?
Answer: GeoJSON stores geometry as explicit coordinates. TopoJSON stores geometry as shared arcs (edges between points), which eliminates duplicate coordinates between adjacent features. TopoJSON files are typically 80% smaller and must be converted via topojson.feature() for D3 rendering.
Question 2
How do you update a force simulation when data changes?
Answer: Call simulation.nodes(newNodes) and simulation.force("link").links(newLinks), then simulation.alpha(1).restart() to reset the energy and restart the simulation.
Question 3
What does d3.hierarchy().sum() do?
Answer: It walks the tree and computes a value for each node by summing its own value (if any) with the values of its descendants. Size-based layouts (treemap, pack) use these values to determine proportions.
Question 4
Which projection is best for a world map?
Answer: d3.geoNaturalEarth1() is aesthetically pleasing and shows minimal distortion. d3.geoMercator() is familiar (Google Maps style) but distorts polar areas. d3.geoEquirectangular() is simple and fast.
Question 5
What is d.fx and d.fy in a force simulation?
Answer: They fix a node’s position — when set, the simulation won’t change that node’s x/y. Setting them to null releases the node. Used in drag handlers to let users pin and move nodes.
Challenge
Build a force-directed graph with 15 nodes and 20 links. Add hover highlighting: when hovering over a node, highlight its connected edges and dim everything else. Add a “reset” button that re-centers the graph.
FAQ
Try It Yourself
Here’s a complete interactive network graph with drag, hover highlighting, and physics:
<!DOCTYPE html>
<html>
<head>
<title>Network Graph — Try It Yourself</title>
<style>
body { font-family: sans-serif; padding: 20px; }
svg { border: 1px solid #ddd; }
.node { fill: #69b3a2; stroke: #fff; stroke-width: 2; cursor: pointer; }
.node:hover { fill: #e91e63; }
.link { stroke: #999; stroke-opacity: 0.6; }
.label { font-size: 11px; fill: #333; }
</style>
</head>
<body>
<h2>Social Network Graph</h2>
<p>Drag nodes to rearrange. Hover to highlight connections.</p>
<svg width="600" height="400" id="graph"></svg>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
var svg = d3.select("#graph"), w = 600, h = 400;
var data = {
nodes: [
{ id: "Alice" }, { id: "Bob" }, { id: "Charlie" },
{ id: "Diana" }, { id: "Eve" }, { id: "Frank" },
{ id: "Grace" }, { id: "Hank" }
],
links: [
{ source: "Alice", target: "Bob" },
{ source: "Alice", target: "Charlie" },
{ source: "Alice", target: "Diana" },
{ source: "Bob", target: "Charlie" },
{ source: "Bob", target: "Eve" },
{ source: "Charlie", target: "Frank" },
{ source: "Diana", target: "Grace" },
{ source: "Diana", target: "Hank" },
{ source: "Eve", target: "Frank" },
{ source: "Grace", target: "Hank" }
]
};
var link = svg.append("g").selectAll("line").data(data.links)
.enter().append("line").attr("class", "link");
var node = svg.append("g").selectAll("circle").data(data.nodes)
.enter().append("circle").attr("class", "node").attr("r", 8)
.call(d3.drag()
.on("start", function(e, d) {
if (!e.active) sim.alphaTarget(0.3).restart();
d.fx = d.x; d.fy = d.y;
})
.on("drag", function(e, d) { d.fx = e.x; d.fy = e.y; })
.on("end", function(e, d) {
if (!e.active) sim.alphaTarget(0);
d.fx = null; d.fy = null;
})
);
var labels = svg.append("g").selectAll("text").data(data.nodes)
.enter().append("text").attr("class", "label")
.attr("dx", 12).attr("dy", 4)
.text(function(d) { return d.id; });
node.on("mouseover", function(e, d) {
var connected = new Set();
data.links.forEach(function(l) {
if (l.source.id === d.id) connected.add(l.target.id);
if (l.target.id === d.id) connected.add(l.source.id);
});
node.attr("opacity", function(n) {
return n.id === d.id || connected.has(n.id) ? 1 : 0.2;
});
link.attr("opacity", function(l) {
return l.source.id === d.id || l.target.id === d.id ? 1 : 0.1;
});
}).on("mouseout", function() {
node.attr("opacity", 1);
link.attr("opacity", 0.6);
});
var sim = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links).id(function(d) { return d.id; }).distance(80))
.force("charge", d3.forceManyBody().strength(-150))
.force("center", d3.forceCenter(w/2, h/2))
.force("collide", d3.forceCollide().radius(20))
.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
</script>
</body>
</html>Try this: Drag a node and watch the physics rebalance. Hover over Alice to see her connections highlighted. Add more nodes to the array and see the layout adapt.
What’s Next
You’ve now covered the full spectrum of D3.js capabilities:
| Tutorial | What You’ll Learn |
|---|---|
| D3.js Reference | Complete API cheatsheet for daily development |
Related topics: JavaScript, SVG, Data Visualization, Node.js, React
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — bringing secure, high-performance software to your digital life.
What’s Next
Congratulations on completing this D3Js Advanced tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro