D3 difference between ordinal and linear scales

As for Ordinal Scales:

Ordinal scales have a discrete domain, such as a set of names or categories.

An ordinal scale's values must be coercible to a string, and the stringified version of the domain value uniquely identifies the corresponding range value.

So, as an example, a domain of an ordinal scale may contain names, like so:

var ordinalScale = d3.scale.ordinal()
        .domain(['Alice', 'Bob'])
        .range([0, 100]);

ordinalScale('Alice'); // 0
ordinalScale('Bob'); // 100

Notice how all values are strings. They cannot be interpolated. What is between 'Alice' and 'Bob'? I don't know. Neither does D3.

Now, as for Quantitative Scales (e.g. Linear Scales):

Quantitative scales have a continuous domain, such as the set of real numbers, or dates.

As an example, you can construct the following scale:

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

linearScale(0); // 0
linearScale(5); // 50
linearScale(10); // 100

Notice how D3 is able to interpolate 5 even if we haven't specified it explicitly in the domain.

Take a look at this jsfiddle to see the above code in action.


In D3.js scales transform a number from the domain to the range. For a linear scale the domain will be a continuous variable, with an unlimited range of values, which can be then transformed to a continuous range. For ordinal scales there will be a discrete domain, for example months of the year where there are limited range of possible values that may be ordered but aren't continuous. The API docs on Github can probably explain the difference better than I have


OK, we can start learning it with using both with the same data to see differences(I'm using d3 v4), imagine we have the data below with using ordinal and linear scales:

const data = [1, 2, 3, 4, 5];

const scaleLinear = d3.scaleLinear()
  .domain([0, Math.max(...data)]).range([1, 100]);

const scaleOrdinal = d3.scaleOrdinal()
  .domain(data).range(['one', 'two', 'three', 'four', 'five']);

Now we start calling them to see the result:

scaleLinear(1); //20
scaleOrdinal(1); //one

scaleLinear(2); //40
scaleOrdinal(2); //two

scaleLinear(5); //100
scaleOrdinal(5); //five

Look at the functions and the results we get, as you see in the ordinal one we map the data to our range, while in the linear one we stretch to the range, so in these cases for example scaleLinear(1) will return 20... our domain max is 100 and 100 divided by 5 is equal 20, so scaleLinear(1) is 20 and scaleLinear(2) is 40...

But as you see, scaleOrdinal(1) is map to the array in the range, so it's equal to one and scaleOrdinal(2) it's equal to two...

So that's how you can use these scales, scaleLinear is useful for many things including present the scale on page, but scaleOrdinal more useful for getting the data in order, that's how it's explained in the documentation:

# d3.scaleLinear() <>

Constructs a new continuous scale with the unit domain [0, 1], the unit range [0, 1], the default interpolator and clamping disabled. Linear scales are a good default choice for continuous quantitative data because they preserve proportional differences. Each range value y can be expressed as a function of the domain value x: y = mx + b.


d3.scaleOrdinal([range]) <>

Constructs a new ordinal scale with an empty domain and the specified range. If a range is not specified, it defaults to the empty array; an ordinal scale always returns undefined until a non-empty range is defined.

Also this is a good example from d3 in depth using both ordinal and linear scales at the same time:

var myData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

var linearScale = d3.scaleLinear()
  .domain([0, 11])
  .range([0, 600]);

var ordinalScale = d3.scaleOrdinal()
  .domain(myData)
  .range(['black', '#ccc', '#ccc']);

d3.select('#wrapper')
  .selectAll('text')
  .data(myData)
  .enter()
  .append('text')
  .attr('x', function(d, i) {
    return linearScale(i);
  })
  .text(function(d) {
    return d;
  })
  .style('fill', function(d) {
    return ordinalScale(d);
  });
body {
  font-family: "Helvetica Neue", Helvetica, sans-serif;
  font-size: 14px;
  color: #333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>

<svg width="800" height="60">
  	<g id="wrapper" transform="translate(100, 40)">
  	</g>
</svg>