block by syntagmatic 2411910

Canvas Parallel Coordinates - 7637 Rows

Full Screen

An example of an SVG interaction layer over a canvas rendering layer. This example has 7637 data points from the USDA Nutrition Dataset. Uses requestAnimationFrame to prevent path rendering from locking up the UI.

Interactions done with the brush component

Based on d3.js Parallel Coordinates

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <title>Canvas Parallel Coordinates</title>
    <style type="text/css">

svg {
  font: 10px sans-serif;
}

canvas, svg {
  position: absolute;
  top: 0;
  left: 0;
}

#chart {
  position: relative;
}

.brush .extent {
  fill-opacity: .3;
  stroke: #fff;
  shape-rendering: crispEdges;
}

.axis line, .axis path {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.axis text {
  text-shadow: 0 1px 0 #fff;
}

    </style>
  </head>
  <body>
    <div id="chart">
      <canvas id="foreground"></canvas>
      <svg></svg>
    </div>
    <script type="text/javascript" src="//mbostock.github.com/d3/d3.v2.js"></script>
    <script type="text/javascript">

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
  return window.requestAnimationFrame       ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame    ||
         window.oRequestAnimationFrame      ||
         window.msRequestAnimationFrame     ||
         function( callback ){
           window.setTimeout(callback, 1000 / 60);
         };
})();

var m = [30, 10, 10, 10],
    w = 960 - m[1] - m[3],
    h = 340 - m[0] - m[2];

var x = d3.scale.ordinal().rangePoints([0, w], 1),
    y = {};

var line = d3.svg.line(),
    axis = d3.svg.axis().orient("left"),
    foreground,
    dimensions,
    brush_count = 0;

var colors = {
  "Dairy and Egg Products": "hsla(28.13278008298755,100%,52.74509803921569%,0.12)",
  "Spices and Herbs": "hsla(214.13793103448276,55.769230769230745%,79.6078431372549%,0.12)",
  "Baby Foods": "hsla(0,0%,33.33333333333333%,0.12)",
  "Fats and Oils": "hsla(29.77777777777777,100%,73.52941176470588%,0.12)",
  "Poultry Products": "hsla(359.6571428571429,69.1699604743083%,49.6078431372549%,0.12)",
  "Soups, Sauces, and Gravies": "hsla(110.11764705882352,57.04697986577182%,70.7843137254902%,0.12)",
  "Vegetables and Vegetable Products": "hsla(120,56.86274509803921%,40%,0.12)",
  "Sausages and Luncheon Meats": "hsla(1.1428571428571388,100%,79.41176470588236%,0.12)",
  "Breakfast Cereals": "hsla(271.3953488372093,39.44954128440367%,57.25490196078431%,0.12)",
  "Fruits and Fruit Juices": "hsla(274.05405405405406,30.57851239669423%,76.27450980392156%,0.12)",
  "Nut and Seed Products": "hsla(10.153846153846157,30.23255813953488%,42.156862745098046%,0.12)",
  "Beverages": "hsla(10,28.915662650602407%,67.45098039215686%,0.12)",
  "Finfish and Shellfish Products": "hsla(318.3333333333333,65.85365853658534%,67.84313725490196%,0.12)",
  "Legumes and Legume Products": "hsla(334.15384615384613,80.24691358024694%,84.11764705882354%,0.12)",
  "Baked Products": "hsla(0,0%,49.80392156862745%,0.12)",
  "Sweets": "hsla(0,0%,78.03921568627452%,0.12)",
  "Cereal Grains and Pasta": "hsla(0,0%,0%,0.12)",
  "Fast Foods": "hsla(60,51.99999999999999%,70.58823529411764%,0.12)",
  "Meals, Entrees, and Sidedishes": "hsla(185.54347826086956,80%,45.09803921568628%,0.12)",
  "Snacks": "hsla(189.29577464788733,57.72357723577235%,75.88235294117646%,0.12)",
  "Ethnic Foods": "hsla(41.87919463087248,75.63451776649744%,61.372549019607845%,0.12)",
  "Restaurant Foods": "hsla(204.56375838926175,70.61611374407583%,41.372549019607845%,0.12)"
};

d3.selectAll("canvas")
    .attr("width", w + m[1] + m[3])
    .attr("height", h + m[0] + m[2])
    .style("padding", m.join("px ") + "px");

foreground = document.getElementById('foreground').getContext('2d');

foreground.strokeStyle = "rgba(0,100,160,0.1)";

var svg = d3.select("svg")
    .attr("width", w + m[1] + m[3])
    .attr("height", h + m[0] + m[2])
  .append("svg:g")
    .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

d3.csv("nutrients.csv", function(data) {

  // Extract the list of dimensions and create a scale for each.
  x.domain(dimensions = d3.keys(data[0]).filter(function(d) {
    return d != "name" && d != "group" &&(y[d] = d3.scale.linear()
        .domain(d3.extent(data, function(p) { return +p[d]; }))
        .range([h, 0]));
  }));

  // Render full foreground
  paths(data, foreground, brush_count);

  // Add a group element for each dimension.
  var g = svg.selectAll(".dimension")
      .data(dimensions)
    .enter().append("svg:g")
      .attr("class", "dimension")
      .attr("transform", function(d) { return "translate(" + x(d) + ")"; });

  // Add an axis and title.
  g.append("svg:g")
      .attr("class", "axis")
      .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
    .append("svg:text")
      .attr("text-anchor", "middle")
      .attr("y", -9)
      .text(String);

  // Add and store a brush for each axis.
  g.append("svg:g")
      .attr("class", "brush")
      .each(function(d) { d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brush", brush)); })
    .selectAll("rect")
      .attr("x", -12)
      .attr("width", 24);

  // Handles a brush event, toggling the display of foreground lines.
  function brush() {
    brush_count++;
    var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
        extents = actives.map(function(p) { return y[p].brush.extent(); });

    // Get lines within extents
    var selected = [];
    data.map(function(d) {
      return actives.every(function(p, i) {
        return extents[i][0] <= d[p] && d[p] <= extents[i][1];
      }) ? selected.push(d) : null;
    });

    // Render selected lines
    foreground.clearRect(0,0,w+1,h+1);
    paths(selected, foreground, brush_count);
  }

  function paths(data, ctx, count) {
    var n = data.length,
        i = 0,
        reset = false;
    function render() {
      var max = d3.min([i+60, n]);
      data.slice(i,max).forEach(function(d) {
        path(d, foreground, colors[d.group]);
      });
      i = max;
    };
    (function animloop(){
      if (i >= n || count < brush_count) return;
      requestAnimFrame(animloop);
      render();
    })();
  };
});


function path(d, ctx, color) {
  if (color) ctx.strokeStyle = color;
  ctx.beginPath();
  dimensions.map(function(p,i) {
    if (i == 0) {
      ctx.moveTo(x(p),y[p](d[p]));
    } else { 
      ctx.lineTo(x(p),y[p](d[p]));
    }
  });
  ctx.stroke();
};

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