D3 Scales For Data
FREE     Duration: 8:55
Part of Course: Introductory D3 Course
 

Takeaways:

  • D3 provides three types of functions that map an input domain to an output range - Quantitative Scales (real numbers), Ordinal Scales (discreet domains), and Time Scales (JavaScript Date Objects)
  • All of the scale functions take in data and convert it to a usable set of output
  • The D3 Axis Component, d3.svg.axis(), displays reference lines, labeled ticks, and does the math to get equal space between the ticks for any D3 Scale you provide automatically
  • The D3 Axis Component is a function, so you have to pass it an argument in which it can do work on - this is always a D3 Selection inside of which it will generate the SVG Axis for us
  • The D3 Axis Component works with Quantitative Scales, Ordinal Scales, and Time Scales
  • Not only do you want to use the D3 Scales for making the Axes, you will also want to scale the data so that it the input data is properly scaled and placed within the data visualization
  • This means that you will want to use the D3 Scales when defining the attribute value pairs of DOM elements you create using the D3 Data Join

Transcript:

D3 Scales For Data

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.


Quantitative Scales - Linear Scales


y = mx + b

D3 Quantitative Scales are for continuous input domains, such as Numbers and Time

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.

The initial data is entered in the domain.

and what we want the data to be scaled to is put into the range.


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

The Domain is Line 1

The Range is Line 2

The Range is thus the result of the y=mx+b transformation from Line 1 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 the middle elements in line 1 get transformed to the middle elements in line 2.



D3 Axes Component Revisited


d3.svg.axis()

The D3 axis component displays reference lines, labeled ticks and does the math to get equal space between the ticks for any scale we provide automatically.

The D3 axis component works with quantitative scales, time scales and even ordinal scales.

d3.select("body").append("svg")
    .attr("width", 200)
    .attr("height",200)
  .append("g")
    .call( d3.svg.axis() );


This is the most basic way to add an axis to an SVG Container.

Notice that we use D3 to create the SVG Container

Then we add the width and height attribute to the SVG Container

Then we append an SVG Group Element

The SVG Group element will hold the D3 Axis Elements.

Finally we call the d3.svg.axis component.

We have to call the D3 Axis component to add the D3 Axis to the SVG Group Element.

The D3.svg.axis component is a function, so when we call it, we are passing to it the current selection.

The current selection being the SVG Group Element.

The D3.svg Axis Component does it's function thing and then returns a new selection.


var margin = {top: 50, right: 50, bottom: 50, left: 50},
    width = 300 - margin.left - margin.right,           
    height = 300 - margin.top - margin.bottom;

var myScale = d3.scale.linear()
    .domain( [0, 10] )
    .range ( [0,width] );

var myXAxis = d3.svg.axis().scale(myScale).orient("top");

var myYAxis = d3.svg.axis().scale(myScale).orient("left");

var mySVG = d3.select("body").append("svg")                 
    .attr("width", width + margin.left + margin.right)      
    .attr("height", height + margin.top + margin.bottom)    
  .append("g")                                                               
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var axisXGroup = mySVG.append("g").call( myXAxis );

var axisYGroup = mySVG.append("g").call( myYAxis );

Given D3 Margin Convention, D3 Scales and D3 Axis Components

We can put together this code to give us an inner drawing space in an SVG Container that has the well defined X and Y axes.

The axes taken into account the scale domain of the data we wanted

The axes also take into account the width and height of the inner SVG drawing space to make sure the axes cover the full width and height..

What is left to figure out is what to do and how to manipulate the data that will go inside.



D3 Scales For Data


var myScale = d3.scale.linear()
    .domain( [0, 10] )
    .range ( [0,width] );
# In the code before, we used this to setup the D3 Scale Function.

We told it the domain of the data was 0 to 10 and that it should be mapped to a range of 0 to the width

Which in this case was 200.


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

Recalling how to think about the Domain and the Range

Where the Domain is Line 1

and the Range is Line 2

The data that we have lives on line 1.

Which means that if we want it to be represented in the scaled version of the world which is line 2

We also have to convert it using the scaling function.


var dataSet = //...

elements = d3.select(...).selectAll(...).data(dataSet).enter().append(...)

elementAttributes = elements.attr(...,function(d,i) { return d; });
    .attr(...,function(d,i) { return d; })
    .attr(...,function(d,i) { return d; })
    .attr(...,function(d,i) { return d; });

In D3 Data Visualizations

There are usually two main places where we touch and/or manipulate the actual real data

One - when we define the data source

Two - when we tell D3 how to find the data that is bound to the DOM Elements

So if we were going to use a scale to manipulate the actual real data to fit into the scaled world we have two choices for where to manipulate the data.


It is really not a choice.

Once you receive or define the real data, leave it alone.

There may be more than one process that needs the actual real data, rather than the scaled data.

var dataSet = //...

elements = d3.select(...).selectAll(...).data(dataSet).enter().append(...)

elementAttributes = elements.attr(...,function(d,i) { return d; });
    .attr(...,function(d,i) { return d; });

Which means, that if any scaling of the data is going to be done,

It should be done when we are taking the real data that is bound to the DOM Elements

and scaling it for the attributes of that element.


var dataSet = //...
var myScale = d3.scale.linear()
    .domain( [0, 10] )
    .range ( [0,width] );

elements = d3.select(...).selectAll(...).data(dataSet).enter().append(...)

elementAttributes = elements.attr(...,function(d,i) { return myScale(d); })
    .attr(...,function(d,i) { return myScale(d); });

This makes sure that the real data is attached to the DOM Element.

However, the attribute data is the data that is scaled.

This is scaled to match the type of scaling that we want to happen in the data visualization.

Most of the time this means fitting inside of the SVG Container and SVG inner drawing space we have defined.


Let's take a look in the JavaScript Console


First, we enter all of the code necessary for the SVG Viewport, the SVG Inner space and the Scale Function

var margin = {top: 50, right: 50, bottom: 50, left: 50},
    width = 300 - margin.left - margin.right,           
    height = 300 - margin.top - margin.bottom;


Then we generate the scale which will be used for both axes elements as well as scaling the data later.

var myScale = d3.scale.linear()
    .domain( [0,   10] )
    .range ( [0,width] );


We define the X-Axis and the Y-Axis functions, scale them and give them an orientation.

var myXAxis = d3.svg.axis().scale(myScale).orient("top");

var myYAxis = d3.svg.axis().scale(myScale).orient("left");


Next, we define the SVG Viewport as well as the Inner Drawing Space

var mySVG = d3.select("body").append("svg")                 
    .attr("width",  width  + margin.left + margin.right)      
    .attr("height", height + margin.top  + margin.bottom);

var innerSpace = mySVG.append("g")                                                               
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


Next we call the X-Axis and Y-Axis functions to have them generate all the D3 Axis Components.

var axisXGroup = innerSpace.append("g").call( myXAxis );

var axisyGroup = innerSpace.append("g").call( myYAxis );

BROWSER - Click into Body element

BROWSER - Click into SVG element

BROWSER - Click into G element

And there we go - we have now the full chart ready for us.


Next, let's define a data object to pass to the D3 Data Operator.

var myDataPoint = [{"x":5, "y":5}];


Next, let's bind the data to an SVG Circle Element

var circle = innerSpace.selectAll("circle")
    .data(myDataPoint)
  .enter().append("circle");

BROWSER - Click on the circle SVG element that was added

You can see that the Circle SVG element was added to the G Group that has the transform translate.

This G group is the inner drawing space.

The circle was added as the last child element, because this is how the D3 append operator works.

The circle does not appear on the screen because we have not define it's attributes.


First, let's define it's attributes without scaling the data.

var circleAttributes = circle
    .attr("cx", function(d,i) { return d.x; })
    .attr("cy", function(d,i) { return d.y; })
    .attr("r", 5);

The attributes are defined based on the data that was bound to the SVG Circle element.

We use anonymous JavaScript functions to get out the relevant x and y values.

We set the radius to be 5.


When we press enter

BROWSER - Press Enter

You can see that the circle was created.

If we look at the elements section of the Chrome Developer Tools, we see that the

CX is 5

the CY is 5

and the Radius is 5

Which is that the data told us.

However, when we look at the chart in the webpage, it currently looks like the circle's data

has a CX of something between 0 and 1

and has a CY of something between 0 and 1

This is because we did not scale the data when we defined the attributes of the circle.


Let's delete the Circle SVG element inside of the Transformed Translated G element to clear the drawing space.

BROWSER - Highlight the Circle element inside of the SVG Group Element that was transformed / translated

BROWSER - Press delete

When using Chrome developer tools, you can delete elements right from this window.

Just click on them and then press the delete key.

As you can see, the circle element is no longer there.


Let's bind the data to the SVG Circle Element and create the circle element again.

var circle = innerSpace.selectAll("circle")
    .data(myDataPoint)
  .enter().append("circle");

BROWSER - Click on the circle SVG element that was added

You can see that the Circle SVG element was added to the G Group that has the transform translate.


This time, let's define it's attributes making sure to scale the data using the scaling function [ DON'T PRESS ENTER UNTIL NEXT PART]

var circleAttributes = circle
    .attr("cx", function(d,i) { return myScale(d.x); })
    .attr("cy", function(d,i) { return myScale(d.y); })
    .attr("r", 5);

The attributes are defined based on the data that was bound to the SVG Circle element.

We use anonymous JavaScript functions to get out the relevant x and y values.

We set the radius to be 5.

This time however, we use the scaling function to scale the real data to a scaled version of the data

This scaled version of the data will match the scaling applied to the X-axis and the Y-axis.


When we press enter

BROWSER - Press Enter

You can see that the circle was created.

If we look at the elements section of the Chrome Developer Tools, we see that the

CX is 100

the CY is 100

and the Radius is 5

Which is what we would expect given that we used the scaling function.

The scaling function took the numbers in the domain and multiplied them by 20 to get the range.

So the cx equals 5 and cy equals 5 from before have been scaled to cx equals 100 and cy equals 100.

Which is great because now the Graph Axis and the Data plotted on the graph actually show the same thing.

That the data point has an X value of 5 and a Y value of 5.

elementAttributes = elements
    .attr(...,function(d,i) { return scaleFunction(d); })
    .attr(...,function(d,i) { return scaleFunction(d); });


And with that, you can see just how easy and useful it can be to scale the data when setting attributes.

It helps to make sure the data attributes are scaled in the same way that the axis attributes have been.

The D3 Scale function does all the math behind the scenes as long as we provide for it a scale with a domain and range.

<< Back To D3 Screencast and Written Tutorials Index