Fred
Fred

Reputation: 649

d3.js - updating stacked bar chart with new dataset

I’m starting with d3.js and built a simple stacked chart.

Now I want to be able to update the chart with new dataset on click.

I followed tutorials and especially the Thinking with Joins article, the General Update Pattern example and this stackoverflow question but I didn’t manage to apply the enter/update/exit logic to my example.

As you can see in this fiddle, the updated axis are placed on top of the previous one and the chart doesn’t update with the new data.

var data1 = [
    {month: 'Jan', A: 20, B: 5, C: 10},
    {month: 'Feb', A: 30, B: 10, C: 20}
];

var data2 = [
    {month: 'Mar', A: 10, B: 55, C: 100},
    {month: 'Apr', A: 10, B: 70, C: 2}
];

var xData = ["A", "B", "C"];

var margin = {top: 20, right: 50, bottom: 30, left: 50},
        width = 400 - margin.left - margin.right,
        height = 300 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
        .rangeRoundBands([0, width], 0.35);

var y = d3.scale.linear()
        .rangeRound([height, 0]);

var color = d3.scale.category20();

var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");

var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left");

var svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


function draw(data) {
    var dataIntermediate = xData.map(function (c) {
        return data.map(function (d) {
            return {x: d.month, y: d[c]};
        });
    });

    var dataStackLayout = d3.layout.stack()(dataIntermediate);

    x.domain(dataStackLayout[0].map(function (d) {
        return d.x;
    }));

    y.domain([0,
        d3.max(dataStackLayout[dataStackLayout.length - 1],
                function (d) { return d.y0 + d.y;})
        ])
      .nice();

    var layer = svg.selectAll(".stack")
            .data(dataStackLayout);

            layer.exit().remove(); // has no effect

            layer.enter().append("g")
            .attr("class", "stack")
            .style("fill", function (d, i) {
                return color(i);
            });

    var rect = layer.selectAll("rect")
            .data(function (d) {
                return d;
            });

            rect.exit().remove(); // has no effect

            rect.enter().append("rect")
                .attr("x", function (d) {
                    return x(d.x);
                })
                .attr("y", function (d) {
                    return y(d.y + d.y0);
                })
                .attr("height", function (d) {
                    return y(d.y0) - y(d.y + d.y0);
                })
                .attr("width", x.rangeBand());



    svg.append("g")
            .attr("class", "axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis);

    svg.append("g")
            .attr("class", "axis")
            .call(yAxis);
}

function updateData() {
    draw(data2);
}

d3.select('#update')
    .on("click", updateData);

draw(data1);

Could you please explain where to insert the exit there and why?

Many thanks

Upvotes: 1

Views: 441

Answers (1)

eko
eko

Reputation: 40647

To summarize the enter/update/exit logic;

When you first create your svg, the enter part takes the new nodes and appends them to the svg.

When you update your svg, the select part updates your data and styles. And the exit part removes the data as you know.

So according to this your pattern is almost correct but when you select your new data(update), you are not updating your styles.

Here's the part you need to change:

var rect = layer.selectAll("rect")
            .data(function (d) {
                return d;
            }).attr("x", function (d) {
                    return x(d.x);
                })
                .attr("y", function (d) {
                    return y(d.y + d.y0);
                })
                .attr("height", function (d) {
                    return y(d.y0) - y(d.y + d.y0);
                })
                .attr("width", x.rangeBand());

And here's the updated fiddle: https://jsfiddle.net/8gp8x89c/2/

Note that the axis' are still present so you either remove and re-append them or apply the update pattern to them also. I leave that part to you.

Upvotes: 1

Related Questions