D3 Scales Part Three
FREE     Duration: 9:49
Part of Course: Introductory D3 Course
 

Takeaways:

  • D3 provides three types of scale functions that map an input domain to an output range - Quantitative Scales, Ordinal Scales, and Time scales
  • The best way to think about the domain and range is that the left most point of the defined domain goes to the left most point of the defined range and the right most point of the defined domain goes to the right most point of the defined range
  • D3 Scale functions allow us to use D3 to do the math for us when figuring out the scale conversion
  • D3 Scale functions can be used to scale data (up or down) to fit within an SVG Viewport
  • The D3 Scale function's range will be decided by the SVG Viewport dimensions
  • The D3 Scale function's domain will be decided by the data that comes in
  • The D3 Scale function can be used to create and place SVG basic shapes to represent data

Transcript:

D3 Scales Part Three

D3 Scales Revisited


Quantitative Scales - for continuous input domains, such as numbers.

Ordinal Scales - for discrete input domains, such as names or categories.

Time Scales - for time domains.


D3 provides three types of functions that map an input domain to an output range.

The Quantitative scales are for real numbers

The Ordinal Scales are Discreet domains, such as letters of the alphabet

The Time Scales are an extension of the Quantitative Scales that use the JavaScript Date Objects.


All of these scales take in data and convert it to a useable set of output.

Sometimes this means scaling up the data and sometimes it means scaling down the data.


D3 Scales

Quantitative Scales

  • Linear Scales
  • Power Scales
  • Log Scales
  • Quantize Scales
  • Quantile Scales
  • Threshold Scales


Ordinal Scales

  • Ordinal Scales
  • Categorical Colors
  • ColorBrewer


Time Scales

  • Time Scales


D3 provides many kinds of ways to transform data through Scales.

This video focuses on the Linear Scale

This allows us to explore the basic concepts of D3 Scales and

Once we understand those, the rest will be easier to comprehend.


Quantitative Scales - Linear Scales

y = mx + b


D3 Quantitative Scales are for continuous input domains, such as numbers.

The mapping is linear in that the output range value y can be expressed as a linear function of the input domain value x.

So we get y = mx + b


d3.scale.linear()
    .domain([0,400] )
    .range( [0,200] );

Rather than having to do this math ourselves, we can get D3 to do the math for us.

Using the D3 Scale Linear

We can tell D3 that the initial data covers 0 to 400

and we want it to cover 0 to 200 after it has been scaled.

This then figures out the correct math for the y=mx+b equation.


d3.scale.linear().domain(...).range(...);

ℝ - Real Numbers
LINE 1: Domain
----------A---------B---------C---------
LINE 2: Range
----------D---------E---------F---------

The Range is thus the result of the transformation from Line 1, the Domain, to Line 2

The left most element in Line 1 gets transformed to the left most element in Line 2

And the right most element in Line 1 gets transformed to the right most element in Line 2

And a middle elements in line 1 gets transformed to a middle elements in line 2.


var lineScale = d3.scale.linear().domain(...).range(...);

D3 scales are functions.

Which means we can use them to generate values useful to generating data and Data Visualizations.

The d3.scale linear function assumes that when you pass it values, that you are passing values from the domain line.

The d3.scale linear function then will return to you a value from the range line.

So it works as a mapping function.

Give it a number from line 1 and it will return to you a number from line 2.


At this point you should understand what a linear scale is, what the domain is and what the range is.

Now let's take a look at how we can use them in a data visualization.



SVG Container and D3 Scales


<svg width="200" height="200">

myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },
           { "n":300, "p":300 },{ "n":400, "p":400 }];

We come back to the SVG viewport that is 200 units by 200 units.

There are two ways to make sure the myData data fits into the SVG Viewport

One - scale up the dimensions of the SVG Viewport so that it contains the max n and max p variables.

Two - scale down the data so that it fits into the SVG Viewport with hardcoded numbers.

Using the knowledge we have now acquired about d3.scale.linear, the domain and the range

We have all the tools necessary to do the second option - scale down the data to fit into the SVG Viewport.


<svg width="200" height="200">
	
myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },
           { "n":300, "p":300 },{ "n":400, "p":400 }];

Because we know the SVG Viewport is 2-dimensional And the myData set is two-dimensional

