Data Vis Tutorial: D3 Bubble Chart of NYC Baby Names

Full tutorial coming soon! In the meantime, check the JSFiddle below.

This visualization shows the top 2011 NYC baby names by mother’s ethnicity.

Background from the D3 API reference and more:

This tutorial is based on our previous bar chart tutorial, Bostock’s bubble chart example, and InfoCaptor’s Bubble My Page service.

CSS

We set up a text size and style for our vis.

text {
 font: 10px sans-serif;
}

HTML

We include D3.js for the graphics, and Jquery for the rollovers.

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>

Javascript

First we set up the global variables for our visualization size, type, colors, and bubbles.

var diameter = 700,
 format = d3.format(",d"),
 color = d3.scale.category20c();

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

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

We then query NYC Open Data for the baby name dataset, and throw an error if this query is not successful.

d3.csv("https://data.cityofnewyork.us/resource/25th-nujf.csv?$limit=10000", function (error, root) {
 if (error) throw error;

Within the d3.csv function, we set up sub-arrays, divided by ethnicity, to record baby names and related attributes for each ethnicity type (which are pre-determined in the data set). We also set up arrays called data and dobj in which we will concatenate all the sub-arrays.

//baby names
 var AsianNameList=[];
 var BlackNameList=[];
 var HispanicNameList=[];
 var WhiteNameList=[]; 
 
 //number of babies with name
 var AsianNameCount=[];
 var BlackNameCount=[];
 var HispanicNameCount=[];
 var WhiteNameCount=[]; 
 
 //ethnicity of mother
 var AsianEthnicity=[]; 
 var BlackEthnicity=[]; 
 var HispanicEthnicity=[]; 
 var WhiteEthnicity=[]; 
 
 var data=[]; //3D array of nameList, nameCount, ethnicity
 var dobj=[]; //array formated specifically for hierarchical processing

We now examine all the names in the dataset and bin them into the sub-arrays. There are duplicate records in the dataset, so we only add the name if it’s not already in the array. We use the “indexOf” function to check if the name is already in the array.

root.forEach(function (d) {
 if (+d["RNK"] <= 10) {
 
 //only add name if it's not uniquely in the array
 if ((d["ETHCTY"]==="ASIAN AND PACIFIC ISLANDER") && (AsianNameList.indexOf(d["NM"]) ===-1)) {
 AsianNameList.push(d["NM"]);
 AsianNameCount.push(+d["CNT"]); //force the string into an integer
 AsianEthnicity.push(d["ETHCTY"]);
 }
 else if ((d["ETHCTY"]==="BLACK NON HISPANIC") && (BlackNameList.indexOf(d["NM"]) ===-1)) {
 BlackNameList.push(d["NM"]);
 BlackNameCount.push(+d["CNT"]); //force the string into an integer
 BlackEthnicity.push(d["ETHCTY"]);
 }
 else if ((d["ETHCTY"]==="HISPANIC") && (HispanicNameList.indexOf(d["NM"]) ===-1)) {
 HispanicNameList.push(d["NM"]);
 HispanicNameCount.push(+d["CNT"]); //force the string into an integer
 HispanicEthnicity.push(d["ETHCTY"]);
 }
 else if ((d["ETHCTY"]==="WHITE NON HISPANIC") && (WhiteNameList.indexOf(d["NM"]) ===-1)) {
 WhiteNameList.push(d["NM"]);
 WhiteNameCount.push(+d["CNT"]); //force the string into an integer
 WhiteEthnicity.push(d["ETHCTY"]);
 }
 }
 });

We then concatenate all this data into one array called data. There is some simple test data currently commented out; this can be helpful for if you are having trouble managing your larger dataset.

data=[
 //test data is commented out:
 //["Tea","Coffee","Soda","Chips","Milk","Chocolate","Beer","Wine"],
 //[130,30,200,40,230,150,80,65]
 AsianNameList.concat(BlackNameList,WhiteNameList,HispanicNameList), 
 AsianNameCount.concat(BlackNameCount,WhiteNameCount,HispanicNameCount), 
 AsianEthnicity.concat(BlackEthnicity,WhiteEthnicity, HispanicEthnicity)
 ];

We prepare an array called dobj to record the index of each name. We also run the function called display_pack.

for (var di=0;di<data[0].length;di++) { 
   dobj.push({"key":di,"value":data[1][di]}); 
} 

display_pack({children: dobj});

The function display_pack consists of three parts. First, we set up SVG nodes (bubbles): here we set up bubble position, color, and mouseover text.

function display_pack(root) {
 var node = svg.selectAll(".node")
 .data(bubble.nodes(root)
 .filter(function(d) { return !d.children; }))
 .enter().append("g")
 .attr("class", "node")
 .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
 .style("fill", function(d) { 
 //color is based on ethnicity
 return color(data[2][d.key]); })
 .on("mouseover", function(d) {
 d3.select(this).style("fill", "gold"); 
 showToolTip(" "+data[0][d.key]+"<br> "+data[2][d.key]+"<br>count: "+data[1][d.key]+" ",d.x+d3.mouse(this)[0]+50,d.y+d3.mouse(this)[1],true);
 //console.log(d3.mouse(this));
 })
 //.on("mousemove", function(d,i) {
 //tooltipDivID.css({top:d.y+d3.mouse(this)[1],left:d.x+d3.mouse(this)[0]+50});
 //}) 
 .on("mouseout", function() {
 d3.select(this).style("fill", function(d) { return color(data[2][d.key]); });
 showToolTip(" ",0,0,false);
 });

Then size our bubbles.

node.append("circle")
 .attr("r", function(d) { return d.r; });

Finally, we add text to our bubbles.

node.append("text")
 .attr("dy", ".3em")
 .style("text-anchor", "middle")
 .style("fill","black")
 .text(function(d) { return data[0][d.key].substring(0, d.r / 3); });

The function showToolTip is called from display_pack, so we need to write showToolTip. And that’s it!

function showToolTip(pMessage,pX,pY,pShow)
 {
 if (typeof(tooltipDivID)==="undefined") {
 tooltipDivID =$('<div id="messageToolTipDiv" style="position:absolute;display:block;z-index:10000;border:2px solid black;background-color:rgba(0,0,0,0.8);margin:auto;padding:3px 5px 3px 5px;color:white;font-size:12px;font-family:arial;border-radius: 5px;vertical-align: middle;text-align: center;min-width:50px;overflow:auto;"></div>');

 $('body').append(tooltipDivID);
 }
 if (!pShow) { tooltipDivID.hide(); return;}
 //MT.tooltipDivID.empty().append(pMessage);
 tooltipDivID.html(pMessage);
 tooltipDivID.css({top:pY,left:pX});
 tooltipDivID.show();
 }