forked from SpaceActuary's block: Bar Chart Transitions - Positive to Negative
forked from SpaceActuary's block: Trapezoid Charts
forked from SpaceActuary's block: Trapezoid Charts 2
forked from SpaceActuary's block: Bugle Charts
forked from SpaceActuary's block: Bar Chart Transitions - Positive to Negative
forked from SpaceActuary's block: Trapezoid Charts
forked from SpaceActuary's block: Trapezoid Charts 2
forked from SpaceActuary's block: Bugle Charts
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| body { | |
| margin: 0; | |
| position: fixed; | |
| top: 0; right: 0; bottom: 0; left: 0; | |
| font-family: Arial, Helvetica, sans-serif | |
| } | |
| svg { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .axis { | |
| /*stroke: lightgrey;*/ | |
| shape-rendering: crispEdges; | |
| } | |
| .axis path { | |
| fill: none; | |
| stroke: lightgrey; | |
| } | |
| .x.axis line { | |
| display: none; | |
| } | |
| .y1.axis line, | |
| .y2.axis line { | |
| fill: none; | |
| stroke: lightgrey; | |
| } | |
| .y2.axis text { | |
| fill: none; | |
| } | |
| .cell:hover .y2.axis text { | |
| fill: #888; | |
| } | |
| path.outline { | |
| fill: none; | |
| stroke: #000000; | |
| stroke-width: 1.5; | |
| } | |
| line.total { | |
| stroke-dasharray: 5,5; | |
| } | |
| </style> | |
| <body> | |
| <button onClick="randomize();">Randomize</button> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| var years = ["2006", "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015"], | |
| groups = ["A", "B", "C", "D", "E", "F", "G", "H"]; | |
| var data = d3.merge(years.map(function(y){ | |
| return groups.map(function(g){ | |
| return {"year":y, "group":g}; | |
| }) | |
| })); | |
| var varianceToggle = true; | |
| var randomizeData = function(d){ | |
| var datum = { | |
| year: d.year, | |
| group: d.group, | |
| prior: 3 + (Math.random() * 6), | |
| actual: 4 + (Math.random() * 5), | |
| expected: 4 + (Math.random() * 5), | |
| }; | |
| datum.ratios = [ | |
| {index: 0, prior: datum.prior, actual: datum.actual}, | |
| {index: 1, prior: datum.prior, actual: 4 + (Math.random() * 5)}, | |
| {index: 2, prior: datum.prior, actual: 4 + (Math.random() * 5)}, | |
| {index: 3, prior: datum.prior, actual: 4 + (Math.random() * 5)}, | |
| {index: 4, prior: datum.prior, actual: 4 + (Math.random() * 5)}, | |
| {index: 5, prior: datum.prior, actual: 4 + (Math.random() * 5)}, | |
| ]; | |
| return datum; | |
| }; | |
| data = data.map(randomizeData); | |
| var margin = {top: 30, right: 20, bottom: 50, left: 30}, | |
| width = 960 - margin.left - margin.right; | |
| height = 500 - margin.top - margin.bottom; | |
| t = 1000, // transition time | |
| b = width / data.length // block width | |
| var x = d3.scale.ordinal() | |
| .rangeRoundBands([0, width], .2) | |
| .domain(data.map(function(d) { return d.year; })); | |
| var y1 = d3.scale.ordinal() | |
| .rangeRoundBands([0, height], .1) | |
| .domain(data.map(function(d) { return d.group; })); | |
| var y2 = d3.scale.linear() | |
| .range([y1.rangeBand(), 0]) | |
| .domain([0, d3.max(data, function(d){ | |
| return d3.max([d.prior, d.actual, d.expected]) | |
| })]) | |
| .nice(); | |
| var xAxis = d3.svg.axis().scale(x).orient("bottom"); | |
| var y1Axis = d3.svg.axis().scale(y1).orient("left"); | |
| var y2Axis = d3.svg.axis().scale(y2).orient("right").ticks(5); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width + margin.left + margin.right) | |
| .attr("height", height + margin.top + margin.bottom) | |
| .append("g") | |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
| svg.append("g") | |
| .attr("class", "y1 axis") | |
| .attr("transform", "translate(" + 0 + ",0)") | |
| .call(y1Axis); | |
| svg.append("g") | |
| .attr("class", "x axis") | |
| .attr("transform", "translate(0," + height + ")") | |
| .call(xAxis); | |
| svg.append("line") | |
| .attr("class","total") | |
| //This is the accessor function we talked about above | |
| var lineFunction = d3.svg.line() | |
| .x(function(d) { return d.x; }) | |
| .y(function(d) { return d.y; }) | |
| .interpolate("linear"); | |
| var drawVariance = function(d, i){ | |
| } | |
| var redraw = function(data){ | |
| // get the min / max variance | |
| y2.domain( | |
| d3.extent( | |
| d3.merge([ | |
| d3.extent(data, function(d){ return d.actual - d.prior; }), | |
| d3.extent(data, function(d){ return d.expected - d.prior; }) | |
| ]) | |
| ) | |
| ).nice();; | |
| var cells = svg.selectAll("g.cell").data(data) | |
| var cellsEnter = cells.enter() | |
| .append("g").attr("class","cell") | |
| .attr("transform", function(d){ | |
| return "translate(" + x(d.year) +"," + y1(d.group) +")"; | |
| }) | |
| cellsEnter | |
| .append("rect") | |
| //.attr("stroke", "steelblue") | |
| .attr("fill", "none") | |
| .attr('pointer-events', 'all') | |
| .attr("height", y1.rangeBand()) | |
| .attr("width", x.rangeBand()) | |
| cellsEnter | |
| .append("g") | |
| .attr("class", "y2 axis") | |
| .attr("transform", "translate(" + x.rangeBand() + ",0)") | |
| .call(y2Axis); | |
| cells | |
| .each(buglechart) | |
| function buglechart(d){ | |
| var cell = d3.select(this); | |
| var bugle = cell.selectAll(".bugle").data([d]) | |
| bugle.enter().append("path") | |
| .attr("class","bugle"); | |
| bugle.transition().duration(1000) | |
| .attr("d", function(d){ | |
| return lineFunction([ | |
| {x: 0, y: y2(0)}, | |
| {x: x.rangeBand() * 0.8, y: y2(d.actual - d.prior)}, | |
| {x: x.rangeBand() * 0.8, y: y2(d.expected - d.prior)} | |
| ])}) | |
| .attr("fill", function(d){ | |
| return d.actual > d.expected ? "crimson" : "seagreen"; | |
| }); | |
| var confetti = cell.selectAll(".confetti").data(d.ratios) | |
| confetti.enter().append("circle") | |
| .attr("class", "confetti") | |
| .attr("r", 3) | |
| .attr("cx", function(c){ | |
| return x.rangeBand() * (1 - 0.03 * c.index); | |
| }); | |
| confetti.transition().duration(1000) | |
| .attr("cy", function(c){ return y2(c.actual - c.prior); }) | |
| .attr("opacity", function(c){ | |
| return (c.index == 0) ? 1 : 0.2; | |
| }) | |
| .attr("fill", function(c){ | |
| return (c.index == 0) ? "black" : | |
| (c.actual / c.prior) > (d.actual / d.prior) && | |
| (c.actual / c.prior) > (d.expected / d.prior) ? "crimson" : | |
| (c.actual / c.prior) < (d.actual / d.prior) && | |
| (c.actual / c.prior) < (d.expected / d.prior) ?"seagreen" : | |
| "black"; | |
| }); | |
| } | |
| /* | |
| var trapezoids = svg.selectAll("path.bars").data(data) | |
| trapezoids.enter().append("path") | |
| .attr("class","bars") | |
| .attr("fill", "#c8c8c8"); | |
| trapezoids.transition().duration(1000) | |
| .attr("d", drawTrapezoid) | |
| var variances = svg.selectAll("path.variance").data(data) | |
| variances.enter().append("path") | |
| .attr("class","variance"); | |
| variances.transition().duration(1000) | |
| .attr("d", drawVariance) | |
| .attr("fill", function(d){ | |
| return d.actual > d.expected ? "crimson" : "seagreen"; | |
| }); | |
| var outline = svg.selectAll("path.outline").data(data) | |
| outline.enter().append("path") | |
| .attr("class","outline"); | |
| outline.transition().duration(1000) | |
| .attr("d", drawTrapezoid); | |
| */ | |
| // redraw y-axis | |
| svg.selectAll("g.y1.axis") | |
| .transition() | |
| .duration(1000) | |
| .call(y1Axis); | |
| svg.selectAll("g.y2.axis") | |
| .transition() | |
| .duration(1000) | |
| .call(y2Axis); | |
| } | |
| redraw(data); | |
| var randomize = function(){ | |
| data = data.map(randomizeData) | |
| redraw(data); | |
| } | |
| </script> | |
| </body> | |
| </html> |