Built with blockbuilder.org
Last active
May 6, 2016 00:08
-
-
Save matt-mcdaniel/01f01ee9a1cb0da504cad9dcb0cf5d3f to your computer and use it in GitHub Desktop.
Interactive Aster Plot
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> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<style> | |
@import url(https://fonts.googleapis.com/css?family=BenchNine); | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
svg { width:100%; height: 100% } | |
* { font-family: 'BenchNine'; } | |
.label { | |
position: absolute; | |
top: 50px; | |
left: 50px; | |
color: #a0a0a0; | |
font-size: 25px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="label"> | |
Click the arcs to render the data subset. | |
</div> | |
<script> | |
var initialData = [ | |
{ | |
name: 'Food', | |
value: 2.4, | |
data: [ | |
{ | |
name: 'Pizza', | |
value: 1.56 | |
}, | |
{ | |
name: 'Burger', | |
value: 1.45 | |
}, | |
{ | |
name: 'Enchilada', | |
value: 1.33 | |
}, | |
{ | |
name: 'Injera', | |
value: 1.00 | |
}, | |
{ | |
name: 'Gyro', | |
value: 0.95 | |
} | |
] | |
}, | |
{ | |
name: 'Grocery', | |
value: 1.8, | |
data: [ | |
{ | |
name: 'Vonns', | |
value: 2.25 | |
}, | |
{ | |
name: 'Kroger', | |
value: 1.5 | |
}, | |
{ | |
name: 'Safeway', | |
value: 0.86 | |
}, | |
] | |
}, | |
{ | |
name: 'Transportation', | |
value: 1.6, | |
data: [ | |
{ | |
name: 'Walking', | |
value: 2.5 | |
}, | |
{ | |
name: 'Biking', | |
value: 2.4 | |
}, | |
{ | |
name: 'Skateboarding', | |
value: 1.5 | |
}, | |
{ | |
name: 'Running', | |
value: 0.85 | |
}, | |
] | |
}, | |
{ | |
name: 'Subjects', | |
value: 1.05, | |
data: [ | |
{ | |
name: 'Math', | |
value: 2.4 | |
}, | |
{ | |
name: 'Computer Science', | |
value: 2.1 | |
}, | |
{ | |
name: 'Music', | |
value: 1.9 | |
}, | |
{ | |
name: 'Philosophy', | |
value: 1.3 | |
}, | |
{ | |
name: 'English', | |
value: 0.9 | |
}, | |
] | |
} | |
]; | |
var margin = { | |
top: 20, | |
right: 20, | |
bottom: 20, | |
left: 20 | |
} | |
var svg = d3.select("body").append("svg") | |
.attr('width', '100%') | |
.attr('height', '100%'); | |
var svgWidth = svg.node().clientWidth; | |
var svgHeight = svg.node().clientHeight; | |
var radius = (Math.min( | |
svgWidth - margin.left - margin.right, | |
svgHeight - margin.top - margin.bottom | |
) / 2); | |
var innerRadius = 0; | |
var arc = d3.svg.arc() | |
.innerRadius(innerRadius) | |
.outerRadius(function(d) { | |
return (radius - innerRadius) * (d.data.value / 2.9) + innerRadius; | |
}); | |
var pie = d3.layout.pie() | |
.sort(function(a, b) { return d3.descending(a.value, b.value); }) | |
.value(function(d) { return 1; }); | |
var g = svg.append('g') | |
.attr('transform', 'translate(' + (svgWidth/2) + ',' + (svgHeight/2) + ')'); | |
// initialize | |
var isInitialData = true; | |
update(); | |
function update(data, tweenArr, tweenColor) { | |
var data = data || initialData; | |
var tweenArr = tweenArr || null; | |
var colorScale; | |
if (isInitialData) { | |
colorScale = getColorScale(data); | |
} else if (!isInitialData && tweenColor) { | |
colorScale = getColorScale(data, tweenColor); | |
} | |
// remove all arcs before rendering new ones | |
g.selectAll('.arc') | |
.data([]) | |
.exit().remove(); | |
var path = g.selectAll('.arc') | |
.data(pie(data)); | |
path.exit().remove(); | |
path.enter().append('path'); | |
path | |
.attr('class', 'arc') | |
.attr('fill', tweenColor || null) | |
.transition() | |
.duration(500) | |
.ease('exp') | |
.attr('fill', function(d, i) { | |
return isInitialData ? colorScale(i) : colorScale(d.data.value); | |
}) | |
.attrTween('d', function(d, i) { | |
if (!tweenArr) { | |
// if initial render, do not interpolate | |
return function() { return arc(d); }; | |
} else { | |
var tweenPie = pie(tweenArr); | |
return arcTweenSegment(d, tweenPie[i]); | |
} | |
}); | |
g.selectAll('.arc-text') | |
.data([]) | |
.exit().remove(); | |
var text = g.selectAll('.arc-text') | |
.data(pie(data)); | |
text.exit().remove(); | |
text.enter().append('text') | |
text | |
.text(function(d) { return d.data.name; }) | |
.attr('text-anchor', 'middle') | |
.attr('class', 'arc-text') | |
.attr('fill', '#fff') | |
.attr('x', function(d) { | |
return arc.centroid(d)[0]; | |
}) | |
.attr('y', function(d) { | |
return arc.centroid(d)[1]; | |
}) | |
.style('font-size', function(d) { | |
var fontSize = d.data.value * 12; | |
return fontSize > 20 ? fontSize : 20; | |
}) | |
.attr('opacity', 0) | |
.transition() | |
.duration(1000) | |
.attr('opacity', 1); | |
/** On Arc Click **/ | |
path.on('click', function(d) { | |
var el = d3.select(this); | |
var clickData = el.data(); | |
var color = el.attr('fill'); | |
var clickDataCopy = deepObjCopy(clickData[0]); | |
// remove all arcs except for selected | |
var path = d3.selectAll('.arc') | |
.data(clickData); | |
path.exit().remove(); | |
// remove all text | |
d3.selectAll('.arc-text') | |
.data([]) | |
.exit().remove(); | |
path | |
.attr('fill', color) | |
.transition() | |
.duration(500) | |
.attrTween('d', function(d) { | |
return arcTweenFullCircle(d); | |
}); | |
// wait for full circle tween to finish | |
var timeout = window.setTimeout(function() { | |
// check to see if will render initial dataset | |
isInitialData = !clickData[0].data.hasOwnProperty('data'); | |
// create deep copy of selected arc | |
var deepCopy = deepObjCopy(clickData[0]); | |
// create array of n length with clicked object | |
var dataLength = deepCopy.data.hasOwnProperty('data') ? | |
deepCopy.data.data.length : initialData.length; | |
var tweenObject = getTweenData(deepCopy, dataLength); | |
// update and tween from clicked object | |
update(d.data.data, tweenObject, color); | |
}, 500); | |
}); | |
function getTweenData(copy, length) { | |
var tweenFromArr = []; | |
for (var i = 0; i < length; i++) { | |
tweenFromArr.push( | |
// sort data by value | |
copy.data | |
); | |
} | |
return tweenFromArr; | |
} | |
function arcTweenFullCircle(d) { | |
// interpolate startAngle to top center, counterclockwise | |
var interpolateStart = d3.interpolate( | |
d.startAngle, | |
0 | |
); | |
// interpolate endAngle to top center, clockwise | |
var interpolateEnd = d3.interpolate( | |
d.endAngle, | |
Math.PI * 2 | |
); | |
return function(t) { | |
d.startAngle = interpolateStart(t); | |
d.endAngle = interpolateEnd(t); | |
return arc(d); | |
} | |
} | |
// tween old arc value to new arc value | |
function arcTweenSegment(d, tweenTo) { | |
var interpolate = d3.interpolate(tweenTo, d); | |
return function(t) { | |
return arc(interpolate(t)); | |
} | |
} | |
} | |
function getColorScale(data, color) { | |
if (!color) { | |
return d3.scale.linear() | |
.domain(initialData.map(function(d, i) { return i; })) | |
.range(['#2ecc71', '#3498db', '#9b59b6', '#e67e22', '#e74c3c']); | |
} else { | |
// get color in RGB format | |
var rgbColor = d3.rgb(color); | |
var colors = []; | |
for (var i = 0; i < data.length; i++) { | |
colors.push([i, rgbColor.darker(i === 0 ? 0 : i/2)]); | |
} | |
var scale = d3.scale.linear() | |
.domain(data | |
.map(function(d) { return d.value; }) | |
.sort(function(a, b) { return d3.ascending(a, b); })) | |
.range(colors.map(function(d) { return d[1]; })); | |
return scale; | |
} | |
} | |
function deepObjCopy (dupeObj) { | |
var retObj = new Object(); | |
if (typeof(dupeObj) == 'object') { | |
if (typeof(dupeObj.length) != 'undefined') | |
var retObj = new Array(); | |
for (var objInd in dupeObj) { | |
if (typeof(dupeObj[objInd]) == 'object') { | |
retObj[objInd] = deepObjCopy(dupeObj[objInd]); | |
} else if (typeof(dupeObj[objInd]) == 'string') { | |
retObj[objInd] = dupeObj[objInd]; | |
} else if (typeof(dupeObj[objInd]) == 'number') { | |
retObj[objInd] = dupeObj[objInd]; | |
} else if (typeof(dupeObj[objInd]) == 'boolean') { | |
((dupeObj[objInd] == true) ? | |
retObj[objInd] = true : retObj[objInd] = false); | |
} | |
} | |
} | |
return retObj; | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment