Skip to content

Instantly share code, notes, and snippets.

@SpaceActuary
Last active March 28, 2016 20:46
Show Gist options
  • Save SpaceActuary/b408e03229958635c43b to your computer and use it in GitHub Desktop.
Save SpaceActuary/b408e03229958635c43b to your computer and use it in GitHub Desktop.
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 = ["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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment