I am using the below example and wanted to have the legend outside the Pie chart and also have the Polyline for the Text and the count and Percentage for each slice.
With the current code I have Pie inside the pie and Text and Percentage are showing when I mouse over the slice.
Appreciate the help a lot.Thanks
Can some one please help as I am unable to move forward.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="normalize.css">
<style>
#chart {
height: 360px;
margin: 0 auto; /* NEW */
position: relative;
width: 360px;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
display: none;
font-size: 12px;
left: 130px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
width: 80px;
z-index: 10;
}
.legend {
font-size: 12px;
}
rect {
cursor: pointer; /* NEW */
stroke-width: 2;
}
rect.disabled { /* NEW */
fill: transparent !important; /* NEW */
}
/* NEW */
h1 { /* NEW */
font-size: 14px; /* NEW */
text-align: center; /* NEW */
}
/* NEW */
</style>
</head>
<body>
<div id="chart"></div>
<script src="Scripts/d3.v3.min.js"></script>
<script>
(function(d3) {
'use strict';
var width = 360;
var height = 360;
var radius = Math.min(width, height) / 2;
var donutWidth = 75;
var legendRectSize = 18;
var legendSpacing = 4;
var color = d3.scale.category20(); //builtin range of colors
var svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.svg.arc()
.innerRadius(radius - donutWidth)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d) { return d.count; })
.sort(null);
var tooltip = d3.select('#chart')
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'label');
tooltip.append('div')
.attr('class', 'count');
tooltip.append('div')
.attr('class', 'percent');
d3.csv('weekdays.csv', function(error, dataset) {
dataset.forEach(function(d) {
d.count = +d.count;
d.enabled = true; // NEW
});
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
}) // UPDATED (removed semicolon)
.each(function(d) { this._current = d; }); // NEW
path.on('mouseover', function(d) {
var total = d3.sum(dataset.map(function(d) {
return (d.enabled) ? d.count : 0; // UPDATED
}));
var percent = Math.round(1000 * d.data.count / total) / 10;
tooltip.select('.label').html(d.data.label);
tooltip.select('.count').html(d.data.count);
tooltip.select('.percent').html(percent + '%');
tooltip.style('display', 'block');
});
path.on('mouseout', function() {
tooltip.style('display', 'none');
});
/* OPTIONAL
path.on('mousemove', function(d) {
tooltip.style('top', (d3.event.pageY + 10) + 'px')
.style('left', (d3.event.pageX + 10) + 'px');
});
*/
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color) // UPDATED (removed semicolon)
.on('click', function(label) { // NEW
var rect = d3.select(this); // NEW
var enabled = true; // NEW
var totalEnabled = d3.sum(dataset.map(function(d) { // NEW
return (d.enabled) ? 1 : 0; // NEW
})); // NEW
if (rect.attr('class') === 'disabled') { // NEW
rect.attr('class', ''); // NEW
} else { // NEW
if (totalEnabled < 2) return; // NEW
rect.attr('class', 'disabled'); // NEW
enabled = false; // NEW
} // NEW
pie.value(function(d) { // NEW
if (d.label === label) d.enabled = enabled; // NEW
return (d.enabled) ? d.count : 0; // NEW
}); // NEW
path = path.data(pie(dataset)); // NEW
path.transition() // NEW
.duration(750) // NEW
.attrTween('d', function(d) { // NEW
var interpolate = d3.interpolate(this._current, d); // NEW
this._current = interpolate(0); // NEW
return function(t) { // NEW
return arc(interpolate(t)); // NEW
}; // NEW
}); // NEW
}); // NEW
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
});
})(window.d3);
</script>
</body>
</html>
You can place the legends where ever you wish by making the legends in a group and placing it using the translate
First Make SVG:
var s = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
Now make a legend group:
var legend_group = s.append('g').attr('transform',
'translate(' + (width / 3) + ',' + (height / 1.4) + ')');
Use translate it to a place of your choice. I have moved it to (width/3, height/1.4)
Make a group in which the pie chart will be drawn.
var svg = s.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (radius) + ')');
Lets make a polyline for each slice: This function will make as many polylines as the dataset length.
function makePolyLines() {
var polyline = svg.selectAll("polyline")
.data(pie(dataset), key);
polyline.enter()
.append("polyline");
//hide polyline for which value is 0, a case when legend is clicked.
svg.selectAll("polyline").style("display", function(d) {
if (d.value == 0) {
return "none";
} else {
return "block";
}
});
polyline.transition().duration(1000)
.attrTween("points", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
return [arc.centroid(d2), outerArc.centroid(d2), pos];
};
});
polyline.exit()
.remove();
}
Similarly make text for labels.
function makeTexts() {
var text = svg.selectAll(".labels")
.data(pie(dataset), key);
text.enter()
.append("text")
.attr("dy", ".35em")
.classed("labels", true)
.text(function(d) {
return d.data.label + " (" + d.data.count + ")";
});
//hide text for which value is 0, a case when legend is clicked.
svg.selectAll(".labels").style("display", function(d) {
if (d.value == 0) {
return "none";
} else {
return "block";
}
});
text.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
};
})
.styleTween("text-anchor", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start" : "end";
};
});
text.exit()
.remove();
}
finally call these two functions.
1) initially after the data is fetched.
2) whenever legend is clicked and the piechart is updated.
Working code here
First, you need to make the svg
element wider. Currently it's var width = 360;
, you can change it to var width = 700;
for example.
After you gained some more space, determine the width of the legend, for the example let's use 300px. Declare a new variable: var legendWidth = 300;
Now, when the legend is being declared:
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = (-2 * legendRectSize);
var vert = i * height - offset;
return 'translate(' + (horz) + ',' + vert + ')';
});
When calculation to horizontal translation, we need to take the legendWidth
into consideration:
var horz = (-2 * legendRectSize) - legendWidth;
Note: You will need to fix the left
and top
CSS properties for the .tooltip
element.
Another note: If you want to take this solution to the next level, you can implement it in a dynamic way instead of having the "magic number" of var legendWidth = 300
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With