Dinosaurius
Dinosaurius

Reputation: 8628

Calendar heatmap: how to define the range of % for each color instead of using quantiles

I am creating the calendar heatmap using D3.

This is my current code:

<!DOCTYPE html>
<meta charset="utf-8">
<html>
  <head>
    <style>
      rect.bordered {
        stroke: #E6E6E6;
        stroke-width:2px;   
      }

      text.mono {
        font-size: 10pt;
        font-family: Consolas, courier;
        fill: #000;
      }

      text    {
        font-family: Consolas, courier;
        font-size:1.0em;
      }

      text.axis-workweek {
        fill: #000;
      }

      text.axis-worktime {
        fill: #000;
      }
    </style>
    <script src="http://d3js.org/d3.v3.js"></script>
  </head>
  <body>
    <div id="chart"></div>
    </div>
    <script type="text/javascript">
     var title="";
      var margin = { top: 90, right: 0, bottom: 80, left: 30 },
          width = 960 - margin.left - margin.right,
          height = 900 - margin.top - margin.bottom,
          gridSize = Math.floor(width / 42),
          legendElementWidth = gridSize*2,
          buckets = 5,
          colors = ["#c7e9b4","#41b6c4","#1d91c0","#225ea8"], // alternatively colorbrewer.YlGnBu[9]
          days = ["1", "2", "3", "4", "5", "6", "7","8", "9", "10", "11", "12","13", "14", "15", "16", "17","18", "19", "20", 
"21", "22","23", "24", "25", "26", "27","28", "29", "30", "31"],
          times = ["7:00", "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00"];
          datasets = ["data.tsv"];

      var svg = d3.select("#chart").append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      svg.append("text")
        .attr("x",0)
        .attr("y",-40)
        .text(title);

      var dayLabels = svg.selectAll(".dayLabel")
          .data(days)
          .enter().append("text")
            .text(function (d) { return d; })
            .attr("x", 0)
            .attr("y", function (d, i) { return i * gridSize; })
            .style("text-anchor", "end")
            .attr("transform", "translate(-6," + gridSize / 1.5 + ")")
            .attr("class", function (d, i) { return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });

      var timeLabels = svg.selectAll(".timeLabel")
          .data(times)
          .enter().append("text")
            .text(function(d) { return d; })
            .attr("x", function(d, i) { return i * gridSize * 2.1; })
            .attr("y", 0)
            .style("text-anchor", "middle")
            .attr("transform", "translate(" + gridSize / 2 + ", -6)")
            .attr("class", function(d, i) { return ((i >= 7 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });

      var heatmapChart = function(tsvFile) {
        d3.tsv(tsvFile,
        function(d) {
          return {
            day: +d.day,
            hour: +d.hour,
            value: +d.value
          };
        },
        function(error, data) {
          var colorScale = d3.scale.quantile()
              .domain([0, buckets - 1, d3.max(data, function (d) { return d.value; })])
              .range(colors);

          var cards = svg.selectAll(".hour")
              .data(data, function(d) {return d.day+':'+d.hour;});

          cards.append("title");

          cards.enter().append("rect")
              .attr("x", function(d) { return (d.hour - 7) * gridSize * 2.05; })
              .attr("y", function(d) { return (d.day - 1) * gridSize; })
              .attr("rx", 4)
              .attr("ry", 4)
              .attr("class", "hour bordered")
              .attr("width", gridSize*2)
              .attr("height", gridSize)
              .style("fill", colors[0]);

          cards.transition().duration(1000)
              .style("fill", function(d) { return colorScale(d.value); });

          cards.select("title").text(function(d) { return d.value; });

          cards.exit().remove();

          var legend = svg.selectAll(".legend")
              .data([0].concat(colorScale.quantiles()), function(d) { return d; });

          legend.enter().append("g")
              .attr("class", "legend");

          legend.append("rect")
            .attr("x", height)
            .attr("y", function(d, i) { return legendElementWidth * (i-0.5); })
            .attr("width", gridSize / 2 )
            .attr("height", legendElementWidth)
            .style("fill", function(d, i) { return colors[i]; });

          legend.append("text")
            .attr("class", "mono")
            .text(function(d) { return "≥ " + Math.round(d) + "%"; })
            .attr("x", (height) + gridSize)
            .attr("y", function(d, i) { return legendElementWidth*i; } );

          legend.exit().remove();

        });  
      };

      heatmapChart(datasets[0]);

    </script>
  </body>
</html>

And this is the result:

enter image description here

How can I define the range of % manually for each color? In particular, there is a big gap between 4% and 52% because of using quantiles (.data([0].concat(colorScale.quantiles()), function(d) { return d; }). I would like to set >0%, >5%, >10%, >15%, >20%, >25%.

SAMPLE DATA SET (data.tsv):

day hour    value
1   7   0
1   8   22.6829268293
1   9   24.3243243243
1   10  23.3374766936
1   11  21.8156028369
1   12  21.8499851765
1   13  22.1323529412
1   14  19.8275862069
1   15  20.4545454546
1   16  23.7329042638
1   17  23.1717337716
1   18  20.1895734597
1   19  22.7552275523
1   20  22.6277372263
1   21  19.7278911564
2   7   0
2   8   24.1124260355
2   9   25.0368912936
2   10  23.8484398217
2   11  23.6625514403
2   12  23.5748436925
2   13  23.3272227315
2   14  21.8181818182
2   15  20.3499079189
2   16  20.7635009312
2   17  26.4220183486
2   18  21.8934911243
2   19  19.8433420366
2   20  19.8614318706
2   21  24.6031746031

Upvotes: 1

Views: 367

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

You can use 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. The input domain is typically a dimension of the data that you want to visualize, such as the height of students (measured in meters) in a sample population. The output range is typically a dimension of the desired output visualization, such as a set of colors (represented as strings).

But pay attention to this:

If the number of values in the scale's range is N + 1, the number of values in the scale's domain must be N. If there are fewer than N elements in the domain, the additional values in the range are ignored.

This means that, in your case, as the colors array has 4 values, the domain can have only three arbitrary values:

 var colorScale = d3.scale.threshold()
     .domain([value1, value2, value3])
     .range(colors);

Set those values using the percentages you want.

Upvotes: 1

Related Questions