malisit
malisit

Reputation: 1258

d3js updates only once

I have a visualization task that I need to make it done with d3.js. Here's my code.

                var w = 700;
                var h = 500;
                var offset = 100;
                var padding = 20;
                var colors = d3.scale.category10();
                var svg = d3.select("body")
                    .append("svg")
                    .attr("width", w)
                    .attr("height", h);




                var texts = function(ds,ds2){
                    var stack = d3.layout.stack();
                    stack_data = stack(ds);

                    var xScale = d3.scale.ordinal()
                        .domain(d3.range(ds[0].length))
                        .rangeRoundBands([0, w-offset], 0.50);


                    var yScale = d3.scale.linear()
                        .domain([0,             
                            d3.max(stack_data, function(d) {
                                return d3.max(d, function(d) {
                                    return d.y0 + d.y -20;
                                });
                            })
                        ])
                        .range([padding, h-50]);

                    var xAxis = d3.svg.axis()
                        .scale(xScale)
                        .orient("bottom")
                        .ticks(ds[0].length);

                    gs = svg.selectAll("g").data(stack_data);

                    for (var i5 = 0; i5 < ds.length; i5++) {
                        gs.enter()
                        .append("g")
                        .attr("class", "stacked_bars")
                        .attr("fill", function(d, i) {
                            return colors(i);
                        });

                        asd = gs.selectAll("rect").data(function(d) { return d; });

                        asd.enter().append("rect");

                        asd.transition()
                            .duration(1000)
                            .attr("x", function(d, i) {
                                return xScale(i);
                            })
                            .attr("y", function(d) {
                                return h - yScale(d.y0) - yScale(d.y);
                            })
                            .attr("height", function(d) {
                                return yScale(d.y);
                            })
                            .attr("width", xScale.rangeBand())
                            .attr("class", "rectbar");

                    };

                    gs.append("g")  // add a group element to the svg
                        .attr("class", "axis") //Assign class "axis" to group
                        .attr("transform", "translate(0," + (h - padding) + ")")  // shift the axis to bottom
                        .call(xAxis);   // call the axis function 

                    gs.exit().remove();
}


                res = dataGenerator("Europe");
                dataset = res[0];
                dataset2 = res[1];

                texts(dataset,dataset2);

            d3.select("#selector").on("change", function() {
                    cont = d3.select(this).property('value');
                    res = dataGenerator(cont)

                    dataset = res[0]
                    dataset2 = res[1]

                    //svg.selectAll(".sym").remove()

                    texts(dataset,dataset2);


                }); 

It basically gets the data and generates stacked bars. When user uses the select element on the page, it updates the data and generates new results. It successfully gets the first results and when user selects another option, it makes it happen also. But when user tries to use select part once again. It only generates bars for dataset's first item.

So, in this particular case I have countries and their data as numbers, at first load and first update it successfully shows all but when it comes to second update, it only generate bars for first country in dataset. It's been hours that I'm trying to fix this. I know I only have a little mistake but couldn't make it to solve.

Also here's the jsfiddle of the code: https://jsfiddle.net/510ep9ux/4/

Since I'm new at d3.js, I may not understand the update concept well. So, any guesses?

Upvotes: 1

Views: 315

Answers (2)

paradite
paradite

Reputation: 6436

Solved, using two separate functions textsInit and textsUpdate :

https://jsfiddle.net/qxqdp36x/2/

Essentially you need to separate initialization and update logic, and avoid re-creating elements when updating, that causes unintended behaviours.

Also, the variables gs and asd needs to be global to be accessible to both functions.

var textsInit = function(ds, ds2) {

    var stack = d3.layout.stack();
    stack_data = stack(ds);

    var xScale = d3.scale.ordinal()
      .domain(d3.range(ds[0].length))
      .rangeRoundBands([0, w - offset], 0.50);


    var yScale = d3.scale.linear()
      .domain([0,
        d3.max(stack_data, function(d) {
          return d3.max(d, function(d) {
            return d.y0 + d.y - 20;
          });
        })
      ])
      .range([padding, h - 50]);

    var xAxis = d3.svg.axis()
      .scale(xScale)
      .orient("bottom")
      .ticks(ds[0].length);
    gs = svg.selectAll("g").data(stack_data);

    bars = gs.enter();
    bars.append("g")
      .attr("class", "stacked_bars")
      .attr("fill", function(d, i) {
        return colors(i);
      });

    asd = gs.selectAll("rect").data(function(d) {
      return d;
    });

    asd.enter().append("rect");

    asd.transition()
      .duration(1000)
      .attr("x", function(d, i) {
        return xScale(i);
      })
      .attr("y", function(d) {
        return h - yScale(d.y0) - yScale(d.y);
      })
      .attr("height", function(d) {
        return yScale(d.y);
      })
      .attr("width", xScale.rangeBand())
      .attr("class", "rectbar");

    gs.append("g") // add a group element to the svg
      .attr("class", "axis") //Assign class "axis" to group
      .attr("transform", "translate(0," + (h - padding) + ")") // shift the axis to bottom
      .call(xAxis); // call the axis function 
  }

And:

var textsUpdate = function(ds, ds2) {
    var stack = d3.layout.stack();
    stack_data = stack(ds);

    var xScale = d3.scale.ordinal()
      .domain(d3.range(ds[0].length))
      .rangeRoundBands([0, w - offset], 0.50);

    var yScale = d3.scale.linear()
      .domain([0,
        d3.max(stack_data, function(d) {
          return d3.max(d, function(d) {
            return d.y0 + d.y - 20;
          });
        })
      ])
      .range([padding, h - 50]);

    gs.data(stack_data);
        asd = gs.selectAll("rect").data(function(d) { return d; });
      asd.transition()
        .duration(1000)
        .attr("x", function(d, i) {
          return xScale(i);
        })
        .attr("y", function(d) {
          return h - yScale(d.y0) - yScale(d.y);
        })
        .attr("height", function(d) {
          return yScale(d.y);
        })
        .attr("width", xScale.rangeBand())
        .attr("class", "rectbar");
  }

Edited to fix a small bug, updating the asd selection's data.

Upvotes: 2

Eric Guan
Eric Guan

Reputation: 15992

I made 2 simple but crucial changes to your code.

https://jsfiddle.net/guanzo/510ep9ux/6/ From

gs = svg.selectAll("g").data(stack_data);

to

gs = svg.selectAll("g.stacked_bars").data(stack_data);

The axis is also contained in a g element, so you have to ensure you're only selecting elements that are used for your data, and not unrelated elements.

From

gs.append("g")
.attr("class", "axis") 
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);

to

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

If you go into the browser inspector you'll see that you have an axis element for EVERY stacked_bars element, you only need 1 obviously. It only looks like there's 1 axis because they're absolutely positioned and stacked on top of each other.

I changed it so that the axis is appended when the svg is created, and every time new data is selected, the axis will update itself.

Upvotes: 1

Related Questions