Adding tooltip in d3.js map

2019-03-30 02:31发布

问题:

I'm trying to add a tooltip showing the name of districts when you hover over it in the map in d3.js. The input is a topojson file and I've been able to successfully generate the map with district boundaries and highlight the currently selected district.

For the tooltip I tried doing something similar to this, but nothing happens at all. The code I've used is given below. The tooltip code is towards the end.

var width = 960,
    height = 600;

var projection = d3.geo.albers()
    .center([87, 28])
    .rotate([-85, 0])
    .parallels([27, 32]);

var path = d3.geo.path()
    .projection(projection);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.append("rect")
    .attr("width", width)
    .attr("height", height);  

var g = svg.append("g");  

var div = d3.select("body").append("div")
  .attr("class", "tooltip")
  .style("opacity", 1e-6);

d3.json("data/nepal3.json", function(error, npl) {
    var districts = topojson.feature(npl, npl.objects.nepal_districts);

    projection
      .scale(1)
      .translate([0, 0]);

    var b = path.bounds(districts),
      s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
      t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

    projection
      .scale(s)
      .translate(t);

      g.selectAll(".nepal_districts")
          .data(districts.features)
        .enter().append("path")
          .attr("class", function(d) { return "nepal_districts " + d.id; })
          .attr("d", path)
          .on("mouseover", function(d,i) {
              d3.select(this.parentNode.appendChild(this)).transition().duration(300)
                  .style({'stroke-width':2,'stroke':'#333333','stroke-linejoin':'round','cursor':'pointer','fill':'#b9270b'});
          })
          .on("mouseout", function(d,i) {
              d3.select(this.parentNode.appendChild(this)).transition().duration(100)
                  .style({'stroke-width':2,'stroke':'#FFFFFF','stroke-linejoin':'round','fill':'#3d71b6'});
          });

      g.append("path")
          .datum(topojson.mesh(npl, npl.objects.nepal_districts, function(a, b) { return a !== b;}))
          .attr("d", path)
          .attr("class", "district-boundary");

      /* Tooltip */

      g.selectAll(".nepal_districts")
          .data(districts.features)
        .enter().append("text")
          .append("svg:rect")
            .attr("width", 140)
            .attr("height", 140)
            .text(function(d) { return d.properties.name; })
            .on("mouseover", mouseover)
            .on("mousemove", mousemove)
            .on("mouseout", mouseout);

      function mouseover() {
        div.transition()
            .duration(300)
            .style("opacity", 1);
      }

      function mousemove() {
        div
            .text(d3.event.pageX + ", " + d3.event.pageY)
            .style("left", (d3.event.pageX - 34) + "px")
            .style("top", (d3.event.pageY - 12) + "px");
      }

      function mouseout() {
        div.transition()
            .duration(100)
            .style("opacity", 1e-6);
      } 

});

The CSS is

div.tooltip {   
  position: absolute;           
  text-align: center;           
  width: 60px;                  
  height: 28px;                 
  padding: 2px;             
  font: 12px sans-serif;        
  background: #4c4c4c;   
  border: 0px;      
  border-radius: 8px;           
  pointer-events: none;         
}

The code I added for "Tooltip" does nothing at all. What am I doing wrong here?

The topojson file has this format. I wanted to get the "name" property to show up in the Tooltip.

{
  "type": "Topology",
  "objects": {
    "nepal_districts": {
      "type": "GeometryCollection",
      "geometries": [
        {
          "type": "Polygon",
          "id": 0,
          "properties": {
            "name": "HUMLA"
          },
          "arcs": [
            [
              0,
              1,
              2,
              3
            ]
          ]
        },

回答1:

Had a similar problem where I ended up adding absolute positioned tooltip to body element, and modyfying its placement according to mouse position.

Add to directive:

function addTooltip(accessor) {
    return function(selection) {
        var tooltipDiv;
        var bodyNode = d3.select('body').node();

        selection.on("mouseover", function(topoData, countryIndex) {
            if (!accessor(topoData, countryIndex)) {
                return;
            }
            // Clean up lost tooltips
            d3.select('body').selectAll('div.tooltipmap').remove();
            formatValue(topoData, countryIndex);

            tooltipDiv = d3.select('body').append('div').attr('class', 'tooltipmap');
            var absoluteMousePos = d3.mouse(bodyNode);
            tooltipDiv.style('left', (absoluteMousePos[0] + 10) + 'px')
                .style('top', (absoluteMousePos[1] - 15) + 'px')
                .style('opacity', 1)
                .style('z-index', 1070);
            accessor(topoData, countryIndex) || '';
        })

            .on('mousemove', function(topoData, countryIndex) {
                if (!accessor(topoData, countryIndex)) {
                    return;
                }
                var absoluteMousePos = d3.mouse(bodyNode);
                tooltipDiv.style('left', (absoluteMousePos[0] + 10) + 'px')
                    .style('top', (absoluteMousePos[1] - 15) + 'px');
                var tooltipText = accessor(topoData, countryIndex) || '';
                tooltipDiv.html(tooltipText);
            })

            .on("mouseout", function(topoData, countryIndex) {
                if (!accessor(topoData, countryIndex)) {
                    return;
                }
                tooltipDiv.remove();
            });
    };


.tooltipmap{ 
  background-color: #000000;
  margin: 10px;
  height: 50px;
  width: 150px;
  padding-left: 10px; 
  padding-top: 10px;
  border-radius: 5px;
  overflow: hidden;
  display: block;
  color: #FFFFFF;
  font-size: 12px;
  position: absolute;
  opacity: 1;
  h6{
    margin: 0;
    padding: 0;
  }
  p{
    color: #FFFFFF;
  }
}

Hope it helps!