kunal
kunal

Reputation: 163

Use legend in stacked bar Graph along with tooltip d3 js

I have done stacked bar graph with the help of this link https://bl.ocks.org/mbostock/1134768 but i want to customize my existing code of stacked graph in which i have to add d3.legend along with tooltip.

Stacked Graph

Right now i have completed till tooltip but there is label problem because i have to give value like this 'A:100' from my array data as shown below

 var data = [
        {month: "4/1854",total:"100" ,A: "45", B:"45", C:"10"},
        {month: "5/1854",total:"200" ,A:"80", B:"70", C:"50"},
        {month: "6/1854",total:"300" ,A:"0", B:"100", C:"200"},
        {month: "7/1854",total:"400" ,A: "200", B:"100", C:"100"},
        {month: "8/1854",total:"500" ,A:"100", B:"200", C:"200"},
        {month: "9/1854",total:"600" ,A:"100", B:"200", C:"300"},
        {month: "10/1854",total:"700" ,A: "400", B:"100", C:"200"},
        {month: "11/1854",total:"800" ,A:"500", B:"200", C:"100"},
        {month: "12/1854",total:"900" ,A:"100", B:"400", C:"500"},
        {month: "13/1854",total:"1000" ,A:"500", B:"0", C:"500"}
    ];

And about d3.legend i don't find any suitable example that give me proper picture that how to use it in my existing code but i have to show like this

legend example

Here is my full working code

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

  var parseDate = d3.time.format("%m/%Y").parse;

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

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

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

var color = d3.scale.category20();
//console.info(color(0));

var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .tickFormat(d3.time.format("%b"));

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

var svg = d3.select("#pie").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 + ")");

data.forEach(function(d) {
      d.month = parseDate(d.month);
      xData.forEach(function(c) {
        d[c] = +d[c];
      });

    })

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(data, function(d) { return d.total; })
    ])
  .nice();

var layer = svg.selectAll(".stack")
        .data(dataStackLayout)
        .enter().append("g")
        .attr("class", "stack")
        .style("fill", function (d, i) {
            console.info(i, color(i));
            return color(i);
        });

layer.selectAll("rect")
        .data(function (d) {
            return d;
        })
        .enter().append("rect")
        .attr("x", function (d) {
            console.info("dx", d.x,x(d.x), x.rangeBand());
            return x(d.x);
        })
        .attr("y", function (d) {
            return y(d.y + d.y0);
        })
        .attr("height", function (d) {
            // console.info(d.y0, d.y, y(d.y0), y(d.y))
            return y(d.y0) - y(d.y + d.y0);
        })
        .attr("width", x.rangeBand() -1)


        .on("mouseover", function(d){

          var delta = d.y1 - d.y0;
          var xPos = parseFloat(d3.select(this).attr("x"));
          var yPos = parseFloat(d3.select(this).attr("y"));
          var height = parseFloat(d3.select(this).attr("height"))

          d3.select(this).attr("stroke","blue").attr("stroke-width",0.8);

          svg.append("text")
          .attr("x",xPos)
          .attr("y",yPos +height/2)
          .attr("class","tooltip")
          .text(d +": "+ delta); 

       })
       .on("mouseout",function(){
          svg.select(".tooltip").remove();
          d3.select(this).attr("stroke","pink").attr("stroke-width",0.2);

        });


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

  svg.append("g")
      .attr("class", "axis axis--y")
      .call(yAxis)
       .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end");

Please provide me some useful suggestion about my graph any help should be appreciated.

Upvotes: 1

Views: 2103

Answers (1)

Mikhail Shabrikov
Mikhail Shabrikov

Reputation: 8509

Let's add a legend and a tooltip for blocks what you mentioned (I cannot use your code because of it is incomplete). Look at the demo in the hidden snippet below (I rewrite data loading to d3.tsv.parse to simplify the example).

First of all, let's increase bottom margin to the legend fits to svg:

var margin = {top: 20, right: 50, bottom: 130, left: 20};

Adds the legend this way (pay attention to the comments):

var legend = svg.append("g") // add g element it will be the container for our legend
    .attr("transform", "translate(0," + (height + 25) + ")") // move it under the bar cart
    .selectAll(".legend")
    .data(causes.reverse()) // bind data
    .enter().append("g")
    .attr("class", "legend")
    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); // put legend items one above the other

