How to make a basic Bubble Chart in D3

Bubble Chart

Getting your data into CSV format

Use a spreadsheet program to export your data as a CSV

Use a spreadsheet program to export your data as a CSV

The var diameter = 500 will determine the maximum dimensions of the area of the circle pack chart.

The color scale we use is d3.scaleOrdinal(d3.schemeCategory10), but you may use one of the other color scales provided by d3.

Alternatively, you can use your own color scale by setting up an ordinal or linear scale, and specifying the domain as several colors .range(["#000000", "#ffffff", "#CCCCCC", "#333333"]).

NOTE: Example below uses D3js Version 5.

Full example below

This project requires fruit.csv file to be in the same folder, and the project has to be run from a webserver.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Basic Bubble Chart</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    /* custom css styles will go here */

    </style>
</head>
<body>

<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var diameter = 500, //max size of the bubbles
    format   = d3.format(",d"),
    color    = d3.scaleOrdinal(d3.schemeCategory10);
    //more color options: https://github.com/d3/d3-scale-chromatic

var bubble = d3.pack()
    .size([diameter, diameter])
    .padding(1.5);

var svg = d3.select("body")
    .append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
    .attr("class", "bubble");

d3.csv("fruit.csv").then(function(data){


    //convert numerical values from strings to numbers
    data = data.map(function(d){ d.value = +d["Amount"]; return d; });

    //Sets up a hierarchy of data object
    var root = d3.hierarchy({children:data})
      .sum(function(d) { return d.value; })
      .sort(function(a, b) { return b.value - a.value; });

    //Once we have hierarchal data, run bubble generator
    bubble(root);

    //setup the chart
    var bubbles = svg.selectAll(".bubble")
        .data(root.children)
        .enter();

    //create the bubbles
    bubbles.append("circle")
        .attr("class", "circle")
        .attr("r", function(d){ return d.r; })
        .attr("cx", function(d){ return d.x; })
        .attr("cy", function(d){ return d.y; })
        .style("fill", function(d) { return color(d.value); });

    //format the text for each bubble
    bubbles.append("text")
        .attr("x", function(d){ return d.x; })
        .attr("y", function(d){ return d.y + 5; })
        .attr("text-anchor", "middle")
        .text(function(d){ return d.data["Fruit"]; })
        .style("fill","white")
        .style("font-family", "Helvetica Neue, Helvetica, Arial, san-serif")
        .style("font-size", "12px");
});
</script>
</body>
</html>

Wrapping Long Lables on the Chart

Sometimes, these charts have labels that are too long to fit in the bubbles. There is a solution to wrapping the labels noted by this StackOverflow post.

To the end of your code, add the following JavaScript function:

function wrap(d) {
    var text = d3.select(this),
      width = d.r * 2,
      x = d.x,
      y = d.y,
      words = text.text().split(/\s+/).reverse(),
      word,
      line = [],
      lineNumber = 0,
      lineHeight = 1.1,
      tspan = text.text(null).append("tspan").attr("x", x).attr("y", y);
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + "em").text(word);
      }
    }
}

Then, to the last line of the block of code where you appended the text lables, add .each(wrap).

You can also limit the size of any bubble that should be labeled by putting a conditional statement in the text function. This example shows how to do both of these things:

bubbles.append("text")
    .attr("x", function(d){ return d.x; })
    .attr("y", function(d){ return d.y + 5; })
    .attr("text-anchor", "middle")
    .text(function(d){ 

        //Only show text if bubble's radius is more than 25
        if(d.r > 25){

            return d.data["Fruit"]; 

        } else {

            //bubble has a radius smaller than 25, don't show label
            return "";
        }
        
    })
    .style("fill","white")
    .style("font-family", "Helvetica Neue, Helvetica, Arial, san-serif")
    .style("font-size", "12px")
    //add this wrap function to wrap long text
    .each(wrap);