Search code examples
javascriptd3.jscolorsbubble-chart

Assign colors to bubbles based on percentage value coming from input


I have a bubble chart in which I have to assign colors to different bubbles based on 'admit_probability' coming from input. I have used 4 different ranges from 'red' to 'blue' and my admit_probailites lie from 0 to 100.So, I want to assign colors such that bubbles with lower admit_probability have "red" color and high ones have "blue" color and there is a gradual changes of colors.

Following is my data:

var data = [{name: "A", rank: 1, student_percentile: 100.0, 
     admit_probability: 24},
    {name: "B", rank: 45, student_percentile: 40.3, 
     admit_probability: 24},
    {name: "C", rank: 89, student_percentile: 89.7, 
     admit_probability: 24},
    {name: "D", rank: 23, student_percentile: 10.9, 
     admit_probability: 24},
    {name: "E", rank: 56, student_percentile: 30.3, 
     admit_probability: 24},
     {name: "F", rank: 34, student_percentile: 110, 
     admit_probability: 84}];


var color_range = ['#FF0000', '#FFFF00' ,'#008000', '#0000FF'];
var color = d3.scaleLinear()
                .domain([0, d3.max(data, function (d) {
                return +d.admit_probability;
            })])
            .range(color_range);

circles = svg.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("opacity", 0.3)
      .attr("r", 20)
      .style("fill", function(d, i){
            return color(i);
      });

When I use the above code, it gives me, only diferent shades of red color and no green, yellow or blue colored bubbles are formed. I am not able to find the issue.

PS: I don't want to use the following solution:

function(d){
            if(+d.admit_probability <= 40){
                return "red";
            }
            else if(+d.admit_probability > 40 && +d.admit_probability <= 75){
                return "yellow";
            }
            else{
                return "green";
            }
      }

Solution

  • A linear scale will interpolate between values in the range. You want a threshold scale:

    Threshold scales are similar to quantize scales, except they allow you to map arbitrary subsets of the domain to discrete values in the range. The input domain is still continuous, and divided into slices based on a set of threshold values (API documentation)

    This looks like:

    var color = d3.scaleTreshold()
                    .domain(thresholds)
                    .range(colors)
    

    Where thresholds is an array holding the thresholds, and colors an array holding colors (in this case). Assuming a very basic threshold graph with two thresholds, you will need three colors: one for below the first threshold, one for between the two thresholds, and one for above the second threshold.

    For your data, with four colors, you would need three thresholds (I'm not sure what they are, as your second code block only has two), something like:

    var color = d3.scaleThreshold()
                    .domain([40,75,90])
                    .range(["red","yellow","green","blue"]);
    

    Where values up to 40 are red, those over 40 and up to 75 yellow, those over 75 and up to 90 green, while those over 90 are blue.

    Example:

    var data = d3.range(100);
    
    var color = d3.scaleThreshold()
      .domain([40,75,90])
      .range(["red","yellow","green","blue"]);
      
    var svg = d3.select("body")
      .append("svg")
      .attr("width",400)
      .attr("height",200);
      
    var rects = svg.selectAll("rect")
      .data(data)
      .enter()
      .append("rect")
      .attr("height",10)
      .attr("width",10)
      .attr("y",function(d,i) { return Math.floor(i/10) * 12 + 10; })
      .attr("x",function(d,i) { return i%10 * 12 + 10; })
      .attr("fill", function(d) { return color(d); })
      
      
      
      
                        
        
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>