Angular and D3.js: Enhancing StackedBar Chart ToolTips with Added Functionality

I have modified the code from this example to be compatible with the latest versions of Angular and D3.

Although it works well after a small tweak, I am unable to see the tooltips when hovering over the chart...

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

<h3>Stacked Bar Chart with Tooltips</h3>

<figure id="stacked-bar-tooltip"></figure>

If you inspect the DOM using this ID, you'll notice that it is crucial for rendering:

import { Component, OnInit } from '@angular/core';
import * as d3 from 'd3';

@Component({
  selector: 'app-stacked-bar-tooltip',
  templateUrl: './stacked-bar-tooltip.component.html',
  styleUrls: ['./stacked-bar-tooltip.component.css']
})
export class StackedBarTooltipComponent implements OnInit {

  data = [
    {"group": "Insulin", "Low": "6", "Normal": "2.75", "High": "5.25"},
    {"group": "Vit B12", "Low": "1", "Normal": "3", "High": "1"},
    {"group": "Vit D3", "Low": "1", "Normal": "3", "High": "1"},
    {"group": "Zinc", "Low": "1", "Normal": "3", "High": "1"}
  ];

  svg: any;

  margin = 50;
  width = 750 - (this.margin * 2);
  height = 400 - (this.margin * 2);

  ngOnInit(): void {
    this.createSvg();
    this.drawBars(this.data);
  }

  createSvg(): void {
    this.svg = d3.select("figure#stacked-bar-tooltip")
    .append("svg")
    .attr("width", this.width + (this.margin * 2))
    .attr("height", this.height + (this.margin * 2))
    .append("g")
    .attr("transform", "translate(" + this.margin + "," + this.margin + ")");
  }

  drawBars(data): void {
    
    // List of subgroups; i.e. the header of the csv data:
    // Prepare the array with the keys for stacking.
    const dataColumns = Object.keys(data[0]);
    const subgroups = dataColumns.slice(1)

    // List of groups; i.e. value of the first
    // column - group - shown on the X axis.
    const groups = data.map(d => d.group);

    // Create the X-axis band scale.
    const x = d3.scaleBand()
    .domain(groups)
    .range([0, this.width])
    .padding(0.2);

    // Draw the X-axis on the DOM.
    this.svg.append("g")
    .attr("transform", "translate(0," + this.height + ")")
    .call(d3.axisBottom(x).tickSizeOuter(0));
    
    // Create the Y-axis band scale.
    const y = d3.scaleLinear()
    .domain([0, 14])
    .range([this.height, 0]);

    // Draw the Y-axis on the DOM.
    this.svg.append("g")
    .call(d3.axisLeft(y));

    // color palette = one color per subgroup
    const color = d3.scaleOrdinal()
    .domain(subgroups)
    .range(['#ffffcc','#4daf4a', '#e41a1c']);

    // Stack the data per subgroup.
    const stackedData = d3.stack()
    .keys(subgroups)
    (data);

    // Create a tooltip.
    const tooltip = d3.select("#stacked-bar-tooltip")
    .append("figure")
    .style("opacity", 0)
    .attr("class", "tooltip")
    .style("background-color", "white")
    .style("border", "solid")
    .style("border-width", "1px")
    .style("border-radius", "5px")
    .style("padding", "10px")

    // Mouse function that change the tooltip when the user hovers/moves/leaves a cell.
    const mouseover = function(event, d) {
      /********** Hack! Otherwise, the following line would not work:
      const subgroupName = d3.select(this.parentNode).datum().key; */
      const subgroupNameObj: any = d3.select(this.parentNode).datum();
      const subgroupName = subgroupNameObj.key;
      /************ End of Hack! ************/
      const subgroupValue = d.data[subgroupName];
      tooltip.html("subgroup: " + subgroupName + "<br>" + "Value: " + subgroupValue)
            .style("opacity", 1)        
    }
    const mousemove = function(event, d) {
      tooltip.style("transform", "translateY(-55%)")  
            .style("left", (event.x)/2+"px")
            .style("top", (event.y)/2-30+"px")
    }
    const mouseleave = function(event, d) {
      tooltip.style("opacity", 0)
    }

    // Create and fill the stacked bars.
    this.svg.append("g")
    .selectAll("g")
    .data(stackedData)
    .join("g")
    .attr("fill", d => color(d.key))
    .selectAll("rect")    
    .data(d => d)
    .join("rect")
    .attr("x", d => x(d.data.group))
    .attr("y", d => y(d[1]))
    .attr("height", d => y(d[0]) - y(d[1]))
    .attr("width", x.bandwidth())
    .attr("stroke", "grey")
    .on("mouseover", mouseover)
    .on("mousemove", mousemove)
    .on("mouseleave", mouseleave);
  }
}

I tested the project in SandBox (link: here) and surprisingly, it works perfectly there, but not in my Angular setup...

Moreover, upon inspecting the figure, the tooltip functionality seems to be operational:

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

What could be causing the issue where the tooltips are not displaying properly?

Answer №1

After some investigation, I discovered that the functionality was actually working properly all along. However, due to other components within the application, the position appeared to be way off, displaying at the top of the page where it was not visible to me.

To rectify this issue and ensure that the tooltip appears above the mouse pointer, I made adjustments to the x and y values in the following manner:

const mousemove = function (event, d) {
      tooltip.style("transform", "translateY(-55%)")
        .style("left", (event.x - 50) + "px")
        .style("top", (event.y + 450) + "px");
}

I am still seeking a solution for positioning: How can I ensure that this element consistently displays just above the mouse pointer, through modifying the current code?

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

Synchronize JSON data with the Document Object Model (DOM

My current project is built using React, where I am rendering the page dynamically based on JSON data. The page consists of various component types, ranging from images to text content. Each component includes a delete option, allowing users to change im ...

nextAuth.js is failing to access the accessToken, returning only the values {iat, exp, jti} instead

Here is the code I am working with: import NextAuth from "next-auth" import CredentialsProvider from "next-auth/providers/credentials" export default NextAuth({ sectret:process.env.NEXTAUTH_SECRET, session: { strategy: "jw ...

Calling `$httpBackend.verifyNoOutstandingRequest()` will not result in any errors being raised

During my test setup, I create an HTTP request but do not execute $httpBackend.flush(). Here is the test scenario: describe("Unit: Testing Services", function() { beforeEach(angular.mock.module('EmsWeb.Services')); describe("Unit: DalSer ...

When attempting to call a Firebase Cloud Function URL in an AngularJS $http request, an Access Control Origin Error

I recently created a cloud function that involves linking with Plaid. I'm currently working on calling this function using AngularJS's $http method. While the cloud function code is being executed, I encountered an error in my console instead of ...

Can Angular be used to dynamically filter a JSON object to display only the fields that match a specified filter text?

Sorry if this question has already been asked; I couldn't find the solution. Here is my issue: In my Angular app, I am retrieving a complex JSON object from a web service. I then present this JSON object to the user in tree format using ngx json vie ...

Load Ajax file smoothly without any flickering

I am working on a webpage that automatically loads data. The script I found on a tutorial website has a flaw - the text keeps blinking every few seconds. Is there a way to prevent this blinking effect? I am new to Ajax and finding it confusing. Below is t ...

Looking for a specific value in a switch case code written in JavaScript

Below is the code snippet found in my alert box or any variable. I need to update the values in product to different values such as 'Tremfya', 'Remicade', dynamically. These values are required for a specific product like "linename" and ...

What are the steps to fixing the TS2722 error that says you cannot call a possibly 'undefined' object?

Here is a snippet of code from my project: let bcFunc; if(props.onChange){ bcFunc = someLibrary.addListener(element, 'change',()=>{ props.onChange(element); //This is where I encounter an error }) } The structure of the props ...

What is the best way to align an InputAdornment IconButton with an OutlinedInput in Material-UI?

Struggling to replicate a text input from a mockup using Material-UI components. Initially tried rendering a button next to the input, but it didn't match. Moving on to InputAdornments, getting closer but can't get the button flush with the input ...

jquery: scroll-triggered opacity effect

I am attempting to gradually increase the opacity of my div as the user scrolls, similar to this: $(document).ready(function() { $(document).scroll(function(e) { changeOpacity(); }); var element = $('#element'); var elem ...

Utilizing AngularJS to dynamically bind input to a value with a filter and keep it updated

I'm facing an issue with an input element linked to an object property along with a filter. Even though I update the object.field programmatically, the displayed value in the input does not change, although the correct value is reflected in the DOM In ...

Tactics for developing an intricate AJAX and jQuery form with nested components

We have a complex form to construct that will allow the creation or updating of multiple instances of entity A, where each instance of entity A can have multiple instances of entity B. Both types of entities are intricate with many fields and dropdowns, b ...

MeteorJS: Verification of User Email addresses

After sending an email verification to a user, how can I ensure they actually verify their email after clicking on the link sent to their address? I'm aware of this function Accounts.onEmailVerificationLink but I'm unsure of how to implement i ...

Angular2 and the exciting world of Mapbox-gl

I am currently facing an issue with integrating mapbox-gl into my Angular2 application. Despite creating the service, the app is not functioning properly. Service import {Injectable} from '@angular/core'; import * as mapboxgl from 'map ...

The Redux store is duplicating the state across different reducers, causing it to appear twice

Having Trouble Viewing Different States for Two Reducers in Redux DevTools In my React project, I am attempting to incorporate a second reducer using Redux to gain a better understanding of the overall state. However, when I inspect the state in Redux Dev ...

A Promise is automatically returned by async functions

async saveUserToDatabase(userData: IUser): Promise<User | null> { const { username, role, password, email } = userData; const newUser = new User(); newUser.username = username; newUser.role = role; newUser.pass ...

Troubleshooting issues with jQuery post on dynamically generated jQuery elements

DISCLAIMER: The following question is extracted from the post: jQuery not working on elements created by jQuery I'm using jQuery to dynamically insert list items into a list through an ajax call that runs every second. Below is the code for the ajax ...

Creating a bucket in Autodesk Forge Viewer is a straightforward process with the Forge API

I am currently facing an issue while trying to create and upload a new bucket for storing models. I keep getting a 400 Bad Request error, even though I managed to make it work in a previous project using a different method. Strangely enough, I now encounte ...

Transform Dynamic Array to JSON structure

I am currently developing a feature in my SvelteKit application that allows users to create custom roles for themselves. Users can input a role name and add it to an array, which is then displayed below. https://i.stack.imgur.com/oUPFU.png My goal is to ...

The .ajaxSubmit function in jquery.form.js seems to be malfunctioning when using the data option

I'm currently working with the jQuery plugin jquery.form.js and I'm looking to programmatically submit a form while including a file. Although I've set up the code and options to work with $.ajax, it's not functioning with .ajaxSubmit. ...