bisamov
bisamov

Reputation: 730

How to place legends for stacked bar chart underneath in d3.js

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

Answers (1)

Coola
Coola

Reputation: 3142

There were a few things going wrong in your code, so I tried to fix some of it.

  1. 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.

  2. 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.

  3. 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.

  4. 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

Related Questions