1

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

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
Kecksohn
  • 21
  • 2
  • Do you have a js fiddle or some sort of demo area you can set up? My first thought is you're not binding data to DOM elements like is supposed to happen in d3 (look into d3's `.data()` method). – Nick Oct 24 '17 at 19:14
  • oh thank you very much, this seems to be my problem – Kecksohn Oct 24 '17 at 19:46

1 Answers1

1

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>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171