简体   繁体   中英

Tooltip always displaying the same value

I'm trying to create a map of coordinates from some data I got in a csv file. The converting of the X/Y axes works perfectly, the circles (or rather dots) get drawn but the mouseover tooltip always displays the last values (or rather the last values +1 which is in my array out of bounds even though the tooltip should be set with the current values of the array.

Longitude and altitude are my two array names

var svgContainer = d3.select("body").append("svg")
    .attr("width", 700)
    .attr("height", 250)
    .style("border", "1px solid black");

var div = d3.select("body").append("div")   
    .attr("class", "tooltip")               
    .style("opacity", 0);


for (i = 0; i < longitude.length; i++) {
    var circleSelection = svgContainer.append("circle")
        .attr("cx", longitude[i])
        .attr("cy", altitude[i])
        .attr("r", 2)
        .style("fill", "purple")
        .on("mouseover", function(d) {      
            div.transition()        
                .duration(200)      
                .style("opacity", .9);      
            div .html("X: " + longitude[i] + " Y: "  + altitude[i]) 
                .style("left", (d3.event.pageX) + "px")     
                .style("top", (d3.event.pageY - 28) + "px");    
        })                  
        .on("mouseout", function(d) {       
            div.transition()        
                .duration(500)      
                .style("opacity", 0);   
        });
}

and here's the css but I doubt the problem's to be found in here

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

Every clue is much appreciated

As a general rule: do not use loops for appending elements in a D3 code. Not only this is not the idiomatic D3 but, more importantly, things will break (as you're seeing right now).

Before anything, here is an explanation of why all the values are the same: JavaScript closure inside loops – simple practical example

Let's see this, hover over any circle:

 var data = ["foo", "bar", "baz"]; var svg = d3.select("svg"); for (var i = 0; i < data.length; i++) { svg.append("circle") .attr("cy", 75) .attr("cx", 50 + i * 100) .attr("r", 20) .attr("fill", "teal") .on("mouseover", function() { console.log(data[i - 1]) }) }
 <script src="https://d3js.org/d3.v4.min.js"></script> <svg></svg>

Things get better using let :

 var data = ["foo", "bar", "baz"]; var svg = d3.select("svg"); for (let i = 0; i < data.length; i++) { svg.append("circle") .attr("cy", 75) .attr("cx", 50 + i * 100) .attr("r", 20) .attr("fill", "teal") .on("mouseover", function() { console.log(data[i]) }) }
 <script src="https://d3js.org/d3.v4.min.js"></script> <svg></svg>

However, even if using let gives the correct result, it is not a good solution, because you are not binding any data.

The best solution is: use a D3 "enter" selection, binding data to the elements:

 var data = ["foo", "bar", "baz"]; var svg = d3.select("svg"); svg.selectAll(null) .data(data) .enter() .append("circle") .attr("cy", 75) .attr("cx", function(d, i) { return 50 + i * 100 }) .attr("r", 20) .attr("fill", "teal") .on("mouseover", function(d) { console.log(d) })
 <script src="https://d3js.org/d3.v4.min.js"></script> <svg></svg>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM