Reputation: 730
I am trying to place the legends underneath the stacked bar chart aligned block. Can not transition it properly. Below is the code i have, at this moment the legends appear on the left top corner, what i am trying to do is the tranistion it properly underneath the stacked bar, is there any suggestion of how can i transition it so it fits the svg as well. Any suggestion will be appriciated
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Example</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
<style>
</style>
</head>
<style>
</style>
<body>
<div class="canvas">
</div>
<script>
var data = [
{month: "Q1-2016", apples: 3840, bananas: 1920, cherries: -1960},
{month: "Q2-2016", apples: 1600, bananas: 1440, cherries: -960},
{month: "Q3-2016", apples: 640, bananas: 960, cherries: -640},
{month: "Q4-2016", apples: 320, bananas: 480, cherries: -640},
{month: "Q5-2016", apples: 3840, bananas: 1920, cherries: -1960},
{month: "Q6-2016", apples: 1600, bananas: 1440, cherries: -960},
{month: "Q7-2016", apples: 640, bananas: 960, cherries: -640},
{month: "Q8-2016", apples: 320, bananas: 480, cherries: -640},
{month: "Q9-2016", apples: 3840, bananas: 1920, cherries: -1960},
{month: "Q10-2016", apples: 1600, bananas: 1440, cherries: 960},
{month: "Q11-2016", apples: 640, bananas: 960, cherries: -640},
{month: "Q12-2016", apples: 320, bananas: 480, cherries: -640},
];
var series = d3.stack()
.keys(["apples", "bananas", "cherries"])
.offset(d3.stackOffsetDiverging)
(data);
var margin = {top: 20, right: 30, bottom: 30, left: 60},
width = 900,
height = 600,
padding = 40,
svg = d3.select(".canvas").append('svg').attr('height', height).attr('width',width);
var x = d3.scaleBand()
.domain(data.map(function(d){return d.month;}))
.rangeRound([margin.left, width-margin.right])
.padding(0.1);
var y = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.rangeRound([height - margin.bottom, margin.top]);
var colors = ["#66b3ff", "#b3d9ff", "#99ddff", "#99ffdd"];
var z = d3.scaleOrdinal(colors);
//create and call the axes
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y);
console.log(series);
svg.append('g')
.selectAll('g')
.data(series)
.enter().append('g')
.attr('fill', function (d) {
return z(d.key);
})
.selectAll('rect')
.data(function(d){ return d; })
.enter().append('rect')
.attr('width', x.bandwidth)
.attr('x', function(d){ return x(d.data.month)})
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
svg.append("g")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y));
var legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', 'translate(' + (padding + 12) + ',0)');
legend.selectAll('rect')
.data(series)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', function(d,i){
return i * 18;
})
.attr('width', 12)
.attr('height', 12)
.attr('fill', function(d,i){
return z(i);
});
legend.selectAll('text')
.data(series)
.enter()
.append('text')
.text(function(d){
return d.key;
})
.attr('x', -18)
.attr('y', function(d, i){
return i * 18;
})
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'hanging');
function stackMin(serie) {
return d3.min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return d3.max(serie, function(d) { return d[1]; });
}
</script>
</body>
</html>
Upvotes: 0
Views: 1274
Reputation: 3142
There were a few things going wrong in your code, so I tried to fix some of it.
Your chart elements (i.e. the bars and axis) should be added to a group. This will allow it to be moved/translated appropriately as needed. I create a chart
variable and assigned these elements to it, rather than directly to the svg. This makes the structure easier to see when you view in the console too.
The height of the legend area should be declared and accounted for in the chart. I declared this as a variable legendh
and account for it in the y axis range.
If you want your legend group to appear below the chart it needs to be translated below the chart. Your earlier transform on the group mentioned .attr('transform', 'translate(' + (padding + 12) + ',0)');
making the y coordinates 0. I changed it to .attr('transform', 'translate(' + (padding + 12) + ','+ (height - legendh) + ')');
. This tells the legend group to move down from the top by the height - legendh
amount, thus placing it below the chart.
Lastly, the colors in the legend were not matching the colors in the chart/bars. This was because your bar fill was goverened by d.key
but your legend fill was based on i
. I chose to make it uniform and get the color using z(i)
.
Here is the working block: https://bl.ocks.org/akulmehta/80153b35ab7498d30408f92cfa50f356
Here is the working code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Example</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
<style>
</style>
</head>
<style>
</style>
<body>
<div class="canvas">
</div>
<script>
var data = [
{month: "Q1-2016", apples: 3840, bananas: 1920, cherries: -1960},
{month: "Q2-2016", apples: 1600, bananas: 1440, cherries: -960},
{month: "Q3-2016", apples: 640, bananas: 960, cherries: -640},
{month: "Q4-2016", apples: 320, bananas: 480, cherries: -640},
{month: "Q5-2016", apples: 3840, bananas: 1920, cherries: -1960},
{month: "Q6-2016", apples: 1600, bananas: 1440, cherries: -960},
{month: "Q7-2016", apples: 640, bananas: 960, cherries: -640},
{month: "Q8-2016", apples: 320, bananas: 480, cherries: -640},
{month: "Q9-2016", apples: 3840, bananas: 1920, cherries: -1960},
{month: "Q10-2016", apples: 1600, bananas: 1440, cherries: 960},
{month: "Q11-2016", apples: 640, bananas: 960, cherries: -640},
{month: "Q12-2016", apples: 320, bananas: 480, cherries: -640},
];
var series = d3.stack()
.keys(["apples", "bananas", "cherries"])
.offset(d3.stackOffsetDiverging)
(data);
var margin = {top: 20, right: 30, bottom: 30, left: 60},
width = 900,
height = 500,
legendh = 100, //determines the height of the legend below the chart
padding = 40,
svg = d3.select(".canvas").append('svg').attr('height', height).attr('width',width);
var x = d3.scaleBand()
.domain(data.map(function(d){return d.month;}))
.rangeRound([margin.left, width-margin.right])
.padding(0.1);
var y = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.rangeRound([height - margin.bottom - legendh, margin.top]);
var colors = ["#66b3ff", "#b3d9ff", "#99ddff", "#99ffdd"];
var z = d3.scaleOrdinal(colors);
//create and call the axes
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y);
var chart = svg.append('g').attr('id','chart'); //make a chart group inside the svg
chart.append('g')
.selectAll('g')
.data(series)
.enter().append('g')
.attr('fill', function (d,i) { //because the legend is based on i this should also be based on i
return z(i);
})
.selectAll('rect')
.data(function(d){ return d; })
.enter().append('rect')
.attr('width', x.bandwidth)
.attr('x', function(d){ return x(d.data.month)})
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
chart.append("g")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.axisBottom(x));
chart.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y));
var legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', 'translate(' + (padding + 12) + ','+ (height - legendh) + ')');
legend.selectAll('rect')
.data(series)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', function(d,i){
return i * 18;
})
.attr('width', 12)
.attr('height', 12)
.attr('fill', function(d,i){
console.log(z(i));
return z(i);
});
legend.selectAll('text')
.data(series)
.enter()
.append('text')
.text(function(d){
return d.key;
})
.attr('x', 15)
.attr('y', function(d, i){
return i * 18;
})
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'hanging');
function stackMin(serie) {
return d3.min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return d3.max(serie, function(d) { return d[1]; });
}
</script>
</body>
</html>
Upvotes: 1