Exploring ways to retrieve specific data from an array in a stacked bar graph tooltip using D3.js version 5

I am working with a JSON file below where I am summing up values in different fields and trying to display the total amount on a tooltip. Each 'rect' has a total field amount as well as group amounts displayed when hovered over.

    [
  {
    "group": "Field 1",
    "0-45": 12345,
    "46-65": 91568,
    "66-85": 13087,
    "85+": 7045
  },
  {
    "group": "Field 2",
    "0-45": 62345,
    "46-65": 15347,
    "66-85": 37688,
    "85+": 7007
  }
]

Sample code snippet...

let totalColVal = d3.nest()
  .key(function(d){return (d.group)})   
  .rollup(function(totals){
    return d3.sum(totals, function(d) {return d3.sum(d3.values(d))})
  }).entries(data)

  console.log(totalColVal)

This gives the following output:

(4) [{…}, {…}, {…}, {…}]
0: {key: "Field 1", value: 124045}
1: {key: "Field 2", value: 122387}
2: {key: "Field 3", value: 172041}
3: {key: "Field 4", value: 67594}

The above code calculates the total amount for each field, but I need assistance on displaying each value in the tooltip when hovering over each rect.

  //tooltip
  let mouseover = function(d) {
    let subgroupName = d3.select(this.parentNode).datum().key;
      // console.log(subgroupName)
    let subgroupValue = d.data[subgroupName];
      // console.log(subgroupValue)    
  
    tooltip.html('<b>Field:</b> <span style="color:yellow">' + d.data.group + '</span>' + '<br>\
      ' + '<b>Count:</b> <span style="color:yellow">' + formater(subgroupValue) + '</span>' + '<br>\
      ' + ' <b>Size Band:</b> <span style="color:yellow">' + subgroupName + '</span>' + '<br>\
      ' + '<b>Field Total:</b>  <span style="color:yellow">' + totalColVal + '</span>')
        .style("opacity", 1)
  };
  let mousemove = function(d) {
    tooltip.style('left', (d3.event.pageX - 70) + 'px')
      .style('top', (d3.event.pageY - 85) + 'px')
  };
  let mouseleave = function(d) {
    tooltip.style("opacity", 0)
  };

TotalColVal currently returns [object Object],[object Object],[object Object],[object Object] in my tooltip. Please provide assistance on this issue.

JS fiddle link here

Answer №1

When accessing the value from the ith bar, it's important to note that there is a second argument beside d available in all d3 functions. The i represents the index of the element within the array you provided.

By simply using i in the mouseover function, your issue was successfully resolved:

const canvas = d3.select('#stacked');

const stackedSvg = canvas.append('svg')
  .attr('preserveAspectRatio', 'xMinYMin meet')
  .attr('viewBox', '0 0 900 500')
  .classed('svg-content', true);

let margin = {top: 40, right: 30, bottom: 20, left: 70},
  width = 260 - margin.left - margin.right,
  height = 250 - margin.top - margin.bottom;

// append the svg object to the body of the page
let svg = stackedSvg.append('g')
  .attr('width', width)
  .attr('height', height)  
  .attr('transform', `translate(${margin.left}, ${margin.top})`);

//number formater   
const formater = d3.format(',d');

let myData = [
  {
    "group": "Field 1",
    "0-45": 12345,
    "46-65": 91568,
    "66-85": 13087,
    "85+": 7045
  },
  {
    "group": "Field 2",
    "0-45": 62345,
    "46-65": 15347,
    "66-85": 37688,
    "85+": 7007
  },
  {
    "group": "Field 3",
    "0-45": 11457,
    "46-65": 28456,
    "66-85": 124564,
    "85+": 7564
  },
  {
    "group": "Field 4",
    "0-45": 19234,
    "46-65": 26754,
    "66-85": 14153,
    "85+": 7453
  }
]

//tooltip
let tooltip = d3.select('body').append('div')
  .attr('class', 'tooltip')
  .style('opacity', 0);

// Parse the Data
Promise.resolve(myData)
  .then(data => {
    // console.log(data);

  //select the size bands
  let keys = d3.keys(data[0]).slice(1); 
    // console.log(keys);

  // List of groups in JSON. value of the first column called group
  let groups = d3.map(data, function(d){return(d.group)}).keys(); 
    // console.log(groups);

  // X axis
  let x = d3.scaleBand()
    .domain(groups)
    .range([0, width])
    .padding([0.2])
  svg.append('g')    
    .attr('transform', 'translate(0,' + height + ')')
    .call(d3.axisBottom(x)
    .tickSizeOuter(0));  
    
  svg.append('text')
    .attr('class', 'xLabel')
    .attr('text-anchor', 'end')
    .attr('x', width / 2 + 20)
    .attr('y', height + 30)                          
    .text('Size Band')
    .style('font-size', 10);    

  // Y axis
  let y = d3.scaleLinear()
    .domain([0, d3.max(data, d => d3.sum(keys, k => +d[k]))])
    .range([ height, 0 ]); 
    
  svg.append('g')
    .call(d3.axisLeft(y)
      .tickSizeOuter(0)
      .tickSizeInner(- width)
      .tickPadding(5))      
      .selectAll(".tick")
      .style("stroke-dasharray", ("1, 1")) //dash line across graph.
      .each(function (d, i) {
        if ( d == 0 ) {
            this.remove();
        }
    });
    
  svg.append('text')
    .attr('class', 'yLabel')
    .attr('transform', 'rotate(-90)')
    .attr('y', - 40)
    .attr('x', - height / 2 + 20)
    .attr('text-anchor', 'end')
    .text('Units')
    .style('font-size', 10);

  // color
  let color = d3.scaleOrdinal()
    .domain(keys)
    .range(['brown', 'steelblue', 'olivedrab', 'darkorange']);

  //stack the data --> stack per subgroup
  let stackedData = d3.stack()
    .keys(keys)
    (data);
    console.log(stackedData)

    let totalColVal = d3.nest()
      .key(function(d){return (d.group)})   
      .rollup(function(totals){
        return d3.sum(totals, function(d) {return d3.sum(d3.values(d))})
      }).entries(data)
    
      console.log(totalColVal)

  //tooltip
  let mouseover = function(d, i) {
    let subgroupName = d3.select(this.parentNode).datum().key;
      // console.log(subgroupName)
    let subgroupValue = d.data[subgroupName];
      // console.log(subgroupValue)    
  
    tooltip.html('<b>Field:</b> <span style="color:yellow">' + d.data.group + '</span>' + '<br>\
      ' + '<b>Count:</b> <span style="color:yellow">' + formater(subgroupValue) + '</span>' + '<br>\
      ' + ' <b>Size Band:</b> <span style="color:yellow">' + subgroupName + '</span>' + '<br>\
      ' + '<b>Field Total:</b>  <span style="color:yellow">' + totalColVal[i].value + '</span>')
        .style("opacity", 1)
  };
  let mousemove = function(d) {
    tooltip.style('left', (d3.event.pageX - 70) + 'px')
      .style('top', (d3.event.pageY - 85) + 'px')
  };
  let mouseleave = function(d) {
    tooltip.style("opacity", 0)
  };

  // Show the bars
  svg.append('g')
    .selectAll('g')
    // Enter in the stack data = loop key per key = group per group
    .data(stackedData)
    .enter().append('g')
      .attr('fill', function(d) { return color(d.key) })
      .selectAll('rect')

      // enter a second time = loop subgroup per subgroup to add all rectangles
      .data(function(d) { return d; })
      .enter().append('rect')  
        .on('mouseover', mouseover)
        .on('mousemove', mousemove)
        .on('mouseleave', mouseleave)
        .transition()
          .attr('y', d => y(d.height))
          .delay(function(d, i) {
            return i * 100
          })      
          .ease(d3.easeLinear)       
        .attr('x', function(d) { return x(d.data.group); })
        .attr('y', function(d) { return y(d[1]); })
        .attr('height', function(d) { return y(d[0]) - y(d[1]); })
        .attr('width',x.bandwidth())        
        .style('stroke', 'black')
        .style('opacity', 0.9);     
 
});
body {
  font-family: halyard-display, sans-serif;
/*   background-color: black; */
}

div.tooltip {
  position: absolute;
  text-align: left;
  width: fit-content;
  height: fit-content;
  padding: 5px 5px;   
  font-size: 16px;
  background:rgb(24, 23, 23);
  color: white;    
  border-radius: 5px;
  pointer-events: none;    
}

.yLabel {
  fill: #DEDC00;
  font-style: italic;
  font-weight: 600;
}

.xLabel {
  fill: #DEDC00;
  font-style: italic;
  font-weight: 600;
}

g .tick text {
  font-size: 8px;
  color: grey;
}

g .tick text:hover {
  font-size: 12px;
}

g .tick {
  color: #DEDC00;
}
<script src="https://d3js.org/d3.v5.js"></script>
<div id="stacked"></div>

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

Meteor: Adding DKIM signatures to your emails

When it comes to sending enrollment emails and password reset emails in Meteor.js, I make use of the default functionality provided: Accounts.sendEnrollmentEmail(...) Email.send(...) Meteor utilizes MailComposer from NodeMailer for email sending purpose ...

Issues rendering ThreeJS geometry data imported from Json file

Having been a dedicated user of this website for some time now, I must admit that this is the first instance where I find myself in need of posting a question. The issue at hand revolves around a Json file from which I extract data and manipulate it with m ...

Generating and Importing Graphics through Iterations in Javascript

I apologize for my lack of experience in this matter. Can someone please guide me on how to utilize a loop to load images? For instance, could you show me how to rewrite the code below using a loop to automate the process? function loadImages() { let ...

The for loop encountered an uncaught type error when trying to access the .length property

Currently, I am working on a school project where the task is to create a basic social network posting application using Django and JavaScript. The purpose of using JavaScript is to dynamically load posts onto the webpage and update HTML components. I have ...

What guidelines should be followed when creating a custom array in programming?

When dealing with a user defined array, what is the best practice in terms of size and element values? Option A: Start by predefining an array of a certain size (for example 100), then prompt the user to input how many elements they would like to store in ...

I need these buttons to determine which div is displayed or appears "on top"

I am facing a challenge with the buttons on one side of my webpage and a main content area occupying most of the page. My goal is to make clicking a button change the main content to a div that contains relevant information. However, I have struggled to f ...

I am having trouble understanding the UnrecognizedPropertyException

I have a JSON object with both simple and complex data types that I am trying to parse using a struct. Here is my struct: public static class PayloadData { String authkey; PizzaPlaceTo pizzaPlace; Map<String, List<String>> updates; ...

What is the best way to add attribution text to background images in reveal.js?

I am considering using reveal.js for a series of lectures. I would like to include attribution text in a subtle location (bottom right) for any background images on slides. These images are specified using <section background-data="url(...)"> withi ...

Iterate through the list retrieved from the backend

I have a list coming from the backend that I need to iterate through and hide a button if any element in the list does not have a status of 6. feedback The response returned can vary in length, not always just one item like this example with 7 elements. ...

axio. to retrieve the information stored in an alternate document

Seeking assistance in utilizing API data in a separate file. I have three files: api.jsx import axios from 'axios'; export const api = (url, data) => { const { path, method } = url; let result ={}; axios({ method: ...

Efficiently input text box values into a canvas in real-time using HTML canvas and keypress

There are three text boxes labeled textbox1, textbox2, and textbox3. I am looking to transfer the values entered into these text boxes directly onto a canvas (cnv) whenever a user types in them, and remove the value from the canvas when it is deleted fro ...

When implementing tooltips in Apexchart's JavaScript, the y-axis second does not appear in the chart

Typically, when I append data to the second y-axis as an array, it works perfectly. However, for some reason, when I try to append a collection to the data, it no longer shows with two y-axes simultaneously. I even tried adding x and y as the Apex documen ...

What is the best way to handle newline characters ( ) when retrieving text files using AJAX?

When using an AJAX call to read a text file, I encountered an issue where it reads the \n\t and backslash symbols. These characters are not needed in the pure text message. How can I ignore or remove them for a clean text display? ...

pagination of data tables on the server side

I am looking to incorporate server-side pagination with data tables for my application. I have approximately 200 records and I now understand the concept of server-side pagination where we send individual AJAX requests for each page link. So, if I want to ...

Creating a GWT-compatible solution for implementing the Google Visualization - Annotation Chart

I am currently using the newly launched Annotation Chart in GWT by integrating native JavaScript. I have managed to display the example chart successfully, but unfortunately, it lacks interactivity and behaves more like an image. Can anyone provide guidanc ...

The looping functionality in Swiperjs is not working properly for sliders

Encountering some issues with the loop setting for swiper. The problem is that it stops sliding before entering a second loop, always halting at a certain point. I have never worked with swiper before, so I am unsure if this is the intended behavior or not ...

What could be causing Firebase serve to malfunction while Firebase deploy runs smoothly?

Every time I run firebase deploy, it successfully creates data. But when I run firebase serve, it always ends up in the catch function! This is the function I'm working with: exports.createUser = functions.https.onRequest((req, res) => { const ...

Tips on how to import the unfamiliar data from a text file into a 2D array

I need help figuring out how to read the unknown contents of a text file into a 2D array and have it formatted like this: M [0] [0]=2 M [0] [1]=1 M [0] [2]=0 M [1] [0]=0 M [1] [1]=1 M [1] [2]=3 M [2] [0]=8 M [2] [1]=9 M [2] [2]=1 M [3] [0]=3 M [3] [1]=5 M ...

Update the Slit Slider's effect to seamlessly fade in and out

I'm currently working on a website that requires a full-screen slider with fade-in and fade-out effects, complete with content and next/previous navigation options. After some research, I stumbled upon this solution. However, my main issue is figurin ...

Tips for expanding an array within Numpy without creating a new array?

Right now, the code I have looks like this import numpy as np ret = np.array([]) for i in range(100000): tmp = get_input(i) ret = np.append(ret, np.zeros(len(tmp))) ret = np.append(ret, np.ones(fixed_length)) This code is not very efficient becaus ...