D3.js Stacked Bar Chart with Value Labels displayed above each bar

Having trouble positioning the total value for each bar above a bar in my bar chart. It seems to stay static no matter what I try. Any help would be greatly appreciated! Code snippet has been shortened for brevity.

Current look:

https://i.sstatic.net/N5ZIm.png

var x = d3.scaleBand()
    .rangeRound([0, width])
    .paddingInner(0.2);

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

var color = d3.scaleOrdinal()
    .range(d3.schemeGreys[7]);

var xAxis = d3.axisBottom(x);

var yAxis = d3.axisLeft(y)
    .tickFormat(d3.format(".2s"));

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 update = function(data) {

  color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Categories"; }));

  data.forEach(function(d) {
    var y0 = 0;
    d.stores = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
    d.total = d.stores[d.stores.length - 1].y1;
  });

  data.sort(function(a, b) { return b.total - a.total; });

  x.domain(data.map(function(d) { return d.Categories; }));
  y.domain([0, d3.max(data, function(d) { return d.total; })]);

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

  var categories = svg.selectAll(".categories")
      .data(data)
    .enter().append("g")
      .attr("class", "g")
      .attr("transform", function(d) { return "translate(" + x(d.Categories) + ",0)"; });

    // more code...

    

Answer №1

To determine the position of the total, utilize your existing y scale much like you have for the top and bottom edges of the rectangles and text on each side. Instead of using d.y0 or d.y1, employ d.total:

categories.append("text")
  .attr("text-anchor", "middle")
  .attr("x", x.bandwidth()/2)
  .attr("y", function(d) { return y(d.total); }) // d.total!
  .style("fill", "black")
  .text(function(d) { return d.total; });

This will position the text directly over the top rectangle, so consider adding a slight offset to it:

categories.append("text")
  .attr("text-anchor", "middle")
  .attr("x", x.bandwidth()/2)
  .attr("y", function(d) { return y(d.total); })
  .attr('dy', '-0.5em') // add 0.5em offset
  .style("fill", "black")
  .text(function(d) { return d.total; });

For reference, here is a functioning example:

var Data = [
    {
        "Categories": "Lissabon",
        "1": "34",
        "2": "76",
        "3": "87",
        "4": "54",
        "5": "66",
        "6": "72"
    },
    { Categories: "This",
      1: 239, 2: 254, 3: 225, 4: 152, 5: 362, 6: 98
    },
    { Categories: "That",
      1: 457, 2: 234, 3: 83, 4: 327, 5: 88, 6: 99
    },
    { Categories: "The Other",
      1: 132, 2: 286, 3: 222, 4: 150, 5: 363, 6: 95
    }
    ]
var height = 600,
width = 600,
margin = { left: 10, right: 10, top: 20, bottom: 20 }

var x = d3.scaleBand()
    .rangeRound([0, width])
    .paddingInner(0.2);

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

var color = d3.scaleOrdinal()
    .range(d3.schemeGreys[7]);

var xAxis = d3.axisBottom(x);

var yAxis = d3.axisLeft(y)
    .tickFormat(d3.format(".2s"));

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 update = function(data) {

  color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Categories"; }));

  data.forEach(function(d) {
    var y0 = 0;
    d.stores = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
    d.total = d.stores[d.stores.length - 1].y1;
  });

  data.sort(function(a, b) { return b.total - a.total; });

  x.domain(data.map(function(d) { return d.Categories; }));
  y.domain([0, d3.max(data, function(d) { return d.total; })]);

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

  var categories = svg.selectAll(".categories")
      .data(data)
    .enter().append("g")
      .attr("class", "g")
      .attr("transform", function(d) { return "translate(" + x(d.Categories) + ",0)"; });

    categories.selectAll("rect")
      .data(function(d) { return d.stores; })
    .enter().append("rect")
      .attr("width", x.bandwidth())
      .attr("y", function(d) { return y(d.y1); })
      .attr("height", function(d) { return y(d.y0) - y(d.y1); })
      .style("fill", function(d) { return color(d.name); });

    categories.selectAll("text")
      .data(function(d) { return d.stores; })
    .enter().append("text")
      .attr("y", function(d) { return y((d.y0 + d.y1) / 2); })
      .attr("x", x.bandwidth()/2)
      .attr('dy', '0.35em')
      .attr("text-anchor","middle")
      .attr("alighnment-baseline", "middle")
      .style("fill",'#fff')
      .text(function(d) { return d.y1 - d.y0; });

    categories.append("text")
      .attr("text-anchor", "middle")
      .attr("x", x.bandwidth()/2)
      .attr("y", function(d) { return y(d.total); })
      .attr('dy', '-0.5em')
      .style("fill", "black")
      .text(function(d) { return d.total; });
}

update(Data)
<script type="text/javascript" src="http://d3js.org/d3.v5.js"></script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

utilizing both javascript and php to showcase a dynamic calendar

After diving into PHP development, I've been working on a code that should display a calendar when clicking "launch calendar" and show the selected date in a text field. However, I encountered an issue where February is not displaying when launching t ...

Error occurred during the Uglify process: Unable to access the 'kind' property as it is undefined

I developed a project using TypeScript (version 3.9.3) and Node (version 10.16.3), but now I want to minify the code by converting it to JavaScript and running UglifyJS. However, after going through this process, the services that were functioning properly ...

The declaration file for the module 'ga-gtag' was not found

I am currently in the process of updating my project from Universal Analytics to GA4, and I have encountered some challenges along the way. My main obstacle at the moment is installing the ga-gtag module so that I can send events using the new formatting f ...

Redis method LRANGE is returning a list in a way that doesn't meet my requirements

I have a significant amount of data stored in redis which I am caching. When I need to retrieve this data, I use the lrange function to fetch it. The issue is that when the data is returned, it is in a format like this: https://i.sstatic.net/r1oa8.png Ho ...

After adding on, do you wish to eliminate?

I have implemented zoom buttons on my Map container However, I need to hide these zoom buttons when a specific map image is displayed Code: JS: Map = function() { //private: var $MapContainer; this.initMap = function($container, zoomHidden) { $Map ...

In VueJS, v-slot is consistently null and void

Here is the issue at hand. Main view: <template> <div class="main"> <Grid :rows=3 :cols=4> <GridItem :x=1 :y=1 /> <GridItem :x=2 :y=1 /> </Grid> </div> </template> <scrip ...

d3 file is functional on Firefox but experiencing issues on Chrome

Currently, I'm part of a team project and have run into an issue where the d3 map is rendering correctly on everyone else's computer except mine. Below is a snippet of my map code: var g = svg.append("g"); // The function(error, us) callback wo ...

What is the best way to retrieve the updated value following a mutation in Vuex?

In my Vuex module, I have a simple setup for managing a global date object: import moment from 'moment'; const state = { date: moment() } // getters const getters = { date: state => state.date, daysInMonth: state => state.dat ...

Button click triggers popup

I have a basic HTML page with a button. I recently signed up for Mailchimp's newsletter and forms widget, and they provided me with a code to add to my site. The code currently triggers a pop-up when the page loads, but I would like it to only activat ...

Incorporate an Angular app into an existing application and seamlessly transfer the data

I am currently in the process of integrating an Angular app into an existing jQuery application. Let's say I have a basic button in my HTML code. My goal is to load the Angular app and execute controller code when the button is clicked. To achieve t ...

Having issues with the functionality of jQuery. It needs to be able to override existing

I am trying to make it so that if yes2 == none, the element with class .bottomandroid is removed and instead, the element with class .cta is displayed. However, it doesn't seem to be working as expected. How can I fix this issue? else if (isAndroid) ...

Looking for guidance on setting up an auto-updating counter with AJAX? Or perhaps you're curious about how to turn off the network

Can I create an auto-reload counter for messages using setTimeout(); to fetch JSON code from test_ajax.php? Is it possible to send information from the server in this case? I'm noticing a large number of requests being made to test_ajax.php when I c ...

Protractor Error: Identifier Unexpectedly Not Found

I've encountered a slight issue with importing and exporting files while working on Protractor tests. HomePage.js export default class HomePage { constructor() { this.path = 'http://automationpractice.com/index.php'; this.searchQ ...

Different methods to disable scrolling when the mobile menu pops up

I have implemented a mobile menu option that appears when you click on the triple line logo, but unfortunately, users can still scroll while the menu is open. I've tried using position:fixed; but haven't been successful in preventing scrolling be ...

How to prevent page refresh when hitting enter in jQuery form?

Currently, my form does not refresh the page when I click the button to submit it. However, if I press Enter while a text input is selected, the page will refresh. Is there a way to make pressing Enter on the text input perform the same action as clicking ...

How can you leverage both sockets and express middleware at the same time?

Is there a way to access the socket of a request within an express middleware function? For example: import express from 'express'; import io from 'socket.io'; const app = express(); // Integrate app and io somehow ... // When a cl ...

What is the best way to ensure that the function is referencing the class appropriately?

Typically when using this, it points to the class being referenced. However, in this scenario, this is set to dataChannel. How can I make this point back to VideoService? Thank you. export class VideoService { dataChannel:any; setupPeerConnectio ...

Applying a class to a single div element

I am struggling with adding a class to a specific div only if it contains an image. <div class="large-6 columns check-Div"> <div class="custom-table"> <div class="text"> <?php echo $latestimage; ?> </div> ...

Using Vue and Vuex to wait for asynchronous dispatch in the created hook

I'm struggling to implement asynchronous functionality for a function that retrieves data from Firebase: Upon component creation, I currently have the following: created(){ this.$store.dispatch("fetchSections"); } The Vuex action looks ...

Tips on customizing specific sections of a template string

In my Vue.js method, I am using a template literal: methodNameDisplay(m) { let nameToDisplay = ''; if (m.friendlyName === m.methodName) { nameToDisplay = m.friendlyName; } else { nameToDisplay = `${m.friendlyName} - ${m.methodName}` ...