简体   繁体   中英

d3 filtering bar chart with legend toggling

I am trying to filter/update a bar chart with legend toggling. I am unsure how to set active states on the bars during initialization - then trying to deactivate - exclude the required datasets on toggle, but restore them when the active states come back.

http://jsfiddle.net/5ruhac83/5/

//legend toggling

legend.append("rect")
  .attr("x", width - 18)
  .attr("width", 18)
  .attr("height", 18)
  .style("fill", function(d, i) {
    return colores_google(i);
  })
  .on("click", function(name) {

    var active = false;

    newState = active ? "active" : "inactive";

    // Hide or show the elements
    d3.select(this).attr("class", newState);

    //set active state

        console.log("name", name)
    toggleBar(name)
  });

//animating the bars - with a pruned data set

function toggleBar(name) {
  var hiddenClassName = 'hidden',
    bar = chartHolder.selectAll('.bars'),
    currentBars = bar.selectAll('[value="' + name + '"]')

  currentBars.classed(hiddenClassName, !currentBars.classed(hiddenClassName))

  var barData = data.map(item => {
    item.valores = item.valores.map(valor => {
      return Object.assign({}, valor, {
        value: bar.selectAll('[value="' + valor.name + '"]').classed(hiddenClassName) ?
          0 : item[valor.name]
      })
    })
    return item;
  })




  var barData = [{
    label: "a",
    "Current Period": 20
  }, {
    label: "b",
    "Current Period": 15
  }, {
    label: "c",
    "Current Period": 25
  }, {
    label: "d",
    "Current Period": 5
  }];

  var options = getOptions(barData);
  barData = refactorData(barData, options);

  console.log("barData", barData)

  bar
    .data(barData)

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

  rect
    .transition()
    .duration(1000)
    .delay(100)
    .attr("width", x0.rangeBand() / 2)
    .attr("y", function(d) {
      return y(d.value);
    })
    .attr("height", function(d) {
      return height - y(d.value);
    });

  rect.exit().remove();

  /*
    var bar = bar.selectAll("rect")

     bar.transition()
        //.attr("id", function(d){ return 'tag'+d.state.replace(/\s|\(|\)|\'|\,+/g, '');})
        .attr("x", function(d) { return x1(d.name); })
        .attr("width", x0.rangeBand())
        .attr("y", function(d) {
            return 0;
          //return y(d.value); 
         })
        .attr("height", function(d) { 
            return 0;
          //return height - y(d.value); 
         });

      //bar.exit().remove();
    */

}

Here's a chart which resets the domain with new options based on the toggled legend:

JS Fiddle DEMO

function toggleBar(name, state) {
    data.forEach(function(d) { 
       _.findWhere(d.valores, {name: name}).hidden = state;
    });
    var filteredOptions;
    if(state) {
        filteredOptions = options.filter(function(d) { return d !== name; });
    } else {
      filteredOptions = options;
    }

    x1.domain(filteredOptions).rangeRoundBands([0, x0.rangeBand()]);

    y.domain([0, d3.max(data, function(d) {
      return d3.max(d.valores.filter(function(k) { return !k.hidden;}), function(d) {
        return d.value;
      });
    })]);

Changes:

  1. You don't need to reset the data on every toggle. I just added a hidden attribute to the "valores" and the while resetting the domain in the toggleBar function, filtered the data based on non-hidden options and set the domain accordingly.

  2. I'd recommend to get used to d3's "enter, update and exit" methods. I hope the code helps you understand that as well. drawBars() is a function that does that.

  3. Changed the way the tooltip is rendered as well. Instead of using querySelector for hovered elements (that's definitely one way), you can just use the parent node's data using the datum() function.

  4. Legends: I've added a stroke for every legend and to indicate whether the corresponding option is hidden or not, the fill-opacity is toggled on every click.

  5. Used a separate color scale with an ordinal domain of options and range to be same as previous colors so that the colors are based on the names and not indices (as before)

  6. Added simple transitions.

  7. Used underscore.js in toggleBars() function. You could switch back to pure JS as well.

And to answer your question on active states , please check for toggling of the "clicked" classnames.

Please go through the code and let me know if you any part of it is unclear. I'll add some comments too.

:)

here is a solution for grouped bar chart legend toggling with animation.

//jsfiddle - http://jsfiddle.net/0ht35rpb/259/

var $this =  this.$('.barChart');

var w = $this.data("width");
var h = $this.data("height");

//var configurations = $this.data("configurations");

var data = [{
  "State": "a",
  "AA": 100,
  "BB": 200
}, {
  "State": "b",
  "AA": 454,
  "BB": 344
},{
  "State": "c",
  "AA": 140,
  "BB": 500
}, {
  "State": "d",
  "AA": 154,
  "BB": 654
}];

var yLabel = "Count";


var svg = d3.select($this[0]).append("svg"),
  margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 40
  },
  width = w - margin.left - margin.right,
  height = h - margin.top - margin.bottom,
  g = svg
  .attr("width", w)
  .attr("height", h)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// The scale spacing the groups:
