agentsclimarketplace

D3 force bubble chart

Skill cxcscmu/SkillLearnBench/skills/b4-skill-creator-claude-opus-4-6/stock-data-visualization/d3-force-bubble-chart

Build D3.js v6 force-simulation bubble charts where bubbles are sized by a numeric metric, colored by category, and clustered by category using forceX/forceY. Use this skill whenever the user asks for bubble charts, force-layout visualizations, or clustered circle packing with D3.js. Triggers on: bubble chart, force simulation, cluster layout, circle packing.From its SKILL.md

Install
npx -y skills add cxcscmu/SkillLearnBench --skill d3-force-bubble-chart

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

3.4 KB, 787 tokens by cl100k_base, as published. Nobody here has run it

D3 Force Bubble Chart

Overview

A force-simulation bubble chart uses d3.forceSimulation to position circles (bubbles) in a layout where they are clustered by category, sized by a numeric value, and colored by category.

Architecture

Sizing Bubbles

Use d3.scaleSqrt() to map a numeric value (e.g., market cap) to bubble radius. Square-root scaling ensures area is proportional to value, which is perceptually accurate.

const radiusScale = d3.scaleSqrt()
  .domain([0, d3.max(data, d => d.value)])
  .range([minRadius, maxRadius]);

For items missing the sizing metric (e.g., ETFs with no market cap), assign a uniform default radius.

Coloring by Category

Use d3.scaleOrdinal() with a color scheme like d3.schemeTableau10:

const colorScale = d3.scaleOrdinal()
  .domain(categories)
  .range(d3.schemeTableau10);

Clustering with Force Simulation

Use forceX and forceY to pull bubbles toward category-specific cluster centers. Calculate cluster centers by dividing the chart space into a grid or ring layout based on the number of categories.

// Compute cluster center positions
const sectorCenters = {};
sectors.forEach((sector, i) => {
  const angle = (2 * Math.PI * i) / sectors.length;
  sectorCenters[sector] = {
    x: centerX + clusterRadius * Math.cos(angle),
    y: centerY + clusterRadius * Math.sin(angle)
  };
});

const simulation = d3.forceSimulation(data)
  .force("x", d3.forceX(d => sectorCenters[d.sector].x).strength(0.3))
  .force("y", d3.forceY(d => sectorCenters[d.sector].y).strength(0.3))
  .force("collide", d3.forceCollide(d => radiusScale(d.value) + padding))
  .force("charge", d3.forceManyBody().strength(-5))
  .on("tick", ticked);

Key tuning parameters:

  • forceX/forceY strength: 0.2–0.4 keeps clusters tight without excessive overlap
  • forceCollide padding: 1–3px between bubbles
  • forceManyBody strength: Small negative value (-2 to -10) for gentle repulsion

Labels Inside Bubbles

Add <text> elements centered on each bubble. Only show labels when the bubble is large enough to contain the text (check radius vs. text length).

node.append("text")
  .text(d => d.ticker)
  .attr("text-anchor", "middle")
  .attr("dy", "0.35em")
  .style("font-size", d => Math.min(radiusScale(d.value) * 0.6, 14) + "px");

Tooltips

Use a <div> with position: absolute that follows the mouse on mouseover/mousemove and hides on mouseout.

Legends

Add a legend mapping colors to sector names. Position it in a corner or below the chart. Use small colored circles/rectangles paired with text labels.

Common Pitfalls

  • Forgetting forceCollide causes overlapping bubbles
  • Using forceCenter with forceX/forceY creates fighting forces — avoid forceCenter when using explicit cluster positioning
  • Not calling simulation.stop() or limiting ticks can cause layout jitter
  • Cluster centers too far apart leads to scattered layout — keep clusterRadius moderate relative to chart width

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.