// 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);