Skip to content

Instantly share code, notes, and snippets.

@matt-mcdaniel
Last active May 6, 2016 00:08
Show Gist options
  • Save matt-mcdaniel/01f01ee9a1cb0da504cad9dcb0cf5d3f to your computer and use it in GitHub Desktop.
Save matt-mcdaniel/01f01ee9a1cb0da504cad9dcb0cf5d3f to your computer and use it in GitHub Desktop.
Interactive Aster Plot
<!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