Home > Blockchain >  D3js: How do I make my tooltip appear next to the selection?
D3js: How do I make my tooltip appear next to the selection?

Time:11-20

I am trying to have a tooltip for each square on my heatmap visualization. However, I am not able to position it next to the cursor correctly. This is what happens:

enter image description here

My code is this:

const margin = {
  top: 20,
  right: 20,
  bottom: 30,
  left: 150,
};
const width = 960 - margin.left - margin.right;
const height = 2500 - margin.top - margin.bottom;

let chart = d3
  .select("#chart2")
  .append("div")
  // Set id to chartArea
  .attr("id", "chartArea")
  .classed("chart", true)
  .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   ")");

// Make sure to create a separate SVG for the XAxis
let axis = d3
  .select("#chart2")
  .append("svg")
  .attr("width", width   margin.left   margin.right)
  .attr("height", 40)
  .append("g")
  .attr("transform", "translate("   margin.left   ", 0)");

// Load the data
d3.csv("https://raw.githubusercontent.com/thedivtagguy/files/main/land_use.csv").then(function(data) {
  // console.log(data);
  const years = Array.from(new Set(data.map((d) => d.year)));
  const countries = Array.from(new Set(data.map((d) => d.entity)));
  countries.reverse();
  const x = d3.scaleBand().range([0, width]).domain(years).padding(0.01);

  // create a tooltip
  var tooltip = d3
    // Select all boxes in the chart
    .select("#chart2")
    .append("div")
    .classed("tooltip", true)
    .style("opacity", 0)
    .attr("class", "tooltip")
    .style("background-color", "white")
    .style("border", "solid")
    .style("border-width", "2px")
    .style("border-radius", "5px")
    .style("padding", "5px");

  const mouseover = function(event, d) {
    tooltip.style("opacity", 1);
    d3.select(this).style("stroke", "black").style("opacity", 1);
  };

  const mousemove = function(event, d) {
    tooltip
      .html("The exact value of<br>this cell is: "   d.value)
      // Position the tooltip next to the cursor
      .style("left", event.x   "px")
      .style("top", event.y / 2   "px");
  };

  const mouseleave = function(event, d) {
    tooltip.style("opacity", 0);
    d3.select(this).style("stroke", "none").style("opacity", 0.8);
  };

  const y = d3.scaleBand().range([height, 0]).domain(countries).padding(0.01);

  // Only 10 years
  axis
    .call(d3.axisBottom(x).tickValues(years.filter((d, i) => !(i % 10))))
    .selectAll("text")
    .style("color", "black")
    .style("position", "fixed")
    .attr("transform", "translate(-10,10)rotate(-45)")
    .style("text-anchor", "end");

  chart
    .append("g")
    .call(d3.axisLeft(y))
    .selectAll("text")
    .style("color", "black")
    .attr("transform", "translate(-10,0)")
    .style("text-anchor", "end");

  const colorScale = d3
    .scaleSequential()
    .domain([0, d3.max(data, (d) => d.change)])
    .interpolator(d3.interpolateInferno);

  // add the squares
  chart
    .selectAll()
    .data(data, function(d) {
      return d.year   ":"   d.entity;
    })
    .join("rect")
    // Add id-s
    .attr("id", function(d) {
      return d.year   ":"   d.entity;
    })
    .attr("x", function(d) {
      return x(d.year);
    })
    .attr("y", function(d) {
      return y(d.entity);
    })
    .attr("width", x.bandwidth())
    .attr("height", y.bandwidth())
    .style("fill", function(d) {
      return colorScale(d.change);
      console.log(d.change);
    })
    .style("stroke-width", 4)
    .style("stroke", "none")
    .style("opacity", 0.8)
    .on("mouseover", mouseover)
    .on("mousemove", mousemove)
    .on("mouseleave", mouseleave);
});
#chart2 .chart {
  width: 960px;
  max-height: 900px;
  overflow-y: scroll;
  overflow-x: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="chart2" class="mx-auto"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

I think I have some idea of why this is happening. Since I am appending the tooltip iv to chart2 div like this, all positioning is happening relative to that div, which means it'll inevitably be below the entire chart and not on top of it:

  // create a tooltip
  var tooltip = d3
    // Select all boxes in the chart
    .select("#chart2")
    .append("div")
    .classed("tooltip", true)
    .style("opacity", 0)
    .attr("class", "tooltip")
    .style("background-color", "white")
    .style("border", "solid")
    .style("border-width", "2px")
    .style("border-radius", "5px")
    .style("padding", "5px");

