Skip to content

Instantly share code, notes, and snippets.

@dankronstal
Last active August 15, 2016 22:54
Show Gist options
  • Save dankronstal/79e85a8428b568fc68e5c7a51998a4cb to your computer and use it in GitHub Desktop.
Save dankronstal/79e85a8428b568fc68e5c7a51998a4cb to your computer and use it in GitHub Desktop.
Blade/Ring Chart

"Blade/Ring Chart" is a grouped bar chart, based on the inspiring Circle of Blue work by Tulp. My version uses actual arcs instead of rect's. Click on an arc to order according to the clicked group (Blade), and the arc Rings will rotate until the arcs of a color in each Ring are aligned for the Blade.

Province Population Percentage Land Area Density Seats People per Seat
Ontario 12851821 0.3839 1076395 12.19 121 106213
Quebec 7903001 0.2361 1365128 5.76 78 101321
British Columbia 4400057 0.1314 925186 4.84 42 104763
Alberta 3645257 0.1089 642317 5.77 34 107213
Manitoba 1208268 0.0361 553556 2.22 14 86305
Saskatchewan 1033381 0.0309 591670 1.75 14 73813
Nova Scotia 921727 0.0275 53338 17.63 11 83793
New Brunswick 751171 0.0224 71450 10.5 10 75117
Newfoundland and Labrador 514536 0.0154 373872 1.36 7 73505
Prince Edward Island 140204 0.0042 5660 24.98 4 35051
Northwest Territories 41462 0.0012 1183085 0.04 1 41462
Yukon 33897 0.001 474391 0.07 1 33897
Nunavut 31906 0.0009 1936113 0.02 1 31906
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>blade ring chart</title>
</head>
<!-- concept based on http://janwillemtulp.com/water/ -->
<body>
<div class="content"></div>
<script src="https://d3js.org/d3.v4.js"></script>
<script>
var dataColors = d3.schemeCategory20;
var ringMax
var sizeDegrees;
var bladeAngles = [];
var bladeOffset = [];
var ringList = [];
var l;
var ringConfig = {radius: 40, width: 20, offset: 1};
var fillValue = [];
var arcFills = d3.arc()
.innerRadius(function(d){ return innerRadius(d.ring); })
.outerRadius(function(d){ return fillRadius(d.ring,d); })
.startAngle(function(d, i){return degreesToRadians(i*sizeDegrees-.15*d.ring);})
.endAngle(function(d, i){return degreesToRadians((i*sizeDegrees)+sizeDegrees-2);});
var chart = d3.select("body").append("svg:svg")
.attr("class", "chart")
.attr("width", 600)
.attr("height", 400)
.append("svg:g")
.attr("transform", "translate(200,200)");
var slices;
function innerRadius(i){
return ringConfig.radius + (i*ringConfig.width) + (i*ringConfig.offset);
}
function fillRadius(i,d){
return ringConfig.width+(d.ring+1<= fillValue.length ? fillValue[d.ring+1](d.value) : 10) + ((i+1)*ringConfig.width) + (i*ringConfig.offset);
}
function degreesToRadians(d){
var r = 0;
r = d * (Math.PI / 180);
return r;
}
function radiansToDegrees(d){
return d*57.2958;
}
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
}
function update(d){
//identify offsets for selected blade
var activeblade = d.blade;
var samebladePrefix = "b"+activeblade+"r";
var sameblade = d3.selectAll('[id*='+samebladePrefix+']')._groups[0];
var bladeOffsets = [];
sameblade.forEach(function(r,i){
bladeOffsets[i]=d3.select(d3.selectAll('[id*='+samebladePrefix+']')._groups[0][i]).attr("currPos");
});
for(var i=0;i<bladeOffsets.length; i++){
do{
if(bladeOffsets[i]>l) bladeOffsets[i]-=l;
}while(bladeOffsets[i]>l)
bladeOffsets[i]=0-bladeOffsets[i];
}
//apply transition
for(var r=0; r <= l; r++)
{
d3.selectAll("[id$=r"+r+"f]")
.transition()
.duration(750)
.attr("transform", function(p,h) {
var currPos = parseInt(d3.select("#b"+activeblade+"r"+p.ring+"f").attr("currPos"))+parseInt(bladeOffsets[r]);
var appliedAngle = 0 + (bladeOffsets[r] > 0 ? bladeAngles[Math.abs(bladeOffsets[r])] : -1*bladeAngles[Math.abs(bladeOffsets[r])]);
if(isNaN(appliedAngle)) {
console.log("hi");
return "rotate(0)";
}else{
//return "rotate("+(3.75*sizeDegrees+appliedAngle)+")"; //rotates selected blade to East facing orientation; todo: calculate instead of hardcode
return "rotate("+(appliedAngle)+")"; //default rotation ends arc angles right-hand edge at 90 degrees
}
});
}
}
d3.csv("http://bl.ocks.org/dankronstal/raw/79e85a8428b568fc68e5c7a51998a4cb/CanadaDemographics.csv", function(dataX) {
//transform raw data
var cd = [];
var bladeList = [];
var ringMin = [];
var ringMax = [];
dataX.forEach(function(d,i){
var ring=0;
Object.keys(d).forEach(function(k,j) {
if(k!="Province"){ //eww gross, hardcoding... must handle this better
cd[cd.length] = {bladeLabel:d.Province,blade:i+1,ring:ring,value:Number(d[k]),ringLabel:k};
ringMin[j] = isNaN(ringMin[j]) || ringMin[j] > Number(d[k]) ? Number(d[k]) : ringMin[j];
ringMax[j] = isNaN(ringMax[j]) || ringMax[j] < Number(d[k]) ? Number(d[k]) : ringMax[j];
ringList[j]=k;
ring++;
}
});
});
for(var i=1; i<=ringMax.length; i++){
fillValue[i] = d3.scaleLinear().domain([ringMin[i],ringMax[i]]).range([1,ringConfig.width]);
}
bladeList = uniq(bladeList);
ringList = uniq(ringList);
data = cd;
//initial sort, largest to smallest by ring
data.sort(function(a, b)
{
if(a.ring === b.ring)
{
var x = a.value, y = b.value;
return x > y ? -1 : x < y ? 1 : 0;
}
return a.ring - b.ring;
});
data.forEach(function(d,i){
ringMax = ringMax > d.ring ? ringMax : d.ring;
bladeList[bladeList.length]=d.bladeLabel;
});
bladeList = uniq(bladeList);
sizeDegrees = 360/bladeList.length;
//blade-specific crunching
data.forEach(function(d,i){
if(bladeAngles[d.blade-1] != null) return;
bladeAngles[d.blade-1] = Math.floor(radiansToDegrees(degreesToRadians((d.blade*sizeDegrees-.15*d.ring))));
});
l = bladeAngles.length-1;
//bind
slices = chart.selectAll("path").data(data)
.enter()
.append("g");
slices.append("svg:path")
.attr("id",function(d){ return "b"+d.blade+"r"+d.ring+"f"; })
.attr("bladeLabel",function(d){ return d.bladeLabel; })
.attr("ringLabel",function(d){ return d.ringLabel; })
.attr("v",function(d){return d.value;})
.attr("currPos",function(d,i){
d.startPos = i- (d.ring*(bladeList.length-1))-d.ring;//set starting position based on sorted position
return d.startPos;
})
.style("fill", function(d,i){ return dataColors[d.blade]; })
.style("fill-opacity","0.75")
.attr("d", arcFills);
slices.on("click",function(d){
update(d);
});
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment