{"url":"https://api.github.com/gists/1483226","forks_url":"https://api.github.com/gists/1483226/forks","commits_url":"https://api.github.com/gists/1483226/commits","id":"1483226","node_id":"MDQ6R2lzdDE0ODMyMjY=","git_pull_url":"https://gist.github.com/1483226.git","git_push_url":"https://gist.github.com/1483226.git","html_url":"https://gist.github.com/mbostock/1483226","files":{".block":{"filename":".block","type":"text/plain","language":null,"raw_url":"https://gist.githubusercontent.com/mbostock/1483226/raw/c17bffd01455d1816e09ffd3dca31a8e96ee8f8a/.block","size":73,"truncated":false,"content":"license: gpl-3.0\nredirect: https://observablehq.com/@d3/d3-horizon-chart\n"},"README.md":{"filename":"README.md","type":"text/markdown","language":"Markdown","raw_url":"https://gist.githubusercontent.com/mbostock/1483226/raw/abe865cdc32d461dd7354b725dda2a3038c773fb/README.md","size":366,"truncated":false,"content":"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.\n\nImplemented with the [d3.horizon](https://github.com/d3/d3-plugins/tree/master/horizon) plugin."},"horizon.js":{"filename":"horizon.js","type":"text/javascript","language":"JavaScript","raw_url":"https://gist.githubusercontent.com/mbostock/1483226/raw/468c14cd5ed5378201c7b8b429f4f11f5ea6aa59/horizon.js","size":5293,"truncated":false,"content":"(function() {\n d3.horizon = function() {\n var bands = 1, // between 1 and 5, typically\n mode = \"offset\", // or mirror\n interpolate = \"linear\", // or basis, monotone, step-before, etc.\n x = d3_horizonX,\n y = d3_horizonY,\n w = 960,\n h = 40,\n duration = 0;\n\n var color = d3.scale.linear()\n .domain([-1, 0, 0, 1])\n .range([\"#08519c\", \"#bdd7e7\", \"#bae4b3\", \"#006d2c\"]);\n\n // For each small multiple…\n function horizon(g) {\n g.each(function(d, i) {\n var g = d3.select(this),\n n = 2 * bands + 1,\n xMin = Infinity,\n xMax = -Infinity,\n yMax = -Infinity,\n x0, // old x-scale\n y0, // old y-scale\n id; // unique id for paths\n\n // Compute x- and y-values along with extents.\n var data = d.map(function(d, i) {\n var xv = x.call(this, d, i),\n yv = y.call(this, d, i);\n if (xv < xMin) xMin = xv;\n if (xv > xMax) xMax = xv;\n if (-yv > yMax) yMax = -yv;\n if (yv > yMax) yMax = yv;\n return [xv, yv];\n });\n\n // Compute the new x- and y-scales, and transform.\n var x1 = d3.scale.linear().domain([xMin, xMax]).range([0, w]),\n y1 = d3.scale.linear().domain([0, yMax]).range([0, h * bands]),\n t1 = d3_horizonTransform(bands, h, mode);\n\n // Retrieve the old scales, if this is an update.\n if (this.__chart__) {\n x0 = this.__chart__.x;\n y0 = this.__chart__.y;\n t0 = this.__chart__.t;\n id = this.__chart__.id;\n } else {\n x0 = x1.copy();\n y0 = y1.copy();\n t0 = t1;\n id = ++d3_horizonId;\n }\n\n // We'll use a defs to store the area path and the clip path.\n var defs = g.selectAll(\"defs\")\n .data([null]);\n\n // The clip path is a simple rect.\n defs.enter().append(\"defs\").append(\"clipPath\")\n .attr(\"id\", \"d3_horizon_clip\" + id)\n .append(\"rect\")\n .attr(\"width\", w)\n .attr(\"height\", h);\n\n defs.select(\"rect\").transition()\n .duration(duration)\n .attr(\"width\", w)\n .attr(\"height\", h);\n\n // We'll use a container to clip all horizon layers at once.\n g.selectAll(\"g\")\n .data([null])\n .enter().append(\"g\")\n .attr(\"clip-path\", \"url(#d3_horizon_clip\" + id + \")\");\n\n // Instantiate each copy of the path with different transforms.\n var path = g.select(\"g\").selectAll(\"path\")\n .data(d3.range(-1, -bands - 1, -1).concat(d3.range(1, bands + 1)), Number);\n\n var d0 = d3_horizonArea\n .interpolate(interpolate)\n .x(function(d) { return x0(d[0]); })\n .y0(h * bands)\n .y1(function(d) { return h * bands - y0(d[1]); })\n (data);\n\n var d1 = d3_horizonArea\n .x(function(d) { return x1(d[0]); })\n .y1(function(d) { return h * bands - y1(d[1]); })\n (data);\n\n path.enter().append(\"path\")\n .style(\"fill\", color)\n .attr(\"transform\", t0)\n .attr(\"d\", d0);\n\n path.transition()\n .duration(duration)\n .style(\"fill\", color)\n .attr(\"transform\", t1)\n .attr(\"d\", d1);\n\n path.exit().transition()\n .duration(duration)\n .attr(\"transform\", t1)\n .attr(\"d\", d1)\n .remove();\n\n // Stash the new scales.\n this.__chart__ = {x: x1, y: y1, t: t1, id: id};\n });\n d3.timer.flush();\n }\n\n horizon.duration = function(x) {\n if (!arguments.length) return duration;\n duration = +x;\n return horizon;\n };\n\n horizon.bands = function(x) {\n if (!arguments.length) return bands;\n bands = +x;\n color.domain([-bands, 0, 0, bands]);\n return horizon;\n };\n\n horizon.mode = function(x) {\n if (!arguments.length) return mode;\n mode = x + \"\";\n return horizon;\n };\n\n horizon.colors = function(x) {\n if (!arguments.length) return color.range();\n color.range(x);\n return horizon;\n };\n\n horizon.interpolate = function(x) {\n if (!arguments.length) return interpolate;\n interpolate = x + \"\";\n return horizon;\n };\n\n horizon.x = function(z) {\n if (!arguments.length) return x;\n x = z;\n return horizon;\n };\n\n horizon.y = function(z) {\n if (!arguments.length) return y;\n y = z;\n return horizon;\n };\n\n horizon.width = function(x) {\n if (!arguments.length) return w;\n w = +x;\n return horizon;\n };\n\n horizon.height = function(x) {\n if (!arguments.length) return h;\n h = +x;\n return horizon;\n };\n\n return horizon;\n };\n\n var d3_horizonArea = d3.svg.area(),\n d3_horizonId = 0;\n\n function d3_horizonX(d) {\n return d[0];\n }\n\n function d3_horizonY(d) {\n return d[1];\n }\n\n function d3_horizonTransform(bands, h, mode) {\n return mode == \"offset\"\n ? function(d) { return \"translate(0,\" + (d + (d < 0) - bands) * h + \")\"; }\n : function(d) { return (d < 0 ? \"scale(1,-1)\" : \"\") + \"translate(0,\" + (d - bands) * h + \")\"; };\n }\n})();\n"},"index.html":{"filename":"index.html","type":"text/html","language":"HTML","raw_url":"https://gist.githubusercontent.com/mbostock/1483226/raw/c82f265bb29cbfe9bf444f7a5c1ae242c350b6cb/index.html","size":1989,"truncated":false,"content":"\n\n\n