How to use d3.domain/range to have a custom domain

2.2k views Asked by At

I'm currently using the whole idea of

var myQuantizeFunction = d3.scale.quantize()
                                 .domain(minMaxFromData) // the minmax using d3.extent
                                 .range(['bla-1', 'bla-2', 'bla-3', 'bla-4', 'bla-5']);

So this works fine when you want to generate a legend across your min-max. The issue is, I have some data which comes back as 0.

Here is an example legend for context :

legend image

As you can see, it's first or lowest value from the range is 0 - 4.7, what I want to really do is have 0 (ie none) as it's own legend item and have everything above ie 1 - 33 in this case as the other ranges.

I want to be able to specify that the first range is 0 and then the domain is split equally between values > 0.

Is there a d3 way of doing this? I'm sure someone else must have had this same problem before, I can't seem to find it but I may not be using the right search terms.

2

There are 2 answers

3
David Lemon On BEST ANSWER

I improved the latest solution to use d3.min() and added code to the test the quantize function. Also I added a small function to colorize the output.

Everything done in the d3 datadriven way

data = [0,0,2,1,4,6,7,8,4,3,0,0];
range = ['bla-1', 'bla-2', 'bla-3', 'bla-4', 'bla-5'];

//strip the first element
reducedRange = range.slice();
reducedRange.shift();

var myQuantizeFunction = 
    d3.scale.quantize()
      .domain([d3.min(data), d3.max(data)])
      .range(reducedRange);

var filterQuantize = function(d){
  if(d==0){
    return range[0];
  }else{
    return myQuantizeFunction(d);
  }
}

var colorize = d3.scale.category10();
// To test this we will put all the data in paragraphs
d3.select('body').selectAll('p').data(data)  .enter()
  .append('p')
  .text(function(d){return d+':'+filterQuantize(d);})
  .style('color',function(d){return colorize(d)});

View this code runing

Hope this helps, good luck!

Update: I stripped zero out of the scale to treat it as a special case as you pointed in the comment.

5
thodic On

From the documentation:

quantize.domain([numbers])

If numbers is specified, sets the scale's input domain to the specified two-element array of numbers. If the array contains more than two numbers, only the first and last number are used. If the elements in the given array are not numbers, they will be coerced to numbers; this coercion happens similarly when the scale is called. Thus, a quantize scale can be used to encode any type that can be converted to numbers. If numbers is not specified, returns the scale's current input domain.

As the name suggests d3 is 'data driven' so ignoring parts of your data set is not part of its ethos.

You need to write your own function to generate the [numbers] array.

Try:

data = [0,0,2,1,4,6,7,8,4,3,0,0];

min = undefined;
data.forEach(function (v) {
    if (v > 0) {
        if (typeof(min) === 'undefined') {
            min = v;
        } else if (v < min) {
            min = v;
        }
    }
})

var myQuantizeFunction = d3.scale.quantize()
                                 .domain([min, d3.max(data)])
                                 .range(['bla-1', 'bla-2', 'bla-3', 'bla-4', 'bla-5']);