forked from denjn5's block: Sunburst Tutorial (d3 v4), Part 4
xxxxxxxxxx
<head>
<title>Sunburst Tutorial (d3 v4), Part 4</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3-tip.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;
}
.d3-tip {
line-height: 1;
padding: 6px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 4px;
font-size: 12px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips specifically */
.d3-tip.n:after {
margin: -2px 0 0 0;
top: 100%;
left: 0;
}
</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.schemeCategory20c);
var color2 = d3.scaleOrdinal(d3.schemeCategory20c);
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) {
console.log('d is '+ d);
console.dir(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 : "Topics" });
// tooltip add
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset(function() {
return [0, 0]
})
.html(function(d) {
console.log("d is ",d);
return "<strong># of "+ d.data.name+"</strong> <span style='color:red'>" + d.data.rank + "</span>";
});
newSlice.call(tip);
newSlice.on("click", highlightSelectedSlice)
.on("mouseover",tip.show)
.on("mouseout",tip.hide);
};
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