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:
- bubble chart as a flattened pack layout
- the pack layout is one of D3’s hierarchy layouts
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(); }