block by harrystevens 302d078a089caf5aeb13e480b86fdaeb

Correlation Matrix

Full Screen

Here’s some reusable code to compute and draw a correlation matrix from a set of data in JavaScript. Here’s how it works:

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
  body {
    margin: 0 auto;
    display: table;
    font-family: "Helvetica Neue", sans-serif;
  }
  rect.selected {
    stroke: #000;
    stroke-width: 2px;
  }
  .axis .domain {
    display: none;
  }
  .axis .tick text.selected {
    font-weight: bold;
    font-size: 1.2em;
    fill: #47ff63;
  }
  .axis .tick line.selected {
    stroke: #47ff63;  
  }
  .tip {
    position: absolute;
    font-size: .8em;
    text-align: center;
    text-shadow: -1px -1px 1px #ffffff, -1px 0px 1px #ffffff, -1px 1px 1px #ffffff, 0px -1px 1px #ffffff, 0px 1px 1px #ffffff, 1px -1px 1px #ffffff, 1px 0px 1px #ffffff, 1px 1px 1px #ffffff;
  }    

  #legend {
    margin-bottom: 10px;
  }
  #legend text {
    font-size: .8em;
  }
  </style>
</head>
<body>
  <div id="legend"></div>
  <div id="grid"></div>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://unpkg.com/jeezy@1.12.11/lib/jeezy.min.js"></script>
  <script src="https://unpkg.com/data2grid@1.0.0/build/data2grid.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.5/chroma.min.js"></script>
  <script>
  d3.select("body").append("div").attr("class", "tip").style("display", "none");

  var data = [];
  var cols = "abcdefghijklmnopqrstuvwxyz".split("");
  for (var i = 0; i <= 30; i++){
    var obj = {index: i};
    cols.forEach(col => {
      obj[col] = jz.num.randBetween(1, 100);
    });
    data.push(obj);
  }
  var corr = jz.arr.correlationMatrix(data, cols);
  var extent = d3.extent(corr.map(function(d){ return d.correlation; }).filter(function(d){ return d !== 1; }));

  var grid = data2grid.grid(corr);
  var rows = d3.max(grid, function(d){ return d.row; });

  var margin = {top: 20, bottom: 1, left: 20, right: 1};

  var dim = d3.min([window.innerWidth * .9, window.innerHeight * .9]);

  var width = dim - margin.left - margin.right, height = dim - margin.top - margin.bottom;

  var svg = d3.select("#grid").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 padding = .1;

  var x = d3.scaleBand()
    .range([0, width])
    .paddingInner(padding)
    .domain(d3.range(1, rows + 1));

  var y = d3.scaleBand()
    .range([0, height])
    .paddingInner(padding)
    .domain(d3.range(1, rows + 1));

  var c = chroma.scale(["tomato", "white", "steelblue"])
    .domain([extent[0], 0, extent[1]]);

  var x_axis = d3.axisTop(y).tickFormat(function(d, i){ return cols[i]; });
  var y_axis = d3.axisLeft(x).tickFormat(function(d, i){ return cols[i]; });

  svg.append("g")
      .attr("class", "x axis")
      .call(x_axis);

  svg.append("g")
      .attr("class", "y axis")
      .call(y_axis);

  svg.selectAll("rect")
      .data(grid, function(d){ return d.column_a + d.column_b; })
    .enter().append("rect")
      .attr("x", function(d){ return x(d.column); })
      .attr("y", function(d){ return y(d.row); })
      .attr("width", x.bandwidth())
      .attr("height", y.bandwidth())
      .style("fill", function(d){ return c(d.correlation); })
      .style("opacity", 1e-6)
    .transition()
      .style("opacity", 1);

  svg.selectAll("rect")

  d3.selectAll("rect")
    .on("mouseover", function(d){

      d3.select(this).classed("selected", true);

      d3.select(".tip")
          .style("display", "block")
          .html(d.column_x + ", " + d.column_y + ": " + d.correlation.toFixed(2));

      var row_pos = y(d.row);
      var col_pos = x(d.column);
      var tip_pos = d3.select(".tip").node().getBoundingClientRect();
      var tip_width = tip_pos.width;
      var tip_height = tip_pos.height;
      var grid_pos = d3.select("#grid").node().getBoundingClientRect();
      var grid_left = grid_pos.left;
      var grid_top = grid_pos.top;

      var left = grid_left + col_pos + margin.left + (x.bandwidth() / 2) - (tip_width / 2);
      var top = grid_top + row_pos + margin.top - tip_height - 5;

      d3.select(".tip")
          .style("left", left + "px")
          .style("top", top + "px");

      d3.select(".x.axis .tick:nth-of-type(" + d.column + ") text").classed("selected", true);
      d3.select(".y.axis .tick:nth-of-type(" + d.row + ") text").classed("selected", true);
      d3.select(".x.axis .tick:nth-of-type(" + d.column + ") line").classed("selected", true);
      d3.select(".y.axis .tick:nth-of-type(" + d.row + ") line").classed("selected", true);

    })
    .on("mouseout", function(){
      d3.selectAll("rect").classed("selected", false);
      d3.select(".tip").style("display", "none");
      d3.selectAll(".axis .tick text").classed("selected", false);
      d3.selectAll(".axis .tick line").classed("selected", false);
    });

  // legend scale
  var legend_top = 15;
  var legend_height = 15;

  var legend_svg = d3.select("#legend").append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", legend_height + legend_top)
    .append("g")
      .attr("transform", "translate(" + margin.left + ", " + legend_top + ")");

  var defs = legend_svg.append("defs");

  var gradient = defs.append("linearGradient")
      .attr("id", "linear-gradient");

  var stops = [{offset: 0, color: "tomato", value: extent[0]}, {offset: .5, color: "white", value: 0}, {offset: 1, color: "steelblue", value: extent[1]}];
  
  gradient.selectAll("stop")
      .data(stops)
    .enter().append("stop")
      .attr("offset", function(d){ return (100 * d.offset) + "%"; })
      .attr("stop-color", function(d){ return d.color; });

  legend_svg.append("rect")
      .attr("width", width)
      .attr("height", legend_height)
      .style("fill", "url(#linear-gradient)");

  legend_svg.selectAll("text")
      .data(stops)
    .enter().append("text")
      .attr("x", function(d){ return width * d.offset; })
      .attr("dy", -3)
      .style("text-anchor", function(d, i){ return i == 0 ? "start" : i == 1 ? "middle" : "end"; })
      .text(function(d, i){ return d.value.toFixed(2) + (i == 2 ? ">" : ""); })

  </script>

</body>
</html>