Search code examples
javascriptarraysd3.jsstackedbarseries

d3.js stacked bar with toggleable series


this time I am trying to create a stacked bar with toggleable series- based on Mike Bostock's example (thanks once more Mike!) I have already succeeded into making it responsive and zoomable, and the toggleable series through a legend is the last thing remaining.

I created the legend items, and applied the correct color by using keys:

var legendItem = d3.select(".legend")
  .selectAll("li")
  .data(keys)
  .enter()
  .append("li")
  .on('click', function(d) {
    keys.forEach(function(c) {
      if (c != d) tKeys.push(c)
    });
    fKeys = tKeys;
    tKeys = [];
    redraw();
  });

legendItem
  .append("span")
  .attr("class", "color-square")
  .style("color", function(d) {
    return colorScale5(d);
  });

legendItem
  .append("span")
  .text(function(d) {
    return (d)
  });

Based on the structure, in order to create the toggleable item, I came to the conclusion that I somehow have to be able to toggle it from the keys AND the dataset - or is there another way to do it? I have managed to remove a specific key from the keys, but not from the dataset, I have no idea how to map it properly.

The second issue is that I can't figure of a way to toggle a key, but just remove it. This is the original dataset:

var data = [{
  "country": "Greece",
  "Vodafone": 57,
  "Wind": 12,
  "Cosmote": 20
}, {
  "country": "Italy",
  "Vodafone": 40,
  "Wind": 24,
  "Cosmote": 35
}, {
  "country": "France",
  "Vodafone": 22,
  "Wind": 9,
  "Cosmote": 9
}]

In the values were provided from a nested dataset, I could attach a key named 'enabled' to each object and could easily filter the dataset, but can't figure out how to attach a key to help in the filtering proccess.

edit3 Removed useless information from the question:

Here is a working fiddle: https://jsfiddle.net/fgseaxoy/2/


Solution

  • SergGr's code works well, but some parts can be cleaner.

    onclick

    var fKeys = keys.slice();
    
    //a helper object to record the state of keys 
    var fKeyReference = fKeys.map(function () {
        return true; //used to indicate if the corresponding key is active
    });
    
    function getActiveKeys(reference) {
        return reference.map(function (state, index) {
            if (state) {
                return keys[index]; //just keep keys whoes state is true
            }
            return false; //return false to be filered
        }).filter(function (name) {
            return name
        });
    }
    
    ...
    .on('click', function (d) {
        if (fKeys.length === 1 && fKeys[0] === d) {
            return;
        }
    
        var index = keys.indexOf(d);
        fKeyReference[index] = !fKeyReference[index]; // toggle state of fKeyReference
        fKeys = getActiveKeys(fKeyReference);
        redraw();
    });
    

    .stack()

    g.selectAll(".d3-group").remove();//remove all groups and draw them all again
    stackedBars = g
        .selectAll(".d3-group")
        .data(d3.stack().keys(fKeys)(dataset));
    

    update the axis (y.domain)

    y.domain([
        0,
        1.2 * d3.max(dataset, function (d) {
            return fKeys.reduce(function (pre, key) {//calculate the sum of values of fKeys
                return pre + d[key];
            }, 0);
        })
    ]);
    

    And finally, jsfiddle