block by syntagmatic 3201108

Scatterplot (Canvas)

Full Screen

index.html

<!doctype html>

<link rel="stylesheet" type="text/css" href="style.css" />

<h1>Nutrient Scatterplot (Canvas)</h1>
<canvas id="chart"></canvas>
<div id="controls">
  <span id="hover-food"></span><br/>
  X axis <select id="xaxis"></select><br/>
  Y axis <select id="yaxis"></select><br/>
</div>

<script src="//mbostock.github.com/d3/d3.v2.js"></script>
<script src="//underscorejs.org/underscore.js"></script>
<script src="scatter.js"></script>

scatter.js

// select svg canvas
var m = [30, 10, 10, 10],       // margins
    w = 500,                    // width
    h = 400,                    // height
    dimensions = [],            // quantitative dimensions
    xcol = 0,                   // active x column
    ycol = 1,                   // active y column
    last = [],                  // last [x,y,color] pairs
    transition_count = 0,       // used to cancel old transitions
    xscale = d3.scale.linear(), // x scale
    yscale = d3.scale.linear(); // yscale 

// color scale
var color = {
  "Baby Foods"                        : '#555555',
  "Baked Products"                    : '#7f7f7f',
  "Beverages"                         : '#c49c94',
  "Breakfast Cereals"                 : '#9467bd',
  "Cereal Grains and Pasta"           : '#bcbd22',
  "Dairy and Egg Products"            : '#ff7f0e',
  "Ethnic Foods"                      : '#e7ba52',
  "Fast Foods"                        : '#dbdb8d',
  "Fats and Oils"                     : '#ffbb78',
  "Finfish and Shellfish Products"    : '#e377c2',
  "Fruits and Fruit Juices"           : '#c5b0d5',
  "Legumes and Legume Products"       : '#f7b6d2',
  "Meals, Entrees, and Sidedishes"    : '#17becf',
  "Nut and Seed Products"             : '#8c564b',
  "Pork Products"                     : "#00ee99",
  "Poultry Products"                  : '#d62728',
  "Restaurant Foods"                  : '#1f77b4',
  "Sausages and Luncheon Meats"       : '#ff9896',
  "Snacks"                            : '#9edae5',
  "Soups, Sauces, and Gravies"        : '#98df8a',
  "Spices and Herbs"                  : '#aec7e8',
  "Sweets"                            : '#c7c7c7',
  "Vegetables and Vegetable Products" : '#2ca02c'
};

// adjust canvas size
canvas = d3.select("#chart")
  .attr("width", w + "px")
  .attr("height", h + "px");

// rendering context
ctx = canvas[0][0].getContext('2d');
ctx.strokeStyle = "rgba(0,0,0,0.8)";
ctx.lineWidth = "1.5";

// load data from csv file
d3.csv('nutrients.csv', function(data) {

  // get columns of csv, mark excluded columns
  var columns = d3.keys( data[0] ),
      excluded = ['name', 'group', 'id'];

  // get quantitative dimensions
  dimensions = _(columns)
    .difference(excluded);

  // extents for each dimension
  var extents = _(dimensions)
    .map(function(col) {
      return [0, d3.max(data, function(d) { return parseFloat(d[col]) })]
    });

  // create scales
  xscale.domain(extents[xcol]).range([m[3], w-m[1]]),
  yscale.domain(extents[ycol]).range([h-m[2], m[0]]);

  // render initial data points
  last = data.map(position)
  last.forEach(circle);

  // change x axis
  function xaxis(i) {
    xcol = i;
    xscale.domain(extents[i]);
    transition(++transition_count);
  };

  // change y axis
  function yaxis(i) {
    ycol = i;
    yscale.domain(extents[i]);
    transition(++transition_count);
  };

  // create dropdowns to change axes
  d3.select("#xaxis")
    .selectAll("option")
    .data(dimensions)
    .enter().append("option")
      .attr("value", function(d,i) { return i; })
      .text(function(d) { return d; })
      .each(function(d,i) {
        if (i == xcol) d3.select(this).attr("selected", "yes");
      });

  d3.select("#xaxis")
      .on("change", function() { xaxis(this.selectedIndex) });

  d3.select("#yaxis")
    .selectAll("option")
    .data(dimensions)
    .enter().append("option")
      .attr("value", function(d,i) { return i; })
      .text(function(d) { return d; })
      .each(function(d,i) {
        if (i == ycol) d3.select(this).attr("selected", "yes");
      });

  d3.select("#yaxis")
      .on("change", function() { yaxis(this.selectedIndex) });

  window.data = data;
});

function transition(count) {
  // next positions
  var next = data.map(position);

  var transition = d3.interpolate(last, next);

  // run transition
  d3.timer(function(t) {
    // abort old transition
    if (count < transition_count) return true;

    clear();
    if (t > 1000) {
      last = next;
      last.forEach(circle);
      return true
    };
    last = transition(t/1000);
    last.forEach(circle);
  });
};

// clear canvas
function clear() {
  ctx.clearRect(0,0,w,h);
};

// from data point, return [x,y,color]
function position(d) {
  var x = xscale(d[dimensions[xcol]]);
  var y = yscale(d[dimensions[ycol]]);
  return [x,y,color[d.group]];
};

// render circle [x,y,color]
function circle(pos) {
  ctx.fillStyle = pos[2];
  ctx.beginPath();
  ctx.arc(pos[0],pos[1],2,0,2*Math.PI);
  ctx.stroke();
  ctx.fill();
};

style.css

body {
  background: #222;
  color: #eee;
  font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: 8px;
}
circle {
  stroke-width: 1px;
  stroke: #111;
  stroke-opacity: 0.5;
}
#chart {
  width: 500px;
  height: 400px;
  background: #151515;
  border-radius: 4px;
  border: 1px solid #333;
  float: left;
  clear: left;
}
#controls {
  float: left;
  margin: 0 20px;
}