Home > Back-end >  Text on top of d3 vertical stacked bar chart
Text on top of d3 vertical stacked bar chart

Time:09-30

I'm trying to build a combo chart, i.e. vertical stack bar and a line chart together. I have built the graph but i want the value of each bar on top of the bar. I found certain code for text on top of single bar but not a clear answer for stacked bar. I have written down some code which is available below and I have commented it as // code i tried for text on top of each stack//. But that doesnt seem to work.

enter image description here

 d3GroupBarChart(datas){
  this.showData = datas
  let textArray = [];
  datas.forEach(element => {
  element.stack.forEach(stack => {
    textArray.push(stack)
   });
  });

 if (datas === null || datas.length == 0) {
   $(".sieir-chart").empty()
   $('.sieir-chart').append(`<div class="no-card-data" >
     <h5>No Data Available </h5>
     </div>`)
   return
 }
 $('.sieir-chart').html('')

 var margin = { top: 20, right: 80, bottom: 100, left: 80 },
  width = $('.group-bar-chart').width() - margin.left - margin.right,
  height = 410 - margin.top - margin.bottom;

 var svg: any = d3.select(".sieir-chart")
  .append("svg")
  .attr("viewBox", `0 0 ${$('.group-bar-chart').width()} 410`)
  .attr("preserveAspectRatio", "xMinYMin meet")

 var g = svg.append("g")
  .attr("height", height)
  .attr("transform",
    "translate("   (margin.left)   ","   (20)   ")");

 var x: any = d3.scaleBand()
  .range([0, width])
  .domain(datas.map(function (d) { return d.group; }))
  .padding(0.2);

 var yMax = Math.max.apply(Math, datas.map(function (o) { return o.maxBarValue; }))
 // Add Y axis
 var y = d3.scaleLinear()
  .domain([0, yMax])
  .range([height, 0])
  .nice();


var self = this;
var formatyAxis = d3.format('.0f');
g.append("g")
  .style('font-weight', 'bold')
  .call(d3.axisLeft(y).tickFormat(function (d: any) {
    if (d % 1 === 0) {
      return d.toLocaleString()
    }
    else {
      return ''
    }
  }).ticks(5));

var y1Max = Math.max.apply(Math, datas.map(function (o) { return o.percentage; }))
var y1: any = d3.scaleLinear().range([height, 0]).domain([0, y1Max]);

var yAxisRight: any = d3.axisRight(y1).ticks(5)
//   //this will make the y axis to the right
g.append("g")
  .attr("class", "y axis")
  .attr("transform", "translate("   (width)   " ,0)")
  .style('font-weight', 'bold')
  .call(yAxisRight);


//   // text label for the y axis
svg.append("text")
  .attr("transform", "rotate(-90)")
  .attr("y", 0 - (margin.left - 100))
  .attr("x", 0 - (height / 2))
  .attr("dy", "1em")
  .style("text-anchor", "middle")
  .style("font-family", "poppins_regular")
  .text("Logged User Count");

// text label for the y1 axis
svg.append("text")
  .attr("transform", "rotate(-90)")
  .attr("y1", 0 - (margin.right - 50))
  .attr("x", 0 - (height / 2))
  .attr("dy", width   130)
  .style("text-anchor", "middle")
  .style("font-family", "poppins_regular")
  .text("Duration in min");


g.append("g")
  .attr("transform", "translate(0,"   height   ")")
  .call(d3.axisBottom(x))
  .selectAll(".tick text")
  .attr("transform", "translate(-5,7)rotate(-15)")
  .style("text-anchor", "middle")
  .style("font-size", "11px")
  .style('font-weight', 'bold')
  .call(this.wrap, x.bandwidth())

var subgroups = ["Total Headcount","Onboarded resource count"];
var groups = d3.map(datas, function (d) { return (d['group']) }).keys();
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
  .domain(subgroups)
  .range([0, x.bandwidth()])
  .padding(0.05)

// color palette = one color per subgroup
var color = d3.scaleOrdinal()
  .domain(subgroups)
  .range(['#006287', '#F68721'])

var self = this;
datas.forEach(data => {
  // console.log("data",data);
  g.selectAll("mybar")
    // Enter in data = loop group per group
    .data(datas)
    .enter()
    .append("g")
    .attr("class","bars")
    .attr("transform", function (d) { return "translate("   x(d.group)   ",0)"; })
    .selectAll("rect")
    .data(function (d) { return subgroups.map(function (key) { return { key: key, 
     value: d[key] }; }); })
    .enter().append("rect")
    .attr("x", function (d) { return xSubgroup(d.key); })
    .attr("y", function (d) { return y(d.value); })
    .attr("width", xSubgroup.bandwidth())
    .attr("height", function (d) { return height - y(d.value); })
    .attr("fill", function (d) { return color(d.key); })
    .append("svg:title")
    .text(function (d) {
      return `${d['key']}:`   d.value;
    })
  
  //code i tried for text on top of each stack
  g.selectAll(".text")
    .data(data.stack)
    .enter().append("text")
    .attr("class", "barstext")
    .attr("x", function (d) { console.log("d", d); return x(d.name); })
    .attr("y", function (d) { return y(d.value); })
    .text(function (d) { console.log("text", d); return (d.value); })

  

  //   // line chart
  var averageline = d3.line()
    .x(function (d, i) { return x(d['group'])   x.bandwidth() / 2; })
    .y(function (d) { return y1(d['percentage']); })
    .curve(d3.curveMonotoneX);



  var path = g.append("path")
    .attr("class", "line")
    .style("fill", "none")
    .style("stroke", "#58D68D")
    .style("stroke-width", 2)
    .attr("d", averageline(datas));

  g.selectAll("myCircles")
    .data(datas)
    .enter()
    .append("circle")
    .attr("class", "dot")
    .style("fill", "white")
    .style("stroke", "#58D68D")
    .style("stroke-width", 2)
    .style('cursor', 'pointer')
    .attr("cx", function (d, i) { return x(d['group'])   x.bandwidth() / 2; })
    .attr("cy", function (d) { return y1(d['percentage']); })
    .attr("r", 3)
    .append("svg:title")
    .text(function (d) {
      return "Percentage: "   d.percentage;
    })
})

}

dummy data

 [
  {
   "group": "Digital Process Industries",
   "Total Headcount": 12,
   "Onboarded resource count": 1,
   "percentage": 13,
   "maxBarValue": 12,
   "stack": [
   {
    "name": "Total Headcount",
    "value": 12
   },
   {
    "name": "Onboarded resource count",
    "value": 1
   }
  ]
},
{
"group": "Digital Discrete Industries",
"Total Headcount": 6,
"Onboarded resource count": 6,
"percentage": 33,
"maxBarValue": 6,
"stack": [
  {
    "name": "Total Headcount",
    "value": 6
  },
  {
    "name": "Onboarded resource count",
    "value": 6
  }
 ]
}]

CodePudding user response:

You are pretty close with your current solution. There are two main things you need to do to get this working correctly:

  1. If you are looping over your data already (datas.forEeach) there is no need to rebind to it in your databinding for the group offset. You should be binding to the individual data element instead (so bind to [data] instead).
  2. Set the group you create off the data element to a variable and append both the rectangles for the bars and the text for the labels to that group rather than the svg. The reason for this is that it is already offset for the group (via the transform call) so you just have to worry about the subgroup x scale.

See this jsfiddle for a working version of your code. I added comments prepended with EDITED -- to all the lines I changed along with an explanation of what I did.

  • Related