block by bollwyvl 5176743

Collapsible Area Plot Matrix

Full Screen

Shows use of .transition() with .transform()

script.js

// not necessary for a gist, but usually good practice
;(function(d3){
  "use strict";
  // some housecleaning to make sure we're not willing things into existence
  var window = this,
    document = window.document,
    documentElement = document.documentElement;
  
  // the root SVG object
  var svg = d3.select("svg"),
    // the width/height of the document
    width, height,

    // a global for whether rows and/or columns are collapsed
    collapsed = { cols: 0, rows: 0 },

    // x/y scales: (r)ow (c)ell. pretty dynamic, see `update_scales`...
    y_r = d3.scale.linear(),
    x_c = d3.scale.linear(),

    // x/y for path... abusing that domain/range default is [0,1]
    y_p = d3.scale.linear(),
    x_p = d3.scale.linear(),
    // ...and some helpers for path things
    x_p_idx = function(datum, idx) { return x_p(idx); },
    y_p_datum = function(datum) { return y_p(datum); },
      
    // a scale of colors...
    color = d3.scale.category10(),
    
    // ...and a helper to make using it easier
    color_by_id = function(datum, idx){ return color(idx); },

    // a generator for the SVG path `d` attribute that squares off the bottom
    area = d3.svg.area()
      .interpolate("basis")
      .x(x_p_idx)
      .y0(1)
      .y1(y_p_datum),
      
    // a generator for the SVG path `d` attribute
    line = d3.svg.line()
      .interpolate("basis")
      .x(x_p_idx)
      .y(y_p_datum),

    // oh right, we need some data...
    data = make_data();

  // main render function (handles init and update).
  function render(){
    // data/screen size may have changed
    update_scales();

    // an outer SVG `g`... not strictly necessary
    var outer = svg.selectAll("g.outer")
        .data([1]),

      // all these `_init` things are only called when new, un-DOM'd data is 
      // found
      outer_init = outer.enter()
        .append("g")
        .attr("class", "outer"),

      // each of the rows of plots
      rows = outer.selectAll("g.row")
        .data(data),
      row_init = rows.enter()
        .append("g").attr("class", "row")
        // use of d3 call, as we reuse this same stuff in the transition 
        // below...
        .call(tx_row),

      // each of the cells (columns within rows)
      cells = rows.selectAll("g.cell")
        // a little weird: likely, the sub data would be in an attribute: 
        // e.g. row.vals
        .data(function(row){ return row; }),
      cell_init = cells.enter()
        .append("g").attr("class", "cell")
        .call(tx_cell),

        
      // note we don't `selectAll`, as we just want one path per cell... now
      areas = cells.select("path.area"),
      area_init = cell_init.append("path")
        .attr("class", "area")
        .style("fill", color_by_id)
        .attr("d", area),


      lines = cells.select("path.line"),
      line_init = cell_init.append("path")
        .attr("class", "line")
        .style("stroke", color_by_id)
        .attr("d", line)
        // thanks ahaarnos: the old effect was kind of artistic, like
        // calligraphy, but weird
        .attr("vector-effect", "non-scaling-stroke");

    // it's possible that the "shape" of the data changed: this removes hanging
    // DOM elements
    rows.exit().remove();
    cells.exit().remove();


    // all the animation stuff
    areas
      .transition()
      .ease("back")
      // not strictly necessary unless data actually changes
      .attr("d", area);
      
    lines
      .transition()
      .ease("back")
      // not strictly necessary unless data actually changes
      .attr("d", line);


    rows
      .transition()
      .call(tx_row);

    cells
      .transition()
      .call(tx_cell);
  }


  // make some fake data. not great.
  function make_data(){
    var rows = Math.ceil(Math.random() * 10),
      cols = Math.ceil(Math.random() * 10),
      points = Math.ceil(Math.random() * 100);

    // probably a prettier way to do this... gets the point across
    return d3.range(rows).map(function(){
      return d3.range(cols).map(function(){
        return d3.range(points).map(function(){
          return Math.random();
        });
      });
    });
  }


  // change the parts of the scales that are dynamic
  function update_scales(){
    width = documentElement.clientWidth;
    height = documentElement.clientHeight;

    y_r.domain([0, data.length])
      .range([0, height]);

    x_c.domain([0, data[0].length])
      .range([0, width]);

    x_p.domain([0, data[0][0].length - 1]);
  }


  // vanity replacement for tons of string parse to create SVG transform attrs
  function tx(mode){
    return function(x, y){
      return mode + "(" + x + "," + y + ") ";
    };
  }
  tx.t = tx("translate");
  tx.s = tx("scale");


  // transform rows
  function tx_row(rows){
    rows.attr("transform", function(datum, idx){
      if(collapsed.rows){ return tx.t(0, 0) + tx.s(1, height); }
      return tx.t(0, y_r(idx)) + tx.s(1, y_r(1));
    });
  }


  // transform the columns (cells) within rows
  function tx_cell(cells){
    cells.attr("transform", function(datum, idx){
      if(collapsed.cols){ return tx.t(0, 0) + tx.s(width, 1); }
      return tx.t(x_c(idx), 0) + tx.s(x_c(1), 1);
    });
  }


  // event handlers.
  d3.selectAll("button").on("click", function(){
    var id = d3.select(this).attr("id").split("_");
    switch(id[0]){
      case "collapse": collapsed[id[1]] = !collapsed[id[1]]; break;
      case "makedata": data = make_data(); break;
    }
    render();
  });

  d3.select(window).on("resize", render);


  // actually render everything.
  render();
}).call(this, d3);

index.html

<html>
  <head>
    <style>
    html, body{
      overflow: hidden;
      padding: 0;
      margin: 0;
    }
    path.line{
      stroke-width: 2;
      fill: transparent;
    }
    path.area{
      fill-opacity: 0.2;
    }
    #controls{
      position:fixed;
    }
    </style>
  </head>
  <body>
    <div id="controls">
      <button id="collapse_cols">Collapse Columns</button>
      <button id="collapse_rows">Collapse Rows</button>
      <button id="makedata">New Data</button>
    </div>
    <!-- Firefox doesn't assume the SVG is as big as its contents -->
    <svg width="100%" height="100%"></svg>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script type="text/javascript" charset="utf-8" src="script.js"></script>
  </body>
</html>