var x0 = d3.scaleBand()
  .rangeRound([0, width])
  .paddingInner(0.1);

// The scale for spacing each group's bar:
var x1 = d3.scaleBand()
  .padding(0.05);

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

var z = d3.scaleOrdinal()
  .range(["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f", "#8bf41b"]);

var keys = d3.keys(data[0]).slice(1);

x0.domain(data.map(function(d) {
  return d.State;
}));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) {
  return d3.max(keys, function(key) {
    return d[key];
  });
})]).nice();

g.append("g")
  .selectAll("g")
  .data(data)
  .enter().append("g")
  .attr("class", "bar")
  .attr("transform", function(d) {
    return "translate(" + x0(d.State) + ",0)";
  })
  .selectAll("rect")
  .data(function(d) {
    return keys.map(function(key) {
      return {
        key: key,
        value: d[key]
      };
    });
  })
  .enter().append("rect")
  .attr("x", function(d) {
    return x1(d.key);
  })
  .attr("y", function(d) {
    return y(d.value);
  })
  .attr("width", x1.bandwidth())
  .attr("height", function(d) {
    return height - y(d.value);
  })
  .attr("fill", function(d, i) {            
    return z(d.key);
  });

g.append("g")
  .attr("class", "axis")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(x0));

g.append("g")
  .attr("class", "yaxis")
  .call(d3.axisLeft(y).ticks(null, "s"))
  .append("text")
  .attr("x", 2)
  .attr("y", y(y.ticks().pop()) + 0.5)
  .attr("dy", "0.32em")
  .attr("fill", "#000")
  .attr("font-weight", "bold")
  .attr("text-anchor", "start")
  .text(yLabel);

var legend = g.append("g")
  .attr("font-family", "sans-serif")
  .attr("font-size", 10)
  .attr("text-anchor", "end")
  .selectAll("g")
  .data(keys.slice().reverse())
  .enter().append("g")
  .attr("transform", function(d, i) {
    return "translate(0," + i * 20 + ")";
  });

legend.append("rect")
  .attr("x", width - 17)
  .attr("width", 15)
  .attr("height", 15)
  .attr("fill", z)
  .attr("stroke", z)
  .attr("stroke-width", 2)
  .on("click", function(d) {
    update(d)
  });

legend.append("text")
  .attr("x", width - 24)
  .attr("y", 9.5)
  .attr("dy", "0.32em")
  .text(function(d) {
    return d;
  });

var filtered = [];

////
//// Update and transition on click:
////

function update(d) {

  //
  // Update the array to filter the chart by:
  //

  // add the clicked key if not included:
  if (filtered.indexOf(d) == -1) {
    filtered.push(d);
    // if all bars are un-checked, reset:
    if (filtered.length == keys.length) filtered = [];
  }
  // otherwise remove it:
  else {
    filtered.splice(filtered.indexOf(d), 1);
  }

  //
  // Update the scales for each group(/states)'s items:
  //
  var newKeys = [];
  keys.forEach(function(d) {
    if (filtered.indexOf(d) == -1) {
      newKeys.push(d);
    }
  })
  x1.domain(newKeys).rangeRound([0, x0.bandwidth()]);
  y.domain([0, d3.max(data, function(d) {
    return d3.max(keys, function(key) {
      if (filtered.indexOf(key) == -1) return d[key];
    });
  })]).nice();

//g.select(".yaxis")
//.call(d3.axisLeft(y).ticks(null, "s"));

var t0 = svg.transition().duration(250);
var t1 = t0.transition();
t1.selectAll(".yaxis").call(d3.axisLeft(y).ticks(null, "s"));


  //
  // Filter out the bands that need to be hidden:
  //
  var bars = svg.selectAll(".bar").selectAll("rect")
    .data(function(d) {
      return keys.map(function(key) {
        return {
          key: key,
          value: d[key]
        };
      });
    })

  bars.filter(function(d) {
      return filtered.indexOf(d.key) > -1;
    })
    .transition()
    .attr("x", function(d) {
      return (+d3.select(this).attr("x")) + (+d3.select(this).attr("width")) / 2;
    })
    .attr("height", 0)
    .attr("width", 0)
    .attr("y", function(d) {
      return height;
    })
    .duration(500);

  //
  // Adjust the remaining bars:
  //
  bars.filter(function(d) {
      return filtered.indexOf(d.key) == -1;
    })
    .transition()
    .attr("x", function(d) {
      return x1(d.key);
    })
    .attr("y", function(d) {
      return y(d.value);
    })
    .attr("height", function(d) {
      return height - y(d.value);
    })
    .attr("width", x1.bandwidth())
    .attr("fill", function(d, i) {
      return z(d.key);
    })
    .duration(500);

  // update legend:
  legend.selectAll("rect")
    .transition()
    .attr("fill", function(d, i) {
      if (filtered.length) {
        if (filtered.indexOf(d) == -1) {
          return z(d);
        } else {
          return "white";
        }
      } else {
        return z(d);
      }
    })
    .duration(100);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM