<body>
    <div id="chart"></div>
    <div id="dataset-picker">
    </div>
</body>
rect.bordered {
        stroke: #E6E6E6;
        stroke-width:2px;
      }

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

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

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


      .d3-tip {
        line-height: 1;
        font-weight: bold;
        padding: 12px;
        background: rgba(0, 0, 0, 0.8);
        color: #fff;
        border-radius: 2px;
      }

      .d3-tip.n:after {
        margin: -1px 0 0 0;
        top: 100%;
        left: 0;
      }
var margin = { top: 50, right: 0, bottom: 100, left: 30 },
          width = 960 - margin.left - margin.right,
          height = 430 - margin.top - margin.bottom,
          gridSize = Math.floor(width / 24),
          legendElementWidth = gridSize*2,
          buckets = 9,
          colors = ["#07b20f", "#d8d849", "#c4411d"],
          days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
          times = ["1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12a", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p", "12p"];
          datasets = ["https://gist.githubusercontent.com/rplevka/349604e99f8c9e5e40d1/raw/91bf520cd419313b9b2762746d216bbfb3c22b19/vyskov-brno.json",
                      "https://gist.githubusercontent.com/rplevka/1ef67383100f13157973/raw/38be1e10779050eead3143187d05bc4dfb4936aa/vyskov-red_hat_no_tolls"];

      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 + ")");


      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; })
            .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 tip = d3.tip()
          .attr('class', 'd3-tip')
          .offset([-10, 0])
          .html(function(d) {
            return "<strong>Trip time:</strong> <span style='color:#ccc'>" + d.time + "m</span>";
          })

      svg.call(tip);

      var heatmapChart = function(file) {
        d3.json(file, function(error, data) {
          min_time = d3.min(data, function(day){
            return parseFloat(d3.min(day.times, function(hour){return hour.time}))});
          max_time = d3.max(data, function(day){
            return parseFloat(d3.max(day.times, function(hour){return hour.time}))});
          var colorScale = d3.scale.linear()
              .domain([min_time, (min_time+max_time)/2, max_time])
              .range(colors);
          for(day of data){
            var cards = svg.selectAll(".hour "+".day"+day.day)
                .data(day.times, function(d) {
                     return day.day + ':' + d.hour;
                })

            cards.append("title");
            console.log(day);
              cards.enter().append("rect")
                  .attr("x", function(d) { return d.hour * gridSize; })
                  .attr("y", function(d) { return day.day * gridSize; })
                  .attr("rx", 4)
                  .attr("ry", 4)
                  .attr("class", "hour bordered")
                  .attr("width", gridSize)
                  .attr("height", gridSize)
                  .style("fill", colors[0])
                  .on('mouseover', tip.show)
                  .on('mouseout', tip.hide);

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

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

              cards.exit().remove();
            var legend = svg.selectAll(".legend")
                //.data([min_time].concat(colorScale.domain()), function(d) { return d; });
                .data(colorScale.domain(), function(d) { return d; });

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

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

            legend.exit().remove();
          }
        });
      };

      heatmapChart(datasets[0]);

      var datasetpicker = d3.select("#dataset-picker").selectAll(".dataset-button")
        .data(datasets);

      datasetpicker.enter()
        .append("input")
        .attr("value", function(d){ return "Dataset " + d })
        .attr("type", "button")
        .attr("class", "dataset-button")
        .on("click", function(d) {
          heatmapChart(d);
        });

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://d3js.org/d3.v3.js
  2. http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js