{{ message }}

Instantly share code, notes, and snippets.

# mbostock/.block

Last active Feb 26, 2019
Horizon Chart

Horizon charts combine position and color to reduce vertical space. Start with a standard area chart, then mirror negative values (in blue) or offset them vertically. Click the + button above to increase the number of bands, turning the area into a horizon.

Implemented with the d3.horizon plugin.

 (function() { d3.horizon = function() { var bands = 1, // between 1 and 5, typically mode = "offset", // or mirror interpolate = "linear", // or basis, monotone, step-before, etc. x = d3_horizonX, y = d3_horizonY, w = 960, h = 40, duration = 0; var color = d3.scale.linear() .domain([-1, 0, 0, 1]) .range(["#08519c", "#bdd7e7", "#bae4b3", "#006d2c"]); // For each small multiple… function horizon(g) { g.each(function(d, i) { var g = d3.select(this), n = 2 * bands + 1, xMin = Infinity, xMax = -Infinity, yMax = -Infinity, x0, // old x-scale y0, // old y-scale id; // unique id for paths // Compute x- and y-values along with extents. var data = d.map(function(d, i) { var xv = x.call(this, d, i), yv = y.call(this, d, i); if (xv < xMin) xMin = xv; if (xv > xMax) xMax = xv; if (-yv > yMax) yMax = -yv; if (yv > yMax) yMax = yv; return [xv, yv]; }); // Compute the new x- and y-scales, and transform. var x1 = d3.scale.linear().domain([xMin, xMax]).range([0, w]), y1 = d3.scale.linear().domain([0, yMax]).range([0, h * bands]), t1 = d3_horizonTransform(bands, h, mode); // Retrieve the old scales, if this is an update. if (this.__chart__) { x0 = this.__chart__.x; y0 = this.__chart__.y; t0 = this.__chart__.t; id = this.__chart__.id; } else { x0 = x1.copy(); y0 = y1.copy(); t0 = t1; id = ++d3_horizonId; } // We'll use a defs to store the area path and the clip path. var defs = g.selectAll("defs") .data([null]); // The clip path is a simple rect. defs.enter().append("defs").append("clipPath") .attr("id", "d3_horizon_clip" + id) .append("rect") .attr("width", w) .attr("height", h); defs.select("rect").transition() .duration(duration) .attr("width", w) .attr("height", h); // We'll use a container to clip all horizon layers at once. g.selectAll("g") .data([null]) .enter().append("g") .attr("clip-path", "url(#d3_horizon_clip" + id + ")"); // Instantiate each copy of the path with different transforms. var path = g.select("g").selectAll("path") .data(d3.range(-1, -bands - 1, -1).concat(d3.range(1, bands + 1)), Number); var d0 = d3_horizonArea .interpolate(interpolate) .x(function(d) { return x0(d[0]); }) .y0(h * bands) .y1(function(d) { return h * bands - y0(d[1]); }) (data); var d1 = d3_horizonArea .x(function(d) { return x1(d[0]); }) .y1(function(d) { return h * bands - y1(d[1]); }) (data); path.enter().append("path") .style("fill", color) .attr("transform", t0) .attr("d", d0); path.transition() .duration(duration) .style("fill", color) .attr("transform", t1) .attr("d", d1); path.exit().transition() .duration(duration) .attr("transform", t1) .attr("d", d1) .remove(); // Stash the new scales. this.__chart__ = {x: x1, y: y1, t: t1, id: id}; }); d3.timer.flush(); } horizon.duration = function(x) { if (!arguments.length) return duration; duration = +x; return horizon; }; horizon.bands = function(x) { if (!arguments.length) return bands; bands = +x; color.domain([-bands, 0, 0, bands]); return horizon; }; horizon.mode = function(x) { if (!arguments.length) return mode; mode = x + ""; return horizon; }; horizon.colors = function(x) { if (!arguments.length) return color.range(); color.range(x); return horizon; }; horizon.interpolate = function(x) { if (!arguments.length) return interpolate; interpolate = x + ""; return horizon; }; horizon.x = function(z) { if (!arguments.length) return x; x = z; return horizon; }; horizon.y = function(z) { if (!arguments.length) return y; y = z; return horizon; }; horizon.width = function(x) { if (!arguments.length) return w; w = +x; return horizon; }; horizon.height = function(x) { if (!arguments.length) return h; h = +x; return horizon; }; return horizon; }; var d3_horizonArea = d3.svg.area(), d3_horizonId = 0; function d3_horizonX(d) { return d[0]; } function d3_horizonY(d) { return d[1]; } function d3_horizonTransform(bands, h, mode) { return mode == "offset" ? function(d) { return "translate(0," + (d + (d < 0) - bands) * h + ")"; } : function(d) { return (d < 0 ? "scale(1,-1)" : "") + "translate(0," + (d - bands) * h + ")"; }; } })();

1
 {"year":[2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2001,2001,2001,2001,2001,2001,2001,2001,2001,2001,2001,2001,2002,2002,2002,2002,2002,2002,2002,2002,2002,2002,2002,2002,2003,2003,2003,2003,2003,2003,2003,2003,2003,2003,2003,2003,2004,2004,2004,2004,2004,2004,2004,2004,2004,2004,2004,2004,2005,2005,2005,2005,2005,2005,2005,2005,2005,2005,2005,2005,2006,2006,2006,2006,2006,2006,2006,2006,2006,2006,2006,2006,2007,2007,2007,2007,2007,2007,2007,2007,2007,2007,2007,2007,2008,2008,2008,2008,2008,2008,2008,2008,2008,2008,2008,2008,2009,2009,2009,2009,2009,2009,2009,2009,2009,2009,2009,2009,2010,2010],"month":[1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8,9,10,11,12,1,2],"rate":[4.5,4.4,4.3,3.7,3.8,4.1,4.2,4.1,3.8,3.6,3.7,3.7,4.7,4.6,4.5,4.2,4.1,4.7,4.7,4.9,4.7,5,5.3,5.4,6.3,6.1,6.1,5.7,5.5,6,5.9,5.7,5.4,5.3,5.6,5.7,6.5,6.4,6.2,5.8,5.8,6.5,6.3,6,5.8,5.6,5.6,5.4,6.3,6,6,5.4,5.3,5.8,5.7,5.4,5.1,5.1,5.2,5.1,5.7,5.8,5.4,4.9,4.9,5.2,5.2,4.9,4.8,4.6,4.8,4.6,5.1,5.1,4.8,4.5,4.4,4.8,5,4.6,4.4,4.1,4.3,4.3,5,4.9,4.5,4.3,4.3,4.7,4.9,4.6,4.5,4.4,4.5,4.8,5.4,5.2,5.2,4.8,5.2,5.7,6,6.1,6,6.1,6.5,7.1,8.5,8.9,9,8.6,9.1,9.7,9.7,9.6,9.5,9.5,9.4,9.7,10.6,10.4]}

### dimerman commented Aug 8, 2013

 is there a way to set the y-axis domain for the horizon chart ? yMax is calculated on lines 34:35 and used in :41 but I see no interface to set it.