I have a zoomable sunburst chart with the following issues:
Legend is displaying vertically instead of horizontal. I thought that float:left on class legend would do the trick but labels display on new line instead.
Allow users to disable categories in the legend to recalculate the sunburst chart.
Tooltip is not showing up. What in the world am I missing?
I want to append the grand total in the middle of the doughnut which dynamically changes upon zoom transitions. How does one go about that?
I apologize for the messy code. I started learning D3 about 2 weeks ago and the code below is a mix of a bunch of different tutorials and stack overflow forums.
Thank you in advance!
// define json object
var root = {
"name": "TOTAL",
"children": [
{
"name": "UNASSIGNED",
"children": [
{"name": "high", "size": 170},
{"name": "med", "size": 701},
{"name": "low", "size": 410}
]
},
{
"name": "CLOSED",
"children": [
{"name": "high", "size": 1701},
{"name": "med", "size": 584},
{"name": "low", "size": 606}
]
},
{
"name": "ATTACHED",
"children": [
{"name": "high", "size": 220},
{"name": "med", "size": 179},
{"name": "low", "size": 322}
]
},
{
"name": "NOTIFIED",
"children": [
{"name": "high", "size": 883},
{"name": "med", "size": 132},
{"name": "low", "size": 1066}
]
},
{
"name": "INTEGRATED",
"children": [
{"name": "high", "size": 883},
{"name": "med", "size": 132},
{"name": "low", "size": 416}
]
},
{
"name": "DELIVERED",
"children": [
{"name": "high", "size": 170},
{"name": "med", "size": 701},
{"name": "low", "size": 410}
]
},
{
"name": "ESCALATED",
"children": [
{"name": "high", "size": 170},
{"name": "med", "size": 701},
{"name": "low", "size": 410}
]
},
{
"name": "COMMITTED",
"children": [
{"name": "high", "size": 170},
{"name": "med", "size": 701},
{"name": "low", "size": 410}
]
},
{
"name": "VERIFIED",
"children": [
{"name": "high", "size": 170},
{"name": "med", "size": 701},
{"name": "low", "size": 410}
]
},
{
"name": "SUBMITTED",
"children": [
{"name": "high", "size": 170},
{"name": "med", "size": 701},
{"name": "low", "size": 410}
]
}
]
}
// set width, height, and radius
var width = 650,
height = 475,
radius = (Math.min(width, height) / 2) - 10; // lowest number divided by 2. Then subtract 10
// legend dimensions
var legendRectSize = 15; // defines the size of the colored squares in legend
var legendSpacing = 6; // defines spacing between squares
var formatNumber = d3.format(",d"); // formats floats
var x = d3.scaleLinear() // continuous scale. preserves proportional differences
.range([0, 2 * Math.PI]); // setting range from 0 to 2 * circumference of a circle
var y = d3.scaleSqrt() // continuous power scale
.range([0, radius]); // setting range from 0 to radius
// setting color scheme
var color = {
'TOTAL': '#FFF',
'UNASSIGNED': '#DADFE1',
'ASSIGNED_TO_EDITOR': '#5BCAFF',
'ATTACHED': '#87D37C',
'ASSIGNED_TO_MENTOR': '#F64747',
'ASSIGNED_TO_REVIEWER': '#7BDDDD',
'ASSIGNED_TO_APPROVER': '#1e90ff',
'INTEGRATION_FAILED': '#F1A9A0',
'DELIVERED': '#4183D7',
'INTEGRATED': '#90C695',
'PUBLISHED': '#E4F1FE',
'COMMIT_FAILED': '#F62459',
'NOTIFIED': '#4ECDC4',
'BLOCKED': '#D24D57',
'ESCALATED': '#DB0A5B',
'SUBMITTED': '#86a531',
'REVIEWED': '#bfba00',
'APPROVED': '#C86DEF',
'ASSIGNED_TO_VERIFIER': '#D2527F',
'COMMITTED': '#5AD427',
'VERIFIED': '#81CFE0',
'CLOSED': '#CF000F'
};
var partition = d3.partition(); // subdivides layers
// define arcs
var arc = d3.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function(d) { return Math.max(0, y(d.y0)); })
.outerRadius(function(d) { return Math.max(0, y(d.y1)); });
// define tooltip
var tooltip = d3.select('body') // select element in the DOM with id 'chart'
.append('div') // append a div element to the element we've selected
.style("opacity","0")
.style("position","absolute");
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'label'); // add class 'label' on the selection
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'count'); // add class 'count' on the selection
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'percent'); // add class 'percent' on the selection
// define SVG element
var svg = d3.select("#chart").append("svg")
.attr("width", width) // set width
.attr("height", height) // set height
.append("g") // append g element
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
root = d3.hierarchy(root);
root.sum(function(d) { return d.size; });// must call sum on the hierarchy first
var path = svg.selectAll("path")
.data(partition(root).descendants()) // path for each descendant
.enter().append("path")
.attr("d", arc) // draw arcs
.style("fill", function (d) { return color[(d.children ? d : d.parent).data.name]; })
.on("click", click)
.append("title")
.text(function(d) { return d.data.name + "\n" + formatNumber(d.value);
});
// mouse event handlers are attached to path so they need to come after its definition
path.on('mouseover', function(d) { // when mouse enters div
var total = d.data.size
var percent = Math.round(1000 * d.value / total) / 10; // calculate percent
tooltip.select('.label').html(d.data.name); // set current label
tooltip.select('.count').html(total); // set current count
tooltip.select('.percent').html(percent + '%'); // set percent calculated above
tooltip.style('display', 'block'); // set display
});
path.on('mouseout', function() { // when mouse leaves div
tooltip.style('display', 'none'); // hide tooltip for that element
});
path.on('mousemove', function(d) { // when mouse moves
tooltip.style('top', (d3.event.layerY + 10) + 'px') // always 10px below the cursor
.style('left', (d3.event.layerX + 10) + 'px'); // always 10px to the right of the mouse
});
function click(d) {
svg.transition()
.duration(750)
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); };
})
.selectAll("path")
.attrTween("d", function(d) { return function() { return arc(d); }; });
}
d3.select(self.frameElement).style("height", height + "px");
// define legend element
var legendWidth = legendRectSize + legendSpacing; // height of element is the height of the colored square plus the spacing
var width= 500;
var height = 75; // height of element is the height of the colored square plus the spacing
var offset = 80; // vertical offset of the entire legend = height of a single element &
var svgw = 20;
var svgh = 20;
var legendContainer = d3.select("#legend").append("svg")
.attr("width", width) // set width
.attr("height", height) // set height
.append("g") // append g element
.attr("transform", function(d, i) {
return "translate(" + i * 20 + ",0)";
});
var legend = legendContainer.selectAll('.legend') // selecting elements with class 'legend'
.data(d3.entries(color)) // refers to an array of labels from our dataset
.enter() // creates placeholder
.append('g') // replace placeholders with g elements
.attr('class', 'legend') // each g is given a legend class
.style('background-color', 'orange')
.attr('transform', function(d, i) {
return "translate(0," + i * 20 + ")" //return translation
});
// adding colored squares to legend
legend.append('rect') // append rectangle squares to legend
.attr('x', 0)
.attr('y', 0)
.attr('width', 10) // width of rect size is defined above
.attr('height', 10) // height of rect size is defined above
.style('fill', function (d) { return color[d.key]; }) // each fill is passed a color
// adding text to legend
legend.append('text')
.attr('x', 20)
.attr('y', 10)
.attr("text-anchor", "start")
.text(function(d) { return d.key; }); // return label
function getRootmostAncestorByWhileLoop(node) {
while (node.depth > 1) node = node.parent;
return node;
}
html, body {
height: 100%;
}
path {
stroke: #fff;
}
/* legend */
#legend {
background-color:yellow;
}
.legend {
font-size: 14px;
float: left;
margin-right:1em;
}
rect {
stroke-width: 2;
}
/* #tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
} */
.tooltip {
opactiy: 0;
positon: absolute;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3.js Donut Chart</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Pacifico" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
</head>
<body>
<div id="chart"></div>
<div id="tooltip" class="hidden">
<p><span id="category"><strong>Important Label Heading</strong></span></p>
<p><span id="value">100</span></p>
</div>
<div id="legend"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
<script src="script.js"></script> <!-- remove if no javascript -->
</body>
</html>