D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
denjn5
Full window
Github gist
Sunburst Tutorial (d3 v4), Part 4
Who doesn't love sunbursts? Visit my
blog
for more.
Sunburst Slice Selection
<!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>
https://d3js.org/d3.v4.min.js