When you’re working in D3, a lot of stuff is going to be broken a lot of the time. Instead of sitting there moaning and randomly changing numbers and symbols in your code, you really need to use the Web Inspector and Console.
The Web Inspector lets you view the HTML that’s on the page. Big whoop! You wrote that HTML, right? No surprises there?
Bzzt - wrong! The D3 code you wrote actively changes the code on the page - adding elements, changing attributes, and generally mixing things around.
By using the web spector you can see what exactly your D3 code has been up to and where your mistakes might have come from (e.g. you’ve moved an element off of the page).
So you open up the Web Inspector and see some HTML like this:
<svg>
<rect></rect>
<rect></rect>
<rect></rect>
</svg>
<svg>
<circle></circle>
<circle></circle>
<circle></circle>
</svg>
Seems sensible enough, but there’s a big big big problem in its future. While your homework display fine and all, let’s think about what might happen.
You did something like the following to make your rectangles and circles:
var circles = d3.selectAll("circles");
circles.data(some_data);
var rectangles = d3.selectAll("rect");
rectangles.data(other_data);
And it worked fine, but… what if instead of a bar graph and some circles I asked you to make two bar graphs? Your HTML might look like this
<svg>
<rect></rect>
<rect></rect>
<rect></rect>
</svg>
<svg>
<rect></rect>
<rect></rect>
<rect></rect>
</svg>
And your JavaScript might look like this
var rectangles1 = d3.selectAll("rect");
rectangles1.data(some_data);
var rectangles2 = d3.selectAll("rect");
rectangles2.data(other_data);
Spend like four seconds trying to figure out what the problem is going to be with that JavaScript. What’s the difference between the first d3.selectAll
and the second one?
…absolutely nothing! They both grab all the rectangles on the page. And that’s trouble.
You wanted the first selectAll
to grab the ones inside of the first svg
, and the second selectAll
to grab the ones inside the second svg
, but nope! Because d3 is willy-nilly grabbing all of the rect
elements on the page, the first set of rectangles and the second set of rectangles are now all tangled together.
As a result, you won’t be able to manipulate the two charts separately, even though they’re in separate svg
elements!
You’ll accidentally be binding data to both of your bar graphs at the same time, and everything will go crazy and break. Don’t worry too much, though, there are a few ways to deal with this.
There are two methods to solve the problem - one involves pre-writing a little bit of HTML, the other involves adding your svg
element on the fly.
svg
elements with classesHow can d3 tell your svg
elements apart? Same as CSS - you’ll need to use classes or ids and the appropriate selector.
<svg class='cat-graph'>
<rect></rect>
<rect></rect>
<rect></rect>
</svg>
<svg class='dog-graph'>
<rect></rect>
<rect></rect>
<rect></rect>
</svg>
D3 isn’t greedy - along with selectAll
it also has a plain old select
. Luckily for us D3 is cool and uses the exact same selectors as CSS - a period for a class.
var cat_svg = d3.select(".cat-graph");
var dog_svg = d3.select(".dog-graph");
You’ll use select
to grab the specific svg
you’re looking for, then selectAll
to grab the elements inside of it.
var cat_rectangles = cat_svg.selectAll('rect');
cat_rectangles.data(some_data);
var dog_rectangles = dog_svg.selectAll('rect');
dog_rectangles.data(other_data);
And now your cat_rectangles
and your dog_rectangles
are separate and never the twain shall meet!
A more advanced way of doing this involves manually appending the SVG. This method is actually a little more popular, but I think in class we’ll be using Method 1.
Step 1: In HTML you make the div
(or div
s) that is going to hold your graphic.
<div class="cat-holder"></div>
<div class="dog-holder"></div>
Step 2: Then you select
that div
, and manually append your svg
to the HTML using .append
.
var cat_svg = d3.select('.cat-holder').append('svg');
var dog_svg = d3.select('.dog-holder').append('svg');
Step 3: Then you bind your data and .enter().append('rect')
to put a rectangles into the svg for every data point. I don’t think we’ve talked about enter
and append
yet, so this will make more sense later on!
var cat_rectangles = cat_svg.selectAll('rect')
.data(some_dat)
.enter()
.append('rect');
var dog_rectangles = dog_svg.selectAll('rect')
.data(other_data)
.enter()
.append('rect');
You can do this even when you don’t have a million graphs on a page (it’s considered good practice). In practice, it might look like this:
HTML:
<div class="chart"></div>
JavaScript:
var datapoints = [1, 2, 3];
// Add an svg inside of .chart,
// set it to be sized 400x400
var svg = d3.select('.chart')
.append('svg')
.attr('height', 400)
.attr('width', 400);
// Space the circles out inside of the
// circle, 100 pixels apart
var circles = svg.selectAll('circle')
.data(datapoints)
.enter()
.append('circle')
.attr('r', 3)
.attr('cy' 40)
.attr('cx', function(d) { d * 100 });