d3.scale.category10() not behaving as expected

前端 未结 3 2126
走了就别回头了
走了就别回头了 2020-12-14 08:11

I\'m encountering unexpected behavior when using d3.scale.category10() to generate 10 fixed colors.

Starting out, I note that colors.range() returns an array of pro

相关标签:
3条回答
  • 2020-12-14 08:19

    You misunderstand the usage of category10.

    As the document mentioned: d3.scale.category10() constructs a new ordinal scale with a range of ten categorical colors.

    That is to say: var color = d3.scale.category10() will construct a new ordinal scale with empty domain and range with ten colors.

    When you use the ordinal scale:

    If no domain is set, a range must be set explicitly. Then, each unique value that is passed to the scale function will be assigned a new value from the output range; in other words, the domain will be inferred implicitly from usage. Although domains may thus be constructed implicitly,

    https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-ordinal_domain you can read the API of ordinal scale for more information.

    Update: an ordinal-scale is a map, not an array.

    If domain is not set explicit, the domain will be construct implicit with the key sequence you invoke color(key).

      var color = d3.scale.category10();
    
      console.log(color.domain()); // []
    
      color("aaa");
      console.log(color.domain()); // ["aaa"]
    
      color("bbb");
      console.log(color.domain());  // ["aaa", "bbb"]
    
      color("ccc");
      console.log(color.domain()); // ["aaa", "bbb", "ccc"]
    

    This is useful when you just want to assign a different color for different clusters, and don't have a fixed color mapping. (Think about the situation: when your program support user upload data file as the data source.)

    If you want to map each category to specific color, you have to set the domain explicit so that the mapping is not depend on the sequence of keys.

      var color = d3.scale.category10();
    
      var domain = ["bbb", "ffffd", "ccc", "23", "hello"];
    
      color.domain(domain);
    
      console.log(color.domain()); // ["bbb", "ffffd", "ccc", "23", "hello"] 
      console.log(color.range());  // ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"] 
    
      color("ffffd"); // "#ff7f0e" : d3 will get index of "ffffd" and return range[index]
    
    0 讨论(0)
  • 2020-12-14 08:32

    I use such solution to set color for generated Chart and its legend:

    this.color = d3.scale.category20().range()[Math.floor(Math.random()*100%20)];
    
    0 讨论(0)
  • 2020-12-14 08:33

    It turns out that setting the scale's domain remedies this issue.

    var colors = d3.scale.category10().domain([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    
    // Now this will return the first item instead of the zeroth.
    console.log(colors(1)); 
    

    Or somewhat more succinctly,

    var colors = d3.scale.category10().domain(d3.range(0,10));
    

    Updated fiddle: http://jsfiddle.net/LqHst/2/

    When the category10 scale is created, it's created with a range of 10 colors, and an empty domain.

    var colors = d3.scale.category10();
    console.log(colors.range()); // -> ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]
    console.log(colors.domain()); // -> []
    

    According to the documentation (and the accepted answer), setting the domain on an ordinal scale is optional. When no domain is set, its values are assumed from calls to the scale function.

    var colors = d3.scale.category10();
    console.log(colors.domain()); // -> []
    console.log(colors(1)); // -> #1f77b4
    console.log(colors.domain()); // -> [1]
    console.log(colors(0), colors(3), colors(7)); // -> #ff7f0e #2ca02c #d62728
    console.log(colors.domain()); // -> [1, 0, 3, 7] 
    

    Only if the given index is not already in the domain does it get added.

    This is why the workaround, as stated in the original question, produced the expected behavior. Stepping through the scale via a for loop queried the scale in natural order, adding ordered indices to the domain.

    var colors = d3.scale.category10();
    
    for (var i = 0; i < 10; i++) {
        colors(i)
    }
    
    console.log(colors.domain()); // -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    The moral of the story is to set the domain explicitly, in order to get more predictable behavior, as shown at the top of this answer.

    0 讨论(0)
提交回复
热议问题