We can choose to map N values to the x-coordinates

And choose to map P values to the y-coordinates On the SVG Coordinate Space.

This means that we will need two linear scale functions:

One for the X axis

and One for the Y Axis.


<svg width="200" height="200">

myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },
           { "n":300, "p":300 },{ "n":400, "p":400 }];
What has to fit into what?

We are going to do two linear scale transformations

The first thing we have to figure out is what is the domain and what is the range for each one.

That is, what data is on Line 1 and what data is on Line 2.

The way I think about it is asking myself the questions - what has to fit into what?

The first WHAT is the domain and the second WHAT is the range.

In this case, the myData values have to fit into the SVG Container.

So the myData values will provide the domain and the SVG Container will provide the range.


<svg width="200" height="200">

myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },
           { "n":300, "p":300 },{ "n":400, "p":400 }];
What has to fit into what?

So myData values are the domain.

We also know that we need a linear scale function for the X axis and one for the Y axis.

In this data set, we have two variables - the N and the P.

The N will be the x axis values

The P will be the y axis values.


myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },
           { "n":300, "p":300 },{ "n":400, "p":400 }];

// minN

// maxN

// minP

// maxP

To get the domain numbers, we have to get the left most and right most points on the lines.

Which means we have to get the minimum N and the minimum P for the left most points

And we have to get the maximum N and the maximum P for the right most points.


minN = d3.min(myData, function(d) { return d.n })

minP = d3.min(myData, function(d) { return d.p });

maxN = d3.max(myData, function(d) { return d.n });

maxN = d3.max(myData, function(d) { return d.p });

To get the min and max P and Ns, we can use the functions D3 provides to us for Arrays.

We make sure to use the accessor functions, since we are dealing with objects.


domainN = d3.extent(myData, function(d) { return d.n });

domainP = d3.extent(myData, function(d) { return d.p });

Because the domain takes in an array with the low point and high point

We can actually just use the D3 Array Extent functionality to have it return the min and the max in a single array.

This saves us from creating 2 variables for the min and 2 variables for the max.


So far...

domainN = d3.extent(myData, function(d) { return d.n });

domainP = d3.extent(myData, function(d) { return d.p });

var xLine = d3.scale.linear().domain(domainN);

var yLine = d3.scale.linear().domain(domainP);

Which means we can use the domainN and domainP variables right in the domain part of the linear scale function.

So far we have figured out what the domain of the line functions for the x and y axis will be.

Next we have to think about what the range will be.


<svg width="200" height="200">

myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },  
           { "n":300, "p":300 },{ "n":400, "p":400 }];

What has to fit into what?

To figure out what the range will be, think about what we said before.

What has to fit into what?

The second what is the range.

Which in our case, is the 200 by 200 SVG container.


<svg width="200" height="200">

rangeSVGX = [0,200];

rangeSVGY = [0,200];

To figure out the range is easier.

Because we know that the SVG container is 200 units wide by 200 units tall,

The max SVG X and the max SVG Y are both 200.

Also, we know that the left top most point is the origin

Which means the min SVG X and the min SVG Y are both 0.

Because we know the range part of d3.scale.linear takes in an array of low and high values

We can just construct an array out of the min and max SVG X and Y coordinates.


<svg width="200" height="200">

myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },  
           { "n":300, "p":300 },{ "n":400, "p":400 }];

var xLineScale = d3.scale.linear().domain(domainN).range(rangeSVGX);

var yLineScale = d3.scale.linear().domain(domainP).range(rangeSVGY);

Using the data and the SVG Container,

we were able to create a linear scale function that will take in any of the myData points

and convert them so that they fit into the SVG Container.


Let's take a look at an example in the JavaScript Console.


First we define the data source

var myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },  
               { "n":300, "p":300 },{ "n":400, "p":400 }];


Next, because the SVG container is hardcoded, let's define the mins and maxs for the SVG

var rangeSVGX = [0,200];

var rangeSVGY = [0,200];

These can normally be defined at the very top of the visualization since they will only be used once.

Sometimes, these aren't even defined as variables and people hardcode them into the actual D3 Scale Linear Functions

I prefer to leave all hardcoded numbers at the top in a variable section that is easy to find, see and change.


Next, let's define the domain for N and the domain for P based on the myData data source.

