D3.js - Tooltips only shows the first properties values of the data object

First things first, you can play with all the code of the component and the data here: CodeSandBox

I have a Timeline component in d3 , and for each one of the circles I want to show the respective data in the tooltip according to the day of the month. As you can see in the image, I can do it, the problem here is that in all the tooltips it only shows the first properties value of data .info . It doesn’t do the “loop” through the tooltips , like it does for the colors of the circles… and I really think that it’s because of the generation of only and exclusive one tooltip per event… i really don’t know how to solve this problem as I tried multiple things.

data.tsx:

const data = [
  {
    tag: "Vários",
    date: "2021-01-01 00:00:00",
    info: [
      {
        tag: "Prescrição",
        date: "2021-01-01 00:00:00",
        healthProf: "Dr. Ana Martins Noronha"
      },
      {
        tag: "Avaliações",
        date: "2021-01-01 00:00:00",
        healthProf: "Dr. João Palmeira"
      }
    ],
    category: {
      tag: "Vários",
      color: "#999999"
    }
  },
  {
    tag: "Prescrição",
    date: "2021-01-02 00:00:00",
    info: [
      {
        tag: "Diagnõsticos",
        date: "2021-01-02 00:00:00",
        healthProf: "Dr. Roberto Ladeiras"
      }
    ],
    category: {
      tag: "Prescrição",
      color: "#4199e0"
    }
  }, ...

I really think the problem comes from the following logic:

Timeline.tsx

tooltip.each(function (d: any, index: number) {
  for (var i = 0; i < d.info.length; i++) {
    tooltip
      .append("span")
      .text((d: any) => {
        return `${d.info[i].tag}: `;
      })
      .attr("width", "1250px")
      .style("color", "#ffffff")
      .style("text-transform", "uppercase")
      .style("font-weight", "bold")
      .style("padding", "0")
      .style("font-size", "10px");

    tooltip
      .append("span")
      .style("font-weight", "regular")
      .text((d: any) => {
        return d.info[i].date;
      })
      .style("color", "#ffffff")
      .style("font-size", "10px");

    tooltip
      .append("div")
      .style("font-weight", "regular")
      .text((d: any) => {
        return d.info[i].healthProf;
      })
      .style("color", "#ffffff")
      .style("font-size", "10px");
  }
});

I looked at your codesandbox and the tooltip code here and there are two problems. One, you have way too much tooltip code and quite a bit is repetitive. Two, the tooltip has to be attached to the element you want it to describe and can’t be fully defined otherwise.

There are usually a few steps to get a good D3 tooltip. First, create a hidden div, often attached to the body, for the tooltip. Two, determine which elements should be described by the tooltip and hence should trigger its appearance. I assume in your case that means the circle.episode elements. Three, in the selection that creates those elements, create mouse functions that will update the tooltip. So you’ll need at least a .on('mouseover', ...) and .on('mouseout', ...) functions on each element that needs a tooltip. You are creating the mouse event functions that will in turn create the tooltip. You are not creating the tooltip directly (except for the initial div).

So your selection will look something like

    d3.select("#Timeline")
      .selectAll(".episode")
      .data(data)
      .raise()
      .join("circle")
      .style("stroke", "#ffffff")
      .style("stroke-width", "2px")
      .style("fill", (d: any) => {
        return d.category.color;
      })
      .attr("cx", (registo: any) => xScale(getDate(registo.date)))
      .attr("cy", 0)
      .attr("r", 5)
      .attr("class", "episode")
      .on('mouseover' (event, datum) => {
        tooltip
	     .attr('id', 'tooltip')
	     .style('display', 'inline')
         .style('position', 'absolute')
         .style('width', '250px')
         .style('height', '50px')
	     .style('visibility', 'visible')
   	     .attr('your-attribute', datum.attribute)
         ...
	     .html('<p>your html</p>');
       })
      ...

where event is the mouse event from the DOM and datum is your current item in .data(...) (assuming a recent D3 version). You can add a mouseout function to hide the div and mousemove to move it relative to the pointer if you like.

1 Like

Hi sir. Thanks for your answer. I understand all of it but the datum. How it will help to iterate throught the tooltips? I only know the datum method, can’t understand how it will play in an .attr and what attribute to choose. I tried the .text(datum.info.tag), but it doesn’t make any sense, I’m very confused.

I can make info appear in the tooltip if i write this: .append("span").text(datum.info[0].healthProf)… but it adds a healthProf name everytime I mouseover one circle, all in the same tooltip. How can I iterate correctly according to the data that i have? Sorry for the doubt (it may be a stupid problem) and thank you.

You don’t iterate through the tooltips. You iterate over the data and represent it as circles with attached mouse functions that make a div appear/disappear that is filled with information the circle represents. You tell D3 that you want circles to represent each schedule day in your data and that each circle should fill and show a tooltip with the schedule details for that day using mouse event functions that you define. You create the functions to create the tooltips; you don’t create the tooltips directly.

The datum argument for the D3 mouse functions are one from

    d3.select("#Timeline")
      .selectAll(".episode")
      .data(data)

One item of the data array is a datum. Most examples shorten it to d. In your case, the first datum in your data should be

  {
    tag: "Vários",
    date: "2021-01-01 00:00:00",
    info: [
      {
        tag: "Prescrição",
        date: "2021-01-01 00:00:00",
        healthProf: "Dr. Ana Martins Noronha"
      },
      {
        tag: "Avaliações",
        date: "2021-01-01 00:00:00",
        healthProf: "Dr. João Palmeira"
      }
    ],
    category: {
      tag: "Vários",
      color: "#999999"
    }
  },

You can reference bits of it in the mouse event callbacks as datum.info[0].tag.

This sounds like your iterating over data to make tooltips and not iterating over data to make circles with mouse event functions to make the tooltips. Like I said, you need the first tooltip declaration (where you create the div) and one selection over which you iterate to make the circles and mouse functions. Comment out the extra tooltip stuff and make one selection with the circles and it works.