Created
December 9, 2016 15:46
-
-
Save Kerrin631/27ac8a486ee612590effa33c7807841e to your computer and use it in GitHub Desktop.
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> | |
<html> | |
<head> | |
<title>SunburstTest</title> | |
</head> | |
<style type="text/css"> | |
path.selected { | |
fill-opacity: 100%; | |
shape-rendering: crisp-edges; | |
} | |
path:hover { | |
fill: yellow; | |
fill-opacity: 100%; | |
shape-rendering: crisp-edges; | |
} | |
form { | |
position: absolute; | |
right: 10px; | |
top: 10px; | |
} | |
.invisible { | |
display: none; | |
} | |
</style> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<body> | |
<form> | |
<label><input type="radio" name="mode" value="impaired"> Visually Enhanced | |
</label> | |
<label><input type="radio" name="mode" value="normal" checked> Normal</label> | |
</form> | |
<script type="text/javascript"> | |
var width = 900, | |
height = 760, | |
radius = (Math.min(width, height) / 2) - 10; | |
var color = d3.scale.category20c(); | |
var impairedColor = d3.scale.category20b(); | |
var newColor = d3.scale.ordinal() | |
.range(["#E50001", "#E12800", "#DD5100", "#D97900", '#D5A000', '#D2C400', '#B3CE00', '#8ACA00', '#62C600', '#3BC200', '#16BF00']); | |
var x = d3.scale.linear() | |
.range([0, 2 * Math.PI]); | |
var y = d3.scale.linear() | |
.range([0, radius]); | |
var partition = d3.layout.partition() | |
.sort(null) | |
.value(function(d) { return 1; }); | |
// var arc = d3.svg.arc() | |
// .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) | |
// .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) | |
// .innerRadius(function(d) { return Math.max(d.depth * 20 - 5, y(d.y)) }) | |
// .outerRadius(function(d) { return Math.max(d.depth * 20 + 10, y(d.y + d.dy)) }) | |
// .cornerRadius(function(d) { return 3; }); | |
var arcStart = d3.svg.arc() | |
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) | |
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) | |
.innerRadius(function(d) { return d.depth > 2 ? Math.max(d.depth * 54, y(d.y)) : Math.max(d.depth * 20, y(d.y)) }) | |
.outerRadius(function(d) { return d.depth > 2 ? Math.max(d.depth * 0 - 200 , y(d.y + d.dy) - 40) : Math.max(d.depth * 20 + 10, y(d.y + d.dy)) }) | |
.cornerRadius(function(d) { return 3; }); | |
var num = 0; | |
var arcTransition = d3.svg.arc() | |
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) | |
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) | |
.innerRadius(function(d) { return d.depth > num + 2 ? Math.max(d.depth * 50, y(d.y)) : Math.max(d.depth * 20, y(d.y)) }) | |
.outerRadius(function(d) { return d.depth > num + 2 ? Math.max(d.depth * 0 - (d.depth * 100) , y(d.y + d.dy) - 40) : Math.max(d.depth * 20 + 10, y(d.y + d.dy)) }) | |
.cornerRadius(function(d) { return 3; }); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") | |
.append("g") | |
.classed("inner", true); | |
d3.json("https://dl.dropboxusercontent.com/u/76642081/flare.json", function(error, root) { | |
if (error) throw error; | |
var g = svg.selectAll("g") | |
.data(partition.nodes(root)) | |
.enter().append("g") | |
.attr('id', function(d,i) { | |
d.id = 'node' + i; | |
return d.id; | |
}); | |
var scaleValue = 1; | |
path = g.append("path") | |
.classed('invisible', function(d) { return d.depth > 3 ? true : false; }) | |
.attr("d", arcStart) | |
.attr('stroke', function(d) { return 'white'; }) | |
.attr("fill", function(d) { return color((d.children ? d : d.parent).id); }) | |
.attr("transform", function() { return "scale(" + scaleValue + ")"; }) | |
.on("click", magnify) | |
.each(stash); | |
var text = g.append("text") | |
.classed('invisible', function(d) { return d.depth > 1 ? true : false; }) | |
.text(function(d) { return d.labels ? d.labels[0] : d.depth <= 0 ? '' : 'Test Label'; }) | |
.attr('font-size', function(d) { return '10px'; }) | |
.attr("text-anchor", "middle") | |
.attr("transform", function(d) { | |
if (d.depth > 0) { return "translate(" + ([arcTransition.centroid(d)[0] * scaleValue, arcTransition.centroid(d)[1] * scaleValue]) + ")" + "rotate(" + getStartAngle(d) + ")"; } | |
}) | |
.attr('opacity', function(d) { | |
if (d.dx > 0.009) { return 1 } else { return 0 } | |
}) | |
.on("click", magnify); | |
var text2 = g.append('text') | |
.text(function(d) { | |
return d.labels ? d.labels[1] : d.depth <= 0 ? '' : 'Test Label'; | |
}) | |
.attr('font-size', function(d) { return '8px'; }) | |
.classed('invisible', true) | |
.classed('text2', true) | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
return "translate(" + arcTransition.centroid(d) + ")" + | |
"rotate(" + getStartAngle(d) + ")"; | |
} | |
}); | |
var text3 = g.append('text') | |
.text(function(d) { | |
return d.labels ? d.labels[2] : d.depth <= 0 ? '' : 'Test Label'; | |
}) | |
.attr('font-size', function(d) { return '8px'; }) | |
.classed('invisible', true) | |
.classed('text3', true) | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
return "translate(" + arcTransition.centroid(d) + ")" + | |
"rotate(" + getStartAngle(d) + ")"; | |
} | |
}); | |
var innerG = d3.selectAll("g.inner"); | |
// change color on radio button click | |
d3.selectAll("input").on("change", function changeColor() { | |
var setColor = this.value === "normal" | |
? function(d) { return color((d.children ? d : d.parent).id); } | |
: function(d) { return impairedColor((d.children ? d : d.parent).id); }; | |
path | |
.attr('fill', setColor) | |
}); | |
// Distort the specified node to 80% of its parent. | |
function magnify(node) { | |
// console.log(node) | |
// get and store parent sequence | |
var parentSequence = getAncestors(node) | |
text.transition().attr("opacity", 0); | |
text2.transition().attr("opacity", 0); | |
text3.transition().attr("opacity", 0); | |
spin(node); | |
var newScale = 1.0 | |
// check if node has a parent. If so, iterate throught parentSequence and update the size of each node in the sequence | |
if (node.parent) { | |
for (var p = 0; p < parentSequence.length; p++) { | |
if (parent = parentSequence[p].parent) { | |
var parent, | |
x = parent.x, | |
k = 0.8, | |
kSide = 0.10, | |
k1 = 0.6 | |
var sibs = node.parent.children | |
for (var i = 0; i < sibs.length; i ++) { | |
if (sibs[i] == node && i != 0 && i != sibs.length-1) { | |
// console.log('no edges') | |
var less = sibs[i - 1] | |
var more = sibs[i + 1] | |
} else if (sibs[i] == node && i == 0) { | |
// console.log('left edge case') | |
var more = sibs[i + 1] | |
} else if (sibs[i] == node && i == sibs.length-1) { | |
// console.log('right edge case') | |
var less = sibs[i - 1] | |
} | |
} | |
parent.children.forEach(function(sibling) { | |
if (parentSequence[p].depth <= 1) { | |
x += reposition(sibling, x, sibling === parentSequence[p] | |
? parent.dx * k1 / parentSequence[p].value | |
: parent.dx * (1 - k1) / (parent.value - parentSequence[p].value)); | |
} else { | |
x += reposition(sibling, x, sibling === parentSequence[p] | |
? parent.dx * k / parentSequence[p].value | |
: sibling === more ? parent.dx * kSide / (sibling.value) | |
: sibling === less ? parent.dx * kSide / (sibling.value) | |
: more && less ? parent.dx * (1 - (k+kSide+kSide)) / (parent.value - (parentSequence[p].value + sibling.value)) | |
: more && ! less ? parent.dx * (1 - (k+kSide)) / (parent.value - parentSequence[p].value) | |
: less && ! more ? parent.dx * (1 - (k+kSide)) / (parent.value - parentSequence[p].value) | |
: parent.dx * (1 - k) / (parent.value - parentSequence[p].value)); | |
} | |
}); | |
} | |
} | |
// if node does not have parent (center node) reset all values to original | |
} else { | |
reposition(node, 0, node.dx / node.value); | |
} | |
path.transition() | |
// .attr('stroke', 'white') | |
.duration(1100) | |
.attr('transform', function() { | |
return "scale(" + newScale + ")" | |
}) | |
.attrTween("d", arcTween) | |
.tween("d", arcTweenZoom(node)) | |
.each("end", function(e, i) { | |
// check if the animated element's data e lies within the visible angle span given in node | |
if (e.depth <= node.depth + 3) { | |
var nodePath = d3.select(this.parentNode).select("path").classed('invisible', false); | |
nodePath.transition().duration(750) | |
} else { | |
var nodePath = d3.select(this.parentNode).select("path").classed('invisible', true); | |
nodePath.transition().duration(750) | |
} | |
// Render information for selected node | |
if (e.id == node.id) { | |
var arcText = d3.select(this.parentNode).select("text").attr("opacity", 1).classed('invisible', false); | |
var origText = arcText[0][0].textContent | |
var shortText = arcText[0][0].textContent.substring(0,5) + '...' | |
if (!(e.origText)) { | |
e.origText = origText | |
} | |
arcText | |
.text(function(d) { | |
return d.origText | |
}) | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90 | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')'; | |
} | |
}) | |
var arcText2 = d3.select(this.parentNode).select(".text2").attr("opacity", 1).classed('invisible', false); | |
arcText2 | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90 | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + | |
"rotate(" + turn + ")" + 'scale(' + newScale + ')'; | |
} | |
}) | |
.attr('dy', 8) | |
.attr('dx', -20); | |
var arcText3 = d3.select(this.parentNode).select(".text3").attr("opacity", 1).classed('invisible', false); | |
arcText3 | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90 | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + | |
"rotate(" + turn + ")" + 'scale(' + newScale + ')'; | |
} | |
}) | |
.attr('dy', 16) | |
.attr('dx', -20); | |
} else { | |
// hide secondary and tertiary labels for inactivated children nodes | |
var arcText2 = d3.select(this.parentNode).select(".text2"); | |
arcText2.classed('invisible', true) | |
var arcText3 = d3.select(this.parentNode).select(".text3"); | |
arcText3.classed('invisible', true) | |
// display secondary and tertiary labels for children nodes | |
if (node.children) { | |
for (var x = 0; x < node.children.length; x++) { | |
if (e == node.children[x] && node.depth >= 1) { | |
var childText2 = d3.select(this.parentNode).select('.text2').attr('opacity', 1).classed('invisible', false) | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90 | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')'; | |
} | |
}) | |
.attr('dy', 8) | |
.attr('dx', -20); | |
var childText3 = d3.select(this.parentNode).select('.text3').attr('opacity', 1).classed('invisible', false) | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
var turn = getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90 | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')'; | |
} | |
}) | |
.attr('dy', 16) | |
.attr('dx', -20); | |
} | |
} | |
} | |
// check that node size is big enough and in proper path to display relevant information | |
if (e.x >= node.x && (e.x < (node.x + node.dx)) && e.depth >= node.depth || e.depth >= node.depth && e.parent == node.parent ) { | |
// get a selection of the associated text element | |
var arcText = d3.select(this.parentNode).select("text"); | |
if (e.depth <= node.depth + 2) { | |
arcText.classed('invisible', false); | |
} else { | |
arcText.classed('invisible', true); | |
} | |
var origText = arcText[0][0].textContent | |
var shortText = arcText[0][0].textContent.substring(0,5) + '...' | |
if (!(e.origText)) { | |
e.origText = origText | |
} | |
// fade in the text element and recalculate positions | |
arcText.transition().duration(750) | |
.attr("opacity", 1) | |
.attr("x", function(d) { return d.x; }) | |
.attr('text-anchor', 'middle') | |
.text(function(d) { | |
if (d.depth >= node.depth && d.parent == node.parent) { | |
return shortText | |
} else { | |
return d.origText | |
} | |
}) | |
.attr("transform", function(d) { | |
// console.log(arcText) | |
if (node.depth > 0) { | |
if (d.depth > 0) { | |
var turn = e.depth == node.depth && e != node ? getNewAngle(d, node) : getNewAngle(d, node) > 0 ? getNewAngle(d, node) + 90 : getNewAngle(d, node) - 90; | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + turn + ")" + 'scale(' + newScale + ')'; | |
} | |
} else { | |
if (d.depth > 0) { | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + (getNewAngle(d, node)) + ")" + 'scale(' + 0.7 + ')'; | |
} | |
} | |
}); | |
} else { | |
var arcText = d3.select(this.parentNode).select("text").classed('invisible', true); | |
arcText.transition().duration(750) | |
.attr("x", function(d) { | |
return d.x; | |
}) | |
.attr("transform", function(d) { | |
if (d.depth > 0) { | |
return "translate(" + ([arcTransition.centroid(d)[0] * newScale, arcTransition.centroid(d)[1] * newScale]) + ")" + "rotate(" + getNewAngle(d, node) + ")" + 'scale(' + newScale + ')'; | |
} | |
}); | |
} | |
} | |
}); | |
} | |
}); | |
var innerG = d3.selectAll("g.inner"); | |
// rotate diagram to display selected node at top (zero degrees) | |
function spin(d) { | |
// console.log(d) | |
var slide = d.depth * 60 | |
var spin1 = new Promise (function(resolve, reject) { | |
var newAngle = - x(d.x + d.dx / 2); | |
innerG | |
.transition() | |
.duration(1500) | |
.attr("transform", function() { | |
return d.depth == 0 ? "rotate(" + 0 + ")" + "scale(" + 1.6 + ")" : "translate( 0," + slide + ")" + "rotate(" + ((180 / Math.PI * newAngle)) + ")" + "scale(" + 1.4 + ")"; | |
}); | |
resolve("Success!"); | |
}); | |
spin1.then(function() { | |
var newerAngle = - x(d.x + d.dx / 2); | |
innerG | |
.transition() | |
.duration(1500) | |
.attr("transform", function() { | |
return d.depth == 0 ? "rotate(" + 0 + ")" + "scale(" + 1.6 + ")" : "translate( 0," + slide + ")" + "rotate(" + ((180 / Math.PI * newerAngle)) + ")" + "scale(" + 1.4 + ")"; | |
}); | |
}); | |
path | |
.classed("selected", function (x) { return d.name == x.name; }); | |
}; | |
// Recursively reposition the node at position x with scale k. | |
function reposition(node, x, k) { | |
node.x = x; | |
if (node.children && (n = node.children.length)) { | |
var i = -1, n; | |
while (++i < n) x += reposition(node.children[i], x, k); | |
} | |
return node.dx = node.value * k; | |
}; | |
// Stash the old values for transition. | |
function stash(d) { | |
d.x0 = d.x; | |
d.dx0 = d.dx; | |
}; | |
// Interpolate the arcs in data space. | |
function arcTween(a) { | |
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a); | |
return function(t) { | |
var b = i(t); | |
a.x0 = b.x; | |
a.dx0 = b.dx; | |
return arcTransition(b); | |
}; | |
}; | |
// Interpolate the scales! | |
function arcTweenZoom(d) { | |
num = d.depth | |
var yd = d3.interpolate(y.domain(), [d.y, 1]), | |
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); | |
return function(d, i) { | |
return i | |
? function(t) { return arcTransition(d); } | |
: function(t) { | |
y.domain(yd(t)).range(yr(t)); | |
return arcTransition(d); | |
}; | |
}; | |
}; | |
// rotate text around diagram to its appropriate location on first render | |
function getStartAngle(d) { | |
// Offset the angle by 90 deg since the '0' degree axis for arc is Y axis, while | |
// for text it is the X axis. | |
var thetaDeg = (180 / Math.PI * (arcTransition.startAngle()(d) + arcTransition.endAngle()(d)) / 2 - 90); | |
// If we are rotating the text by more than 90 deg, then "flip" it. | |
// This is why "text-anchor", "middle" is important, otherwise, this "flip" would | |
// be a little harder. | |
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg; | |
} | |
// rotate text around diagram to correspond with its node after click event | |
function getNewAngle(d, node) { | |
var thetaDeg = (180 / Math.PI * (arcTransition.startAngle()(d) + arcTransition.endAngle()(d)) / 2 - 90); | |
if (node.depth == 0) { | |
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg; | |
} else { | |
return (thetaDeg < 90) ? thetaDeg - 180 : thetaDeg; | |
} | |
} | |
// find parent path | |
function getAncestors(node) { | |
var path = []; | |
var current = node; | |
while (current.parent) { | |
path.unshift(current); | |
current = current.parent; | |
} | |
return path; | |
}; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment