1

I want tooltips to appear next to my mouse when it hovers over a node. I tried solutions I found on SO, but so far, only got this solution by Boxun to work, although it's not quite what I had in mind (D3.js: Position tooltips using element position, not mouse position?).

I was wondering why in my listener function,

.on('mousemove', function(d) {})

, the functions

Tooltips
.style("left", d3.mouse(this)[0])
.style("top", (d3.mouse(this)[1])) 

or

Tooltips
.style("left", d3.event.pageX + 'px')
.style("top", d3.event.pageY + 'px')

shows up on top of the svg instead of where my mouse is.

From reading the answers to the link above, I think I have to transform my coordinates somehow, but I was not able to get that to work.

Here, I am using d3.event.pageX and my mouse is over cherry node. enter image description here

import * as d3_base from "d3";
import * as d3_dag from "d3-dag";

const d3 = Object.assign({}, d3_base, d3_dag);

drawDAG({
  graph: [
    ["apples", "banana"],
    ["cherry", "tomato"],
    ["cherry", "avocado"],
    ["squash", "banana"],
    ["lychee", "cherry"],
    ["dragonfruit", "mango"],
    ["tomato", "mango"]
  ]
})

async function drawDAG(response) {
  loadDag(response['graph'])
    .then(layoutAndDraw())
    .catch(console.error.bind(console));
}

async function loadDag(source) {
  const [key, reader] = ["zherebko", d3_dag.dagConnect().linkData(() => ({}))]
  return reader(source);
}

function layoutAndDraw() {

  const width = 800;
  const height = 800;

  const d3 = Object.assign({}, d3_base, d3_dag);

  function sugiyama(dag) {
    const layout = d3.sugiyama()
      .size([width, height])
      .layering(d3.layeringSimplex())
      .decross(d3.decrossOpt())
      .coord(d3.coordVert());

    layout(dag);
    draw(dag);
  }

  return sugiyama;

  function draw(dag) {
    // Create a tooltip
    const Tooltip = d3.select("root")
      .append("div")
      .attr("class", "tooltip")
      .style('position', 'absolute')
      .style("opacity", 0)
      .style("background-color", "black")
      .style("padding", "5px")
      .style('text-align', 'center')
      .style('width', 60)
      .style('height', 30)
      .style('border-radius', 10)
      .style('color', 'white')


    // This code only handles rendering
    const nodeRadius = 100;

    const svgSelection = d3.select("root")
      .append("svg")
      .attr("width", width)
      .attr("height", height)
      .attr("viewBox", `${-nodeRadius} ${-nodeRadius} ${width + 2 * nodeRadius} ${height + 2 * nodeRadius}`);
    const defs = svgSelection.append('defs');

    const steps = dag.size();
    const interp = d3.interpolateRainbow;
    const colorMap = {};
    dag.each((node, i) => {
      colorMap[node.id] = interp(i / steps);
    });

    // How to draw edges
    const line = d3.line()
      .curve(d3.curveCatmullRom)
      .x(d => d.x)
      .y(d => d.y);

    // Plot edges
    svgSelection.append('g')
      .selectAll('path')
      .data(dag.links())
      .enter()
      .append('path')
      .attr('d', ({
        data
      }) => line(data.points))
      .attr('fill', 'none')
      .attr('stroke-width', 3)
      .attr('stroke', ({
        source,
        target
      }) => {
        const gradId = `${source.id}-${target.id}`;
        const grad = defs.append('linearGradient')
          .attr('id', gradId)
          .attr('gradientUnits', 'userSpaceOnUse')
          .attr('x1', source.x)
          .attr('x2', target.x)
          .attr('y1', source.y)
          .attr('y2', target.y);
        grad.append('stop').attr('offset', '0%').attr('stop-color', colorMap[source.id]);
        grad.append('stop').attr('offset', '100%').attr('stop-color', colorMap[target.id]);
        return `url(#${gradId})`;
      });

    // Select nodes
    const nodes = svgSelection.append('g')
      .selectAll('g')
      .data(dag.descendants())
      .enter()
      .append('g')
      .attr('width', 100)
      .attr('height', 100)
      .attr('transform', ({
        x,
        y
      }) => `translate(${x}, ${y})`)
      .on('mouseover', function(d) {
        Tooltip
          .style('opacity', .8)
          .text(d.id)
      })
      .on('mouseout', function(d) {
        Tooltip
          .style('opacity', 0)
      })
      .on('mousemove', function(d) {
        var matrix = this.getScreenCTM()
          .translate(+this.getAttribute("cx"), +this.getAttribute("cy"));
        Tooltip
          .html(d.id)
          .style("left", (window.pageXOffset + matrix.e - 50) + "px")
          .style("top", (window.pageYOffset + matrix.f - 60) + "px");
      })

    // Plot node circles
    nodes.append('rect')
      .attr('y', -30)
      .attr('x', (d) => {
        return -(d.id.length * 15 / 2)
      })
      .attr('rx', 10)
      .attr('ry', 10)
      .attr('width', (d) => {
        return d.id.length * 15;
      })
      .attr('height', (d) => 60)

      .attr('fill', n => colorMap[n.id])

    // Add text to nodes
    nodes.append('text')
      .text(d => {
        let id = '';
        d.id.replace(/_/g, ' ').split(' ').forEach(str => {
          if (str !== 'qb')
            id += str.charAt(0).toUpperCase() + str.substring(1) + '\n';
        });
        return id;
      })
      .attr('font-size', 25)
      .attr('font-weight', 'bold')
      .attr('font-family', 'sans-serif')
      .attr('text-anchor', 'middle')
      .attr('alignment-baseline', 'middle')
      .attr('fill', 'white')
      .attr();
  }
}
hao
  • 73
  • 1
  • 9

1 Answers1

1

You can try using this instead, this will make sure that the tooltip is displayed on the exact mouse position.

 d3.event.offsetY