Skip to content

Instantly share code, notes, and snippets.

@dankronstal
Last active February 5, 2016 20:29
Show Gist options
  • Save dankronstal/f8ea0f57c25ab57c03a1 to your computer and use it in GitHub Desktop.
Save dankronstal/f8ea0f57c25ab57c03a1 to your computer and use it in GitHub Desktop.
Fancy donut chart

two-level nested donut chart

<!DOCTYPE html>
<meta charset="utf-8">
<link type="text/css" rel="stylesheet" href="https://mbostock.github.io/d3/talk/20111116/colorbrewer/colorbrewer.css"/>
<style>
</style>
<body>
<!-- loosely from: http://bl.ocks.org/mikehadlow/93b471e569e31af07cd3 -->
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
//parameters - most of these could be passed in if this was methodized
var width = 460,
height = 300,
arcRad = 100,
arcRadOffset = 10,
childOpacity = 1,
parentOpacityOff = .5,
parentOpacityOver = .8,
transitionDuration = 150,
groupOneColor = "#006BB1", //blue-ish
groupTwoColor = "#00BD62", //green-ish
defaultLabelColor = "#333333",
defaultNameLabel = "Default Label",
defaultValueLabel = "Description";
/***** start setting up some data *****/
var dataset = {
parentRing: [{name:"Group One", value:0, count:0, color:groupOneColor}, {name:"Group Two", value:0, count:0, color:groupTwoColor}],
childRing: [{name:"Element One", value:100, group: 0},{name:"Element Two", value:200, group: 0},{name:"Element Three", value:300, group: 0},{name:"Element Four", value:400, group: 1},{name:"Element Five",value:500, group: 0},{name:"Element Six",value:600, group: 0},{name:"Element Seven", value:700, group: 1}],
};
//re-hash/process data
dataset["childRing"].forEach(function (d, i){
var whichRing = identifyParent(i); //figure out which parent this child belongs to
dataset["parentRing"][whichRing].value += d.value; //add child value to parent
dataset["parentRing"][whichRing].count++; //add child count to parent
dataset["childRing"][i].color = d3.rgb(dataset["parentRing"][whichRing].color).brighter(dataset["parentRing"][whichRing].count/2); //parent's color is assigned to child, progressively lighter
});
/***** end setting up some data *****/
//this is arbitrary, and should probably be based on a property or something
function identifyParent(i){
return i < 4 ? 0 : 1;
}
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var arc = d3.svg.arc();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
/***** start arc shadows *****/
var defs = svg.append("defs");
var filter = defs.append("filter")
.attr("id", "dropshadow");
filter.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 1)
.attr("result", "blur");
filter.append("feOffset")
.attr("in", "blur")
.attr("dx", .5)
.attr("dy", .5)
.attr("result", "offsetBlur");
var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
.attr("in", "offsetBlur");
feMerge.append("feMergeNode")
.attr("in", "SourceGraphic");
/***** stop arc shadows *****/
//compose label boxes
var groupNameText = svg.append("text")
.style("font-family","Helvetica")
.style("font-weight","bold")
.attr("fill", defaultLabelColor)
.attr("transform", "translate(0,0)")
.attr("text-anchor","middle")
.text(defaultNameLabel);
var groupValueText = svg.append("text")
.style("font-family","Helvetica")
.attr("fill", defaultLabelColor)
.attr("transform", "translate(0,20)")
.attr("text-anchor","middle")
.text(defaultValueLabel);
// compose arcs
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i, j){
return d.data.color;
})
.attr("id", function(d, i, j) {
if(j==1) return "child"+identifyParent(i);
else return "parent"+i;
})
.attr("d", function(d, i, j)
{
if(j == 1) return arc.innerRadius(arcRad/1.5).outerRadius(arcRad)(d); //define inner arcs
else return arc.innerRadius(arcRad).outerRadius(arcRad+(arcRadOffset/2))(d); //define outer arcs
})
.attr("filter", "url(#dropshadow)")
.style("opacity", function(d){ return d.value >= 1000 ? parentOpacityOff : childOpacity; }) //probably this needs to be done more like "d" above..
.on("mouseover", function(d,i,j) {
groupNameText.text(d.data.name).style("fill", d3.rgb(d.data.color).darker(1));
groupValueText.text(d.data.value).style("fill", d3.rgb(d.data.color).darker(1));
if(d3.select(this).attr("id").indexOf("child") == 0){ //mousing over a child arc
d3.select(this).transition()
.duration(transitionDuration)
.attr("d", function(d){ return arc.innerRadius(arcRad/1.5).outerRadius(arcRad+arcRadOffset)(d); });
var parentId = "#parent"+d3.select(this).attr("id").substr(d3.select(this).attr("id").length-1,d3.select(this).attr("id").length);
d3.select(parentId).transition()
.duration(transitionDuration)
.attr("d", function(d){ return arc.innerRadius(arcRad).outerRadius(10+arcRad+arcRadOffset)(d); })
.style("opacity", parentOpacityOff);
}
else if(d3.select(this).attr("id").indexOf("parent") == 0){ //mousing over a parent arc
d3.select(this).transition()
.duration(transitionDuration)
.attr("d", function(d){ return arc.innerRadius(arcRad).outerRadius(arcRad+arcRadOffset*3)(d); })
.style("opacity", parentOpacityOver);
}
})
.on("mouseout",function(d,i,j){
groupNameText.text(defaultNameLabel).style("fill", defaultLabelColor);
groupValueText.text(defaultValueLabel).style("fill", defaultLabelColor);
if(d3.select(this).attr("id").indexOf("child") == 0){ //mousing off a child arc
d3.select(this).transition()
.duration(transitionDuration)
.attr("d", function(d){ return arc.innerRadius(arcRad/1.5).outerRadius(arcRad)(d); });
var parentId = "#parent"+d3.select(this).attr("id").substr(d3.select(this).attr("id").length-1,d3.select(this).attr("id").length);
d3.select(parentId).transition()
.duration(transitionDuration)
.attr("d", function(d){ return arc.innerRadius(arcRad).outerRadius(arcRad+(arcRadOffset/2))(d); });
}
else if(d3.select(this).attr("id").indexOf("parent") == 0){ //mousing off a parent arc
d3.select(this).transition()
.duration(transitionDuration)
.attr("d", function(d){ return arc.innerRadius(arcRad).outerRadius(arcRad+(arcRadOffset/2))(d); })
.style("opacity", parentOpacityOff);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment