Who doesn't love sunbursts? Visit my blog for more.
Last active
August 11, 2017 11:43
-
-
Save denjn5/6d161cb24695c8df503f9109045ea629 to your computer and use it in GitHub Desktop.
Sunburst Tutorial (d3 v4), Part 4
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
license: gpl-3.0 | |
height: 550 | |
border: no |
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
{ | |
"name": "TOPICS", "children": [{ | |
"name": "Topic A", | |
"children": [{"name": "Sub A1", "size": 4, "rank": 1}, {"name": "Sub A2", "size": 4, "rank": 1}] | |
}, { | |
"name": "Topic B", | |
"children": [{"name": "Sub B1", "size": 3, "rank": 1}, {"name": "Sub B2", "size": 3, "rank": 1}, { | |
"name": "Sub B3", "size": 3, "rank": 1}] | |
}, { | |
"name": "Topic C", | |
"children": [{"name": "Sub C1", "size": 4, "rank": 1}, {"name": "Sub C2", "size": 4, "rank": 1}] | |
}, { | |
"name": "Topic D", | |
"children": [{"name": "Sub D1", "size": 4, "rank": 6}, {"name": "Sub D2", "size": 4, "rank": 6}] | |
}, { | |
"name": "Topic E", | |
"children": [{"name": "Sub E1", "size": 4, "rank": 6}, {"name": "Sub E2", "size": 4, "rank": 6}] | |
}, { | |
"name": "Topic F", | |
"children": [{"name": "Sub F1", "size": 4, "rank": 6}, {"name": "Sub F2", "size": 4, "rank": 6}] | |
}, { | |
"name": "Topic G", | |
"children": [{"name": "Sub G1", "size": 4, "rank": 100}, {"name": "Sub G2", "size": 4, "rank": 100}] | |
}, { | |
"name": "Topic H", | |
"children": [{"name": "Sub H1", "size": 4, "rank": 100}, {"name": "Sub H2", "size": 4, "rank": 100}] | |
}] | |
} |
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> | |
<title>Sunburst Tutorial (d3 v4), Part 4</title> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
</head> | |
<style> | |
@import url('https://fonts.googleapis.com/css?family=Raleway'); | |
body { | |
font-family: "Raleway", "Helvetica Neue", Helvetica, Arial, sans-serif; | |
} | |
text { | |
pointer-events: none; /* Make text "non selectable" */ | |
} | |
button { | |
border: none; | |
color: white; | |
padding: 6px 12px; | |
text-align: center; | |
text-decoration: none; | |
display: inline-block; | |
} | |
#leftButton { | |
border-radius: 10px 3px 3px 10px; | |
} | |
#rightButton { | |
border-radius: 3px 10px 10px 3px; | |
} | |
</style> | |
<body> | |
<svg></svg><br> | |
Size: <label><input class="sizeSelect" type="radio" name="sizeSelect" value="size" checked> Size</label> | |
<label><input class="sizeSelect" type="radio" name="sizeSelect" value="count"> Count</label><br> | |
Show: <label><input class="showSelect" type="radio" name="showSelect" value="top3"> Top 3</label> | |
<label><input class="showSelect" type="radio" name="showSelect" value="top6"> Top 6</label> | |
<label><input class="showSelect" type="radio" name="showSelect" value="all" checked> All</label> | |
</body> | |
<script> | |
// Variables | |
var width = 500; | |
var height = 500; | |
var radius = Math.min(width, height) / 2; | |
var color = d3.scaleOrdinal(d3.schemeCategory20b); | |
var color2 = d3.scaleOrdinal(d3.schemeCategory20b); | |
d3.selectAll('button').style("background-color", | |
color2() | |
); | |
// Size our <svg> element, add a <g> element, and move translate 0,0 to the center of the element. | |
var g = d3.select('svg') | |
.attr('width', width) | |
.attr('height', height) | |
.append('g') | |
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); | |
// Create our sunburst data structure and size it. | |
var partition = d3.partition() | |
.size([2 * Math.PI, radius]); | |
// Get the data from our JSON file | |
d3.json("data.json", function(error, nodeData) { | |
if (error) throw error; | |
allNodes = nodeData; | |
var showNodes = JSON.parse(JSON.stringify(nodeData)); | |
drawSunburst(allNodes); | |
}); | |
function drawSunburst(data) { | |
// Find the root node, calculate the node.value, and sort our nodes by node.value | |
root = d3.hierarchy(data) | |
.sum(function (d) { return d.size; }) | |
.sort(function (a, b) { return b.value - a.value; }); | |
// Calculate the size of each arc; save the initial angles for tweening. | |
partition(root); | |
arc = d3.arc() | |
.startAngle(function (d) { d.x0s = d.x0; return d.x0; }) | |
.endAngle(function (d) { d.x1s = d.x1; return d.x1; }) | |
.innerRadius(function (d) { return d.y0; }) | |
.outerRadius(function (d) { return d.y1; }); | |
// Add a <g> element for each node; create the slice variable since we'll refer to this selection many times | |
slice = g.selectAll('g.node').data(root.descendants(), function(d) { return d.data.name; }); // .enter().append('g').attr("class", "node"); | |
newSlice = slice.enter().append('g').attr("class", "node").merge(slice); | |
slice.exit().remove(); | |
// TRY 1: ID selection that's has been drawn previously... (requires us to set "drawn" down below) | |
//newSlice.filter ( function(d) { return !d.drawn; }).append('path') | |
// .attr("display", function (d) { return d.depth ? null : "none"; }).style('stroke', '#fff'); | |
// TRY 2: Only create paths on "first run" | |
//if (firstRun) { | |
// newSlice.append('path').attr("display", function (d) { return d.depth ? null : "none"; }).style('stroke', '#fff'); | |
//} | |
// TRY 1&2: Set path-d and color always. But this isn't using new arc...? | |
//newSlice.selectAll('path').attr("d", arc).style("fill", function (d) { return color((d.children ? d : d.parent).data.name); }); | |
// Append <path> elements and draw lines based on the arc calculations. Last, color the lines and the slices. | |
slice.selectAll('path').remove(); | |
newSlice.append('path').attr("display", function (d) { return d.depth ? null : "none"; }) | |
.attr("d", arc) | |
.style('stroke', '#fff') | |
.style("fill", function (d) { return color((d.children ? d : d.parent).data.name); }); | |
// Populate the <text> elements with our data-driven titles. | |
slice.selectAll('text').remove(); | |
newSlice.append("text") | |
.attr("transform", function(d) { | |
return "translate(" + arc.centroid(d) + ")rotate(" + computeTextRotation(d) + ")"; }) | |
.attr("dx", "-20") | |
.attr("dy", ".5em") | |
.text(function(d) { return d.parent ? d.data.name : "" }); | |
newSlice.on("click", highlightSelectedSlice); | |
}; | |
d3.selectAll(".showSelect").on("click", showTopTopics); | |
d3.selectAll(".sizeSelect").on("click", sliceSizer); | |
// Redraw the Sunburst Based on User Input | |
function highlightSelectedSlice(c,i) { | |
clicked = c; | |
var rootPath = clicked.path(root).reverse(); | |
rootPath.shift(); // remove root node from the array | |
newSlice.style("opacity", 0.4); | |
newSlice.filter(function(d) { | |
if (d === clicked && d.prevClicked) { | |
d.prevClicked = false; | |
newSlice.style("opacity", 1); | |
return true; | |
} else if (d === clicked) { | |
d.prevClicked = true; | |
return true; | |
} else { | |
d.prevClicked = false; | |
return (rootPath.indexOf(d) >= 0); | |
} | |
}) | |
.style("opacity", 1); | |
//d3.select("#sidebar").text("another!"); | |
}; | |
// Redraw the Sunburst Based on User Input | |
function sliceSizer(r, i) { | |
// Determine how to size the slices. | |
if (this.value === "size") { | |
root.sum(function (d) { return d.size; }); | |
} else { | |
root.count(); | |
} | |
root.sort(function(a, b) { return b.value - a.value; }); | |
partition(root); | |
newSlice.selectAll("path").transition().duration(750).attrTween("d", arcTweenPath); | |
newSlice.selectAll("text").transition().duration(750).attrTween("transform", arcTweenText); | |
}; | |
// Redraw the Sunburst Based on User Input | |
function showTopTopics(r, i) { | |
//alert(this.value); | |
var showCount; | |
// Determine how to size the slices. | |
if (this.value === "top3") { | |
showCount = 3; | |
} else if (this.value === "top6") { | |
showCount = 6; | |
} else { | |
showCount = 100; | |
} | |
var showNodes = JSON.parse(JSON.stringify(allNodes)); | |
showNodes.children.splice(showCount, (showNodes.children.length - showCount)); | |
drawSunburst(showNodes); | |
}; | |
/** | |
* When switching data: interpolate the arcs in data space. | |
* @param {Node} a | |
* @param {Number} i | |
* @return {Number} | |
*/ | |
function arcTweenPath(a, i) { | |
var oi = d3.interpolate({ x0: a.x0s, x1: a.x1s }, a); | |
function tween(t) { | |
var b = oi(t); | |
a.x0s = b.x0; | |
a.x1s = b.x1; | |
return arc(b); | |
} | |
return tween; | |
} | |
/** | |
* When switching data: interpolate the text centroids and rotation. | |
* @param {Node} a | |
* @param {Number} i | |
* @return {Number} | |
*/ | |
function arcTweenText(a, i) { | |
var oi = d3.interpolate({ x0: a.x0s, x1: a.x1s }, a); | |
function tween(t) { | |
var b = oi(t); | |
return "translate(" + arc.centroid(b) + ")rotate(" + computeTextRotation(b) + ")"; | |
} | |
return tween; | |
} | |
/** | |
* Calculate the correct distance to rotate each label based on its location in the sunburst. | |
* @param {Node} d | |
* @return {Number} | |
*/ | |
function computeTextRotation(d) { | |
var angle = (d.x0 + d.x1) / Math.PI * 90; | |
// Avoid upside-down labels | |
return (angle < 120 || angle > 270) ? angle : angle + 180; // labels as rims | |
//return (angle < 180) ? angle - 90 : angle + 90; // labels as spokes | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment