"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.
Last active
August 15, 2016 22:54
-
-
Save dankronstal/79e85a8428b568fc68e5c7a51998a4cb to your computer and use it in GitHub Desktop.
Blade/Ring Chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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