d3-force-simulation
D3.js force simulation for creating interactive bubble charts with clustering, collision detection, and physics-based positioning
- License 许可
- MIT
- 最近更新
- 2026-10-02
- Stars
- ★ 92
- Forks
- 7
01 前言 Front Matter
- name
- d3-force-simulation
- description
- D3.js force simulation for creating interactive bubble charts with clustering, collision detection, and physics-based positioning
03 SKILL.md 正文
D3.js Force Simulation
Overview
Force simulation in D3 uses physics-based algorithms to position nodes (bubbles) based on forces. Perfect for creating bubble charts with natural-looking clusters.
Core Concepts
1. Creating a Simulation
const simulation = d3.forceSimulation(nodes)
.force("name", forceFunction)
.on("tick", updatePositions);
// Start simulation (runs in background)
simulation.alpha(1); // reset energy
2. Common Forces
forceX / forceY
Position bubbles toward target x/y coordinates (creates clustering):
// Group by sector - each sector gets target x position
const sectors = Array.from(new Set(data.map(d => d.sector)));
const sectorX = d3.scalePoint()
.domain(sectors)
.range([0, width]);
const simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX()
.x(d => sectorX(d.sector)) // each sector pulled to its x position
.strength(0.05) // weak force (allows spreading)
)
.force("y", d3.forceY()
.y(height / 2) // all centered vertically
.strength(0.03)
);
forceCollide
Prevents bubbles from overlapping:
.force("collide", d3.forceCollide()
.radius(d => radiusScale(d.marketCap) + 2) // add padding
.strength(0.5) // collision strength (0-1)
)
forceManyBody
Repulsive or attractive force between all nodes:
.force("charge", d3.forceManyBody()
.strength(-50) // negative = repulsion, positive = attraction
)
3. Tick Events
Update positions on each simulation frame:
simulation.on("tick", () => {
// Update circles
circles
.attr("cx", d => d.x)
.attr("cy", d => d.y);
// Update text
labels
.attr("x", d => d.x)
.attr("y", d => d.y);
});
4. Preventing Bubbles from Moving Off-Screen
Fix nodes to boundaries:
simulation.on("tick", () => {
nodes.forEach(d => {
// Clamp positions within bounds
d.x = Math.max(d.radius, Math.min(width - d.radius, d.x));
d.y = Math.max(d.radius, Math.min(height - d.radius, d.y));
});
// Update positions
circles.attr("cx", d => d.x).attr("cy", d => d.y);
});
5. Interactive Dragging
Allow users to drag nodes:
function drag(simulation) {
return d3.drag()
.on("start", (event, d) => {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", (event, d) => {
d.fx = event.x;
d.fy = event.y;
})
.on("end", (event, d) => {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
});
}
circles.call(drag(simulation));
Complete Example: Bubble Cluster Chart
// Setup
const nodes = data.map(d => ({
id: d.ticker,
sector: d.sector,
value: d.marketCap,
radius: Math.sqrt(d.marketCap / Math.PI)
}));
const width = 800, height = 600;
// Scales
const sectorX = d3.scalePoint()
.domain(Array.from(new Set(data.map(d => d.sector))))
.range([100, width - 100]);
const radiusScale = d3.scaleSqrt()
.domain([0, d3.max(nodes, d => d.value)])
.range([5, 50]);
const colorScale = d3.scaleOrdinal()
.domain(Array.from(new Set(data.map(d => d.sector))))
.range(d3.schemeCategory10);
// Simulation
const simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX().x(d => sectorX(d.sector)).strength(0.05))
.force("y", d3.forceY().y(height / 2).strength(0.03))
.force("collide", d3.forceCollide().radius(d => radiusScale(d.value) + 2))
.force("charge", d3.forceManyBody().strength(-30))
.on("tick", () => {
circles
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
// Render
const svg = d3.select("body").append("svg")
.attr("width", width).attr("height", height);
const circles = svg.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", d => radiusScale(d.value))
.attr("fill", d => colorScale(d.sector));
Performance Tips
- Use
simulation.alphaMin(0.001)to stop simulation faster - Reduce
strengthvalues for smoother settling - Use
radiusin forceCollide matching actual visual radius - Call
simulation.stop()when not needed to save CPU
04 同仓库更多 Skills
object_counter
Count occurrences of an object in the image using computer vision algorithm.
travel-planning-workflow
Invoke this skill when performing travel planning. It defines a structured workflow for generating itineraries. Following this workflow is essential to ensure the final plan satisfies user requirements.
bundled-dataset-search-tool
invoke this skill when you need to perform database search for travel planning. This skill provides some useful pre-packaged tools to look up accommodations, attractions, cities, driving distance, flights, and restaurants from the bundled dataset.
glm-output
Output-processing guidance for GLM tasks. Especially useful after glm-basics and glm-calibration when you need verifier-matching metrics from output.nc and a final /root/metrics.json.
glm-calibration
Calibration guidance for GLM tasks. Often most effective after glm-basics has clarified the setup; glm-output is the companion skill for exact final metric computation.
glm-basics
A good starting point for GLM calibration tasks. Use to inspect glm3.nml, confirm how GLM runs, and identify the relevant files before moving on to calibration and output evaluation.