d3.scale
Scales! They aren’t just for playing recorder in music class, they’re an integral part to becoming a d3 master.
They also make life a heck of a lot easier.
Okay, so let’s say we’re making a bar graph. We have:
HTML
<svg class="bar" width="120" height="100"></svg>
Data
var datapoints = [50, 2, 3, 4, 5];
So our svg
is 120 pixels wide, and our largest data value is 5
. I think it makes sense to have every bar be d * 20
pixels wide, since that way the largest bar is 100 pixels long!
<svg class="bar" width="150" height="100"></svg>
<script>
var datapoints = [1, 2, 3, 4, 5];
var svg = d3.select('.bar');
var rects = svg.selectAll('rect')
.data(datapoints) // bind our data
.enter() // grab the 'new' data points
.append('rect') // append the bars
.attr('x', 0) // set the left hand side to 0
.attr('y', function(d, i) {
return i * 15; // space them 15 pixels apart
})
.attr('height', 10) // each bar is 10 pixels tall
.attr('width', function(d) {
return d * 20; // and now the width
});
</script>
Oh but wait! We got a new data point.
var datapoints = [1, 2, 3, 4, 5, 50];
Okay, cool. That’s fine, no big deal! Our biggest value is 50
now, so if we’re trying to make the biggest bar 100 pixels, then that’s going to be d * 2
. Since, of course, 50 * 2
is 100
.
<svg class="bar" width="150" height="100"></svg>
<script>
var datapoints = [1, 2, 3, 4, 5, 50];
var svg = d3.select('.bar');
var rects = svg.selectAll('rect')
.data(datapoints) // bind our data
.enter() // grab the 'new' data points
.append('rect') // append the bars
.attr('x', 0) // set the left hand side to 0
.attr('y', function(d, i) {
return i * 15; // space them 15 pixels apart
})
.attr('height', 10) // each bar is 10 pixels tall
.attr('width', function(d) {
return d * 2; // and now the width
});
</script>
Oh but wait! Our editor called, she said that we got a new svg
that’s 410 pixels wide, and one of our data points changed!
<svg class="bar" width="410" height="100"></svg>
var datapoints = [560, 20, 30, 40, 50, 150];
Okay, so now our equation is… I don’t know. Something something math? Maybe it we aim for 400 pixels wide, we’ll use… 400 / 560
which is 0.72
, so the equation will be d * 0.72
. I guess? Maybe?
<svg class="bar" width="410" height="100"></svg>
<script>
var datapoints = [560, 20, 30, 40, 50, 150];
var svg = d3.select('.bar');
var rects = svg.selectAll('rect')
.data(datapoints) // bind our data
.enter() // grab the 'new' data points
.append('rect') // append the bars
.attr('x', 0) // set the left hand side to 0
.attr('y', function(d, i) {
return i * 15; // space them 15 pixels apart
})
.attr('height', 10) // each bar is 10 pixels tall
.attr('width', function(d) {
return d * 2; // and now the width
});
</script>
Oh but wait! Now apparently we need to label the left-hand side, so we need to start like 20 pixels in, so we now only have 400 - 20 = 380
pixels to work with. And we apparently are supposed to start the bars from 10
instead of 0
which means some other sort of math I can’t think about right now and oh god why is everything so bad.
The reason is: because we haven’t been using scales
d3.scale()
take one range of numbers and convert them into another range. Technically it’s a method that creates a function, but who cares about that?
!!!FOR EXAMPLE!!!
Let’s go back to our first example. Biggest data point of 5
, trying to take up 100
or so pixels with the bar.
HTML
<svg class="bar" width="120" height="100"></svg>
Data
var datapoints = [50, 2, 3, 4, 5];
Here’s how we use d3.scale
to switch between the two. It takes the input of 0
-5
(a.k.a. the domain) and maps it to the output of 0
-100
(a.k.a. the range). Any value you send it is converted between those two number ranges.
<script>
var scale = d3.scale.linear().domain([0,5]).range([0,100]);
document.writeln("Using the scale...<br>");
document.writeln("1 becomes ", scale(1), "<br>");
document.writeln("2 becomes ", scale(2), "<br>");
document.writeln("3.5 becomes ", scale(3.5), "<br>");
document.writeln("4.75 becomes ", scale(4.75), "<br>");
document.writeln("5 becomes ", scale(5), "<br>");
</script>
NOTE:
document.writeln
is how you print to the screen in JavaScript.console.log
is its infinitely more useful older, cooler sibling.
Let’s say you wanted a bar that was 410
pixels long, but your largest value is 560
. No need to do math, just rely on d3.scale
! It will convert 0 through 560 into the appropriate values between 0 and 410 pixels.
<script>
var scale = d3.scale.linear().domain([0,560]).range([0,410]);
document.writeln("Using the scale...<br>");
document.writeln("10 becomes ", scale(10), "<br>");
document.writeln("20 becomes ", scale(20), "<br>");
document.writeln("35 becomes ", scale(35), "<br>");
document.writeln("475 becomes ", scale(475), "<br>");
document.writeln("560 becomes ", scale(500), "<br>");
</script>
NOTE: Don’t forget the square brackets inside of
.domain
and.range
! It isn’t.domain(0, 20)
, it’s.domain([0, 20])
. Don’t ask me why.
You set up your d3.scale
up top, and then you use it inside of your function.
<svg class="bar" width="410" height="100"></svg>
<script>
var datapoints = [560, 20, 30, 40, 50, 150];
// Create a scale to take 0-560 as input and return 0-400 as output
var scale = d3.scale.linear().domain([0,560]).range(0, 400);
var svg = d3.select('.bar');
var rects = svg.selectAll('rect')
.data(datapoints) // bind our data
.enter() // grab the 'new' data points
.append('rect') // append the bars
.attr('x', 0) // set the left hand side to 0
.attr('y', function(d, i) {
return i * 15; // space them 15 pixels apart
})
.attr('height', 10) // each bar is 10 pixels tall
.attr('width', function(d) {
return scale(d); // USE THE SCALE!!!
});
</script>
We’re going to do three things to make this especially awesome.
d3.max
to get the largest value being graphedsvg
through d3<svg class="bar"></svg>
<script>
var states = [
{'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136},
{'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},
{'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},
{'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},
{'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},
{'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354 , 'p_water': 0.157},
];
// LOOK AT THIS
var width = 400;
var height = 120;
// LOOK AT THIS
// Set up the SVG
// Set the height and width via d3!
var svg = d3.select('.bar').attr('height', height).attr('width', width);
// LOOK AT THIS
// Get the largest area
// d3.max is usually taught as d3.max([10, 2, 3, 4]) => 10
// but frankly you're always using it with lists of dictionaries
// so why not start now?
var largest_area = d3.max(states, function(d) { return d['total_area'] });
// LOOK AT THIS
// Create a scale to take 0-max area as input, return 0-svg's width as output
var scale = d3.scale.linear().domain([0, largest_area]).range([0, width]);
// Now no matter how the data changes or the width of the svg changes,
// it'll always adjust automatically! THAT IS CRAZY.
var rects = svg.selectAll('rect')
.data(states) // bind our data
.enter() // grab the 'new' data points
.append('rect') // append the bars
.attr('x', 0) // set the left hand side to 0
.attr('y', function(d, i) {
return i * 15; // space them 15 pixels apart
})
.attr('height', 10) // each bar is 10 pixels tall
.attr('width', function(d) {
// LOOK AT THIS
return scale(d['total_area']); // USE THE SCALE!!!
});
</script>
Now I’m just going to cut and paste the same code, but change var width = 400
to be var width = 80
. I’m also going to remove the state with the largest area.
Bask in the gentle glow of its adaptability!
<svg class="bar"></svg>
<script>
var states = [
{'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136},
{'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},
{'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},
{'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},
{'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354 , 'p_water': 0.157},
];
var width = 80;
var height = 120;
// Set up the SVG
// Set the height and width via d3!
var svg = d3.select('.bar').attr('height', height).attr('width', width);
// Get the largest area
// d3.max is usually taught as d3.max([10, 2, 3, 4]) => 10
// but frankly you're always using it with lists of dictionaries
// so why not start now?
var largest_area = d3.max(states, function(d) { return d['total_area'] });
// Create a scale to take 0-max area as input, return 0-svg's width as output
var scale = d3.scale.linear().domain([0, largest_area]).range([0, width]);
// Now no matter how the data changes or the width of the svg changes,
// it'll always adjust automatically! THAT IS CRAZY.
var rects = svg.selectAll('rect')
.data(states) // bind our data
.enter() // grab the 'new' data points
.append('rect') // append the bars
.attr('x', 0) // set the left hand side to 0
.attr('y', function(d, i) {
return i * 15; // space them 15 pixels apart
})
.attr('height', 10) // each bar is 10 pixels tall
.attr('width', function(d) {
return scale(d['total_area']); // USE THE SCALE!!!
});
</script>
See how it got all small and appropriate? Jeez that is so neat.
…I’ll get to this one. TODO.