legend.append("rect") // append legend items rect
    .attr("x", width - 18)
    .attr("width", 18)
    .attr("height", 18)
    .style("fill", function(d, i) { return z(causes.length - 1 - i);}); // set appropriate color with z scale

legend.append("text") // append legend items text
    .attr("x", width - 24)
    .attr("y", 9)
    .attr("dy", ".35em")
    .style("text-anchor", "end")
    .text(function(d) { return d; }); // set appropriate text

The fastest way to add tooltip use d3-tip library. Add it to your project with npm or script tag.

Define tooltip this way:

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(data, cause) {
    return "<div class=\"tooltip\">" + cause + ":" + data.y + "</div>";
  })

svg.call(tip);

Attach appropriate event handler functions for mouseover and mouseout event for rect elements:

layer.selectAll("rect")
  .data(function(d) { return d; })
  .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() - 1)
  .on('mouseover', function(d,i,j) { tip.show(d, causes[causes.length - 1 - j]); })
  .on('mouseout', tip.hide); 

Note: it works for d3v3 if you use d3v4 you will be forced to rewrite mouseover handler function a bit.

var causes = ["wounds", "other", "disease"];

var parseDate = d3.time.format("%m/%Y").parse;

var margin = {top: 20, right: 50, bottom: 130, left: 20},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

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

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

var z = d3.scale.category10();

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickFormat(d3.time.format("%b"));

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

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 + ")");


var dataAsString = `date	total	disease	wounds	other
4/1854	8571	1	0	5
5/1854	23333	12	0	9
6/1854	28333	11	0	6
7/1854	28772	359	0	23
8/1854	30246	828	1	30
9/1854	30290	788	81	70
10/1854	30643	503	132	128
11/1854	29736	844	287	106
12/1854	32779	1725	114	131
1/1855	32393	2761	83	324
2/1855	30919	2120	42	361
3/1855	30107	1205	32	172
4/1855	32252	477	48	57
5/1855	35473	508	49	37
6/1855	38863	802	209	31
7/1855	42647	382	134	33
8/1855	44614	483	164	25
9/1855	47751	189	276	20
10/1855	46852	128	53	18
11/1855	37853	178	33	32
12/1855	43217	91	18	28
1/1856	44212	42	2	48
2/1856	43485	24	0	19
3/1856	46140	15	0	35`;

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(data, cause) {
    return "<div class=\"tooltip\">" + cause + ":" + data.y + "</div>";
  })

svg.call(tip);

var crimea = d3.tsv.parse(dataAsString, function(item) {
	return {
  	date: parseDate(item.date),
    total: +item.total,
    disease: +item.disease,
    wounds: +item.wounds,
    other: +item.other
  };
});

var layers = d3.layout.stack()(causes.map(function(c) {
  return crimea.map(function(d) {
    return {x: d.date, y: d[c]};
  });
}));

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

var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return z(i); });

layer.selectAll("rect")
  .data(function(d) { return d; })
  .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() - 1)
  .on('mouseover', function(d,i,j) { tip.show(d, causes[causes.length - 1 - j]); })
  .on('mouseout', tip.hide);

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

svg.append("g")
  .attr("class", "axis axis--y")
  .attr("transform", "translate(" + width + ",0)")
  .call(yAxis);

var legend = svg.append("g")
		.attr("transform", "translate(0," + (height + 25) + ")")
		.selectAll(".legend")
    .data(causes.reverse())
    .enter().append("g")
    .attr("class", "legend")
    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

legend.append("rect")
    .attr("x", width - 18)
    .attr("width", 18)
    .attr("height", 18)
    .style("fill", function(d, i) { return z(causes.length - 1 - i);});

legend.append("text")
    .attr("x", width - 24)
    .attr("y", 9)
    .attr("dy", ".35em")
    .style("text-anchor", "end")
    .text(function(d) { return d; });

function type(d) {
  d.date = parseDate(d.date);
  causes.forEach(function(c) { d[c] = +d[c]; });
  return d;
}
.axis text {
  font: 10px sans-serif;
}

.legend {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 60%;
}

.axis line,
.axis path {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.axis--x path {
  display: none;
}

.tooltip {
  background-color: lightblue;
  border-radius: 5px;
  padding: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.js"></script>

Upvotes: 1

Related Questions