const mousemove = function(event, d) {
        tooltip
          .html("The exact value of<br>this cell is: "   d.value)
          // Position the tooltip next to the cursor
          .style("left", event.x   "px")
          .style("top", event.y / 2   "px");
      };

But how else would I do this? I have tried trying to select by the hovered-on rect element but that hasn't been working. I am trying to do something like this.

How can I fix this?

A live version can be found here

CodePudding user response:

Just add in the mousemove function .style("position","absolute"); and change top style to .style("top", event.y "px")

const margin = {
  top: 20,
  right: 20,
  bottom: 30,
  left: 150,
};
const width = 960 - margin.left - margin.right;
const height = 2500 - margin.top - margin.bottom;

let chart = d3
  .select("#chart2")
  .append("div")
  // Set id to chartArea
  .attr("id", "chartArea")
  .classed("chart", true)
  .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   ")");

// Make sure to create a separate SVG for the XAxis
let axis = d3
  .select("#chart2")
  .append("svg")
  .attr("width", width   margin.left   margin.right)
  .attr("height", 40)
  .append("g")
  .attr("transform", "translate("   margin.left   ", 0)");

// Load the data
d3.csv("https://raw.githubusercontent.com/thedivtagguy/files/main/land_use.csv").then(function(data) {
  // console.log(data);
  const years = Array.from(new Set(data.map((d) => d.year)));
  const countries = Array.from(new Set(data.map((d) => d.entity)));
  countries.reverse();
  const x = d3.scaleBand().range([0, width]).domain(years).padding(0.01);

  // create a tooltip
  var tooltip = d3
    // Select all boxes in the chart
    .select("#chart2")
    .append("div")
    .classed("tooltip", true)
    .style("opacity", 0)
    .attr("class", "tooltip")
    .style("background-color", "white")
    .style("border", "solid")
    .style("border-width", "2px")
    .style("border-radius", "5px")
    .style("padding", "5px");

  const mouseover = function(event, d) {
    tooltip.style("opacity", 1);
    d3.select(this).style("stroke", "black").style("opacity", 1);
  };

  const mousemove = function(event, d) {
    tooltip
      .html("The exact value of<br>this cell is: "   d.value)
      // Position the tooltip next to the cursor
      .style("left", event.x   "px")
      .style("top", event.y   "px")
      .style("position","absolute");
  };

  const mouseleave = function(event, d) {
    tooltip.style("opacity", 0);
    d3.select(this).style("stroke", "none").style("opacity", 0.8);
  };

  const y = d3.scaleBand().range([height, 0]).domain(countries).padding(0.01);

  // Only 10 years
  axis
    .call(d3.axisBottom(x).tickValues(years.filter((d, i) => !(i % 10))))
    .selectAll("text")
    .style("color", "black")
    .style("position", "fixed")
    .attr("transform", "translate(-10,10)rotate(-45)")
    .style("text-anchor", "end");

  chart
    .append("g")
    .call(d3.axisLeft(y))
    .selectAll("text")
    .style("color", "black")
    .attr("transform", "translate(-10,0)")
    .style("text-anchor", "end");

  const colorScale = d3
    .scaleSequential()
    .domain([0, d3.max(data, (d) => d.change)])
    .interpolator(d3.interpolateInferno);

  // add the squares
  chart
    .selectAll()
    .data(data, function(d) {
      return d.year   ":"   d.entity;
    })
    .join("rect")
    // Add id-s
    .attr("id", function(d) {
      return d.year   ":"   d.entity;
    })
    .attr("x", function(d) {
      return x(d.year);
    })
    .attr("y", function(d) {
      return y(d.entity);
    })
    .attr("width", x.bandwidth())
    .attr("height", y.bandwidth())
    .style("fill", function(d) {
      return colorScale(d.change);
      console.log(d.change);
    })
    .style("stroke-width", 4)
    .style("stroke", "none")
    .style("opacity", 0.8)
    .on("mouseover", mouseover)
    .on("mousemove", mousemove)
    .on("mouseleave", mouseleave);
});
#chart2 .chart {
  width: 960px;
  max-height: 900px;
  overflow-y: scroll;
  overflow-x: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="chart2" class="mx-auto"></div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related