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: Bar Chart Transitions - Positive to Negative
forked from SpaceActuary's block: Trapezoid Charts
forked from SpaceActuary's block: Trapezoid Charts 2
<!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 = ["2011", "2012", "2013", "2014", "2015"], | |
groups = ["FD", "Ded", "Net"]; | |
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: 50}, | |
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 ? "orange" : "limegreen"; | |
}); | |
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 : | |
(c.actual / c.prior) > (d.actual / d.prior) && | |
(c.actual / c.prior) > (d.expected / d.prior) ? 1 : | |
(c.actual / c.prior) < (d.actual / d.prior) && | |
(c.actual / c.prior) < (d.expected / d.prior) ? 1 : | |
1; | |
}) | |
.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) ? "orange" : | |
(c.actual / c.prior) < (d.actual / d.prior) && | |
(c.actual / c.prior) < (d.expected / d.prior) ? "limegreen" : | |
"darkgray"; | |
}); | |
} | |
/* | |
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> |