Home > Tutorials > D3 Tutorials > Using scales
Using 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.
Why we need scales
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!
Open example in new window
<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>
Open example in new window
Round 2
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
.
Open example in new window
<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>
Open example in new window
Round 3
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?
Open example in new window
<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>
Open example in new window
Round 4
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
The magic of 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.
Open example in new window
<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>
Open example in new window
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.
Open example in new window
<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>
Open example in new window
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.
Using d3.scale in practice
You set up your d3.scale
up top, and then you use it inside of your function.
Open example in new window
<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>
Open example in new window
Examples
Example: Round 1
We’re going to do three things to make this especially awesome.
Use d3.max
to get the largest value being graphed
Set the width of your svg
through d3
Set up your scale automatically using 1. and 2.
Open example in new window
<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>
Open example in new window
Example: Round 2
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!
Open example in new window
<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>
Open example in new window
See how it got all small and appropriate? Jeez that is so neat .
Non-numeric scales
…I’ll get to this one. TODO.