SpyTh SpyTh - 1 year ago 72
Javascript Question

Ordinal scale behaviour

I would like to create an ordinal scale with one to one correspondence between range and domain. I would expect that values/strings that do not belong to the domain would return nothing. Instead I get the following behaviour:

var x = d3.scaleOrdinal().domain(['a', 'b', 'c']).range([10, 20, 30])

Is there any logic behind this behaviour? Maybe I am missing something, but I couldn't find something out there.

Answer Source

That's the expected behaviour of an ordinal scale. Actually, D3 scales (quantitative or qualitative) are made in such a way that you can pass values outside the domain (see my answer here).

If you want to define outputs for unknown inputs, use scale.unknown():

var x = d3.scaleOrdinal()
    .domain(['a', 'b', 'c'])
    .range([10, 20, 30])
    .unknown("not in the scale");

console.log("a is " + x("a"));
console.log("b is " + x("b"));
console.log("c is " + x("c"));
console.log("f is " + x("f"));
console.log("xyz is " + x("xyz"));
<script src="https://d3js.org/d3.v4.min.js"></script>

EDIT: Regarding OP's question in the comments:

Are the range values for values outside the domain chosen randomly or is there a logic?

The logic can be seen inspecting the source code:

function scale(d) {
    var key = d + "", i = index.get(key);
    if (!i) {
        if (unknown !== implicit) return unknown;
        index.set(key, i = domain.push(d));
    return range[(i - 1) % range.length];

It shows us that, if the value doesn't exist, it's pushed into the domain. So, values not specified in the domain will have an output that will "cycle" through the range. Check this demo, the output is 10, 20, 30, 10, 20, 30, 10, 20...:

var x = d3.scaleOrdinal()
    .domain(['a', 'b', 'c'])
    .range([10, 20, 30]);

console.log("d is " + x("d"));
console.log("e is " + x("e"));
console.log("f is " + x("f"));
console.log("g is " + x("g"));
console.log("h is " + x("h"));
console.log("i is " + x("i"));
console.log("j is " + x("j"));
<script src="https://d3js.org/d3.v4.min.js"></script>

Which has the same outcome of writing this:

var x = d3.scaleOrdinal()
.domain(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
.range([10, 20, 30]);

In this case d corresponds to 10, e corresponds to 20, f corresponds to 30 etc...