TommyF
TommyF

Reputation: 7150

D3v4 Stacked Barchart Tooltip

I'm using d3.stack() to create a normalized stacked barchart.
But I'm having trouble accessing the relevant values of the inital dataset for a mousover tooltip.

serie.selectAll("rect")
    .data(function(d) { return d; })
    .enter().append("rect")
      ...
      .on("mousemove", function(d){
          let coords = d3.mouse(svg.node());
          tooltip.style("left", coords[0] + "px");
          tooltip.style("top", coords[1] - 70 + "px");
          tooltip.style("display", "inline-block");
          tooltip.html("HOW TO ACCESS DATA HERE?");
        });

d is the Array[2] at this point with the values defining baseline / topline, d.data is the complete original data object but is missing the information over which stack of the series I'm currently hovering.
Ideally I want the tooltip to show for a dataset like {name:"item1", foo:10, bar:20}

value: 10
percentage: 33%

when hovering above the foo rect.
All the examples I've found are for D3v3 where you can simply use d.y to access the relevant value but that doesn't seem to work any more with D3v4.

Upvotes: 5

Views: 3584

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

You can get the name of the element accessing the parent's data, and using that name to get the value:

.on('mouseover', function(d, i) {
    var thisName = d3.select(this.parentNode).datum().key;
    var thisValue = d.data[thisName];
    var total = d.data.foo + d.data.bar;
    tooltip.html("Name: " + thisName + "<br>Value: " + thisValue + "<br>Percentage: " + thisValue / total + "%");
});

Here is your code with that modification:

var svg = d3.select("svg"),
    margin = {
        top: 20,
        right: 60,
        bottom: 30,
        left: 40
    },
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x = d3.scaleBand()
    .rangeRound([0, width])
    .padding(0.1)
    .align(0.1);

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

var colors = ["#FF0000", "#00FF00", "#0000FF"];

var data = [{
    name: "item1",
    foo: 10,
    bar: 20
}, {
    name: "item2",
    foo: 50,
    bar: 50
}];

x.domain(data.map(function(d) {
    return d.name;
}));

var serie = g.selectAll(".serie")
    .data(stack.keys(["foo", "bar"])(data))
    .enter().append("g")
    .attr("class", "serie")
    .attr("fill", function(d, i) {
        return colors[i];
    });


var tooltip = d3.select('#tooltip');

var rects = serie.selectAll("rect")
    .data(function(d) {
        return d;
    })
    .enter().append("rect")

rects.attr("x", function(d) {
        return x(d.data.name);
    })
    .attr("y", function(d) {
        return y(d[1]);
    })
    .attr("height", function(d) {
        return y(d[0]) - y(d[1]);
    })
    .attr("width", x.bandwidth())
    .on('mouseover', function(d, i) {
        var thisName = d3.select(this.parentNode).datum().key;
        var thisValue = d.data[thisName];
        var total = d.data.foo + d.data.bar;
        tooltip.html("Name: " + thisName + "<br>Value: " + thisValue + "<br>Percentage: " + thisValue / total + "%");
    });

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

g.append("g")
    .attr("class", "axis axis--y")
    .call(d3.axisLeft(y).ticks(10, "%"));
<script src="https://d3js.org/d3.v4.min.js"></script>

<div id="tooltip">tooltip</div>
<svg width="500" height="300"></svg>

Upvotes: 8

Related Questions