var domainN = d3.extent(myData, function(d) { return d.n });

domainN;

var domainP = d3.extent(myData, function(d) { return d.p });

domainP;

As you can see, the extent function worked great and provided the right min and max numbers.


Next, let's define the d3 linear scale functions.

var xLineScale = d3.scale.linear().domain(domainN).range(rangeSVGX);

var yLineScale = d3.scale.linear().domain(domainP).range(rangeSVGY);


Just to make sure, let's test the left most and right most points for each D3 Linear Scale Function

[xLineScale(0),xLineScale(400)];

[yLineScale(0),yLineScale(400)];

As you can see, the linear scales are doing the right conversions.


And with these functions, we are now ready to work with the data. # We can now convert the original data numbers into numbers that will fit into the 200 unit by 200 unit SVG viewport.



D3 Scales and SVG Data Points


d3.scale.linear().domain(...).range(...);

ℝ - Real Numbers
LINE 1: Domain
----------A---------B---------C---------
LINE 2: Range
----------D---------E---------F---------

Now, we will focus on what happens to the elements in the middle of the number line.

That is, the B and the E.

The D3 Scale Linear function takes the middle elements in line 1 and transforms them to middle elements in line 2.


myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },
           { "n":300, "p":300 },{ "n":400, "p":400 }];

We have to convert the middle two JSON objects into something that will fit into the SVG Viewport.

We can put these data points into the Linear Scale Function to get the results.


<svg width="200" height="200">

myData = [ { "n":  0, "p":  0 },{ "n":200, "p":200 },  
           { "n":300, "p":300 },{ "n":400, "p":400 }];

The linear conversion from the domain to the range is to divide the number by 2.


var xLineScale = d3.scale.linear().domain(...).range(...);

var yLineScale = d3.scale.linear().domain(...).range(...);

d3.selection.data(myData)).enter().append("circle")
    .attr("cx",function(d,i){ return xLineScale(d.n); })
    .attr("cy",function(d,i){ return yLineScale(d.p); })
    .attr("r","15");

What we can do now with these functions is to use them in the D3 Pattern.

When we are specifying Data in the attributes.

This does the linear scale conversion for us on the fly for each data element created.


Let's see how this works in the JavaScript console.


The linear conversion from the domain to the range is to divide the number by 2.

That is, we want to take something that has a maximum of 400 and convert it to something that has a maximum of 200.

xLineScale(200);

yLineScale(200);

As you can see, it does what we thought - it returns 100, which is 200 divided by 2.


Now, let's add an SVG Element to the screen.

var svgContainer = d3.select("body").append("svg").attr("width",200).attr("height",200);

BROWSER - Click on the HTML to expand the body element to see the SVG Container.

I hard code the width and height numbers to make it clear what we were doing.


Next, let's create the SVG Circle Elements Selection using the D3 pattern you should now recognize.

var circleSelection = svgContainer.selectAll("circle").data(myData).enter().append("circle");

BROWSER - Click on the HTML to expand the SVG element to see the SVG DOM CIRCLE elements.

As you can see, we have created the 5 Circle Elements that will exist inside of the SVG Viewport


Finally, let's add in the attributes based on scaled data.

var circleAttributes = circleSelection
    .attr("cx",function(d,i){ return xLineScale(d.n) })
    .attr("cy",function(d,i){ return yLineScale(d.p) })
    .attr("r","15");

As you can see, we were able to scale the data from the myData data source to be able to fit into the

SVG Container 200 units wide by 200 units tall.

You can see that the D3 Linear Scale Functions were used inside of the anonymous function for each attribute.


Now that we have the circles, a very important thing to notice

d3.selectAll("circle");

If we select all the circles and click down to see what data is attached to them.

BROWSER - OPEN Circle 4

BROWSER - Highlight n = 400 and p = 400.

You can see that the data attached to the circle is the actual original data.

Why would we want to save the original data?

Because this data can be used to construct text or call outs in the Data Visualization

It is very important to never change the initial data.

This is why the transformation is done in the attributes section.


And with that, we have covered the full basics of the D3 scales.

We covered what they are theoretically, what the domain is, what the range is, how we can think of them like a function

What values we can pass in and expect to get out and how we can actually use them in a Data Visualization.

<< Back To D3 Screencast and Written Tutorials Index