Flow
Flow

Reputation: 33

d3 - Data loads partially

I have a problem with a data string returning 'undefined' in my tooltip, while that same piece of data does affect the colors of my svg.

Objective: I want to display a map, chloropleth style, that displays a tooltip for each country with a) country names, loaded from a .json object (works fine) b) some values corresponding to each country, loaded from a tsv (works partially so far)

File structure : The main .js file calls 1 topojson file (with country paths and names) as well as a .tsv file (with specific area data for each country)

Resources used: 1- MBostock's chloropleth (see note below) 2- d3.tip by Justin Palmer

Play with it

Here is a Plunker for people to play with it (due to the heaviness of the world.json I use, it may take a while to load).

---------------

Relevant code bit :

  queue()
.defer(d3.json, "world.json")
.defer(d3.tsv, "winemap.tsv", function setMaptoTotal(d) { rate.set(d.name, +d.total); })
.await(ready);



 var tip = d3.tip()
      .attr('class', 'd3-tip')
       .html(function mylabel(d) { return d.properties.name  + "<table><tr><td><span style='color: #fcf772'> Total area cultivated:  " + d.total  +" ha</span></td></tr><tr><td> <span style='color:#bf2a2a'> Global rank: "+ d.rank + " </span></td></tr></table>";})
      .direction('sw')
      .offset([0, 2]);

var svg = d3.selectAll('body')
    .append('svg')
        .attr('width',width)
        .attr('height',height)
        .call(zoom)
        .call(tip);

// ACTION STARTS HERE // 
function ready(error, world) {
     svg.append("g")
      .attr("class", "counties")
    .selectAll("path")
      .data(topojson.feature(world, world.objects.countries).features)
    .enter().append("path")
 .attr("class", function (d) { return quantize(rate.get(d.properties.name)); })
      .attr("d", path)
      .call(tip)
      .on('mouseover', tip.show)
     .on('mouseout', tip.hide)
     ;

Options tried:

1- Moving .call(tip) after or inside the ready function which triggers data load via queue(). >>> This still returns undefined in my tooltip.

2- Use queue.defer(mylabel(d)). >>> Nothing appears on screen.

3- Taking into account the asynchronous nature of d3.tsv, I went through this question. If I understand well, you have to refer to the function you want to call both inside and after d3.tsv (with or without setting a global variable). I thus planned to refer to the mylabel(d) function both inside and after d3.tsv. However I noticed that function (d) { return quantize(rate.get(d.properties.name)); }) is not written in my d3.tsv yet it works fine.

Note: sorry I could not post more links at this stage, e.g. to MBostick's chloropleth or d3.tip, but it requires more reputation :)

Upvotes: 1

Views: 878

Answers (1)

Flow
Flow

Reputation: 33

In case anyone faces the same issue, I finally found the answer. There were 2 problems:

1- Data should be first associated with an array.

First you need to pair, inside the ready function, the id with each data entry

function ready (error, world, data)  {
 var Total= {};
 var Name= {};
 var Rank= {};

 data.forEach(function(d) {
        Total[d.id] = +d.total;
        Name[d.id] = d.name;
        Rank[d.id] = +d.rank;
 });

2- This method only works with referring explicitly to "id"

The command .data(topojson.feature(world, world.objects.countries).features) will search for the id property of each json object that you want to apply a transformation with. The bridge between your .tsv and .json MUST be id, both in your .json and .tsv files as well as the code.

The reason you can't replace id with just any name while working with topoJSON is that it holds a specific function in the topojson code itself (see below).

{var r={type:"Feature",id:t.id,properties:t.properties||{},geometry:u(n,t)};return null==t.id&&delete r.id,r}

I solved this by adding into my .tsv file a column with the id attributes of all countries objects inside my .json file. I named this column idso the pairing described in 1- can function.

Careful, you want to represent all countries that are in your .json on your .tsv, otherwise "undefined" will appear again when you hover the countries that would not be mentioned on your .tsv.

Final, working code -

 function ready (error, world, data)  {
     var pairDataWithId= {};
     var pairNameWithId= {};
     var pairRankWithId= {};
    // “d” refers to that data parameter, which in this case is our .csv that (while not necessary) is also named “data.” 
     data.forEach(function(d) {
            pairDataWithId[d.id] = +d.total;
            pairNameWithId[d.id] = d.name;
            pairRankWithId[d.id] = +d.rank;
     });

 // Below my tooltip vars
 var tipin =  function(d) { d3.select(this).transition().duration(300).style('opacity', 1);
 div.transition().duration(300).style('opacity', 1);
 div.text(pairNameWithId[d.id] + " Total Area: "  + pairDataWithId[d.id]+ " Rank: "  + pairRankWithId[d.id])
    .style('left', (d3.event.pageX) + "px")
    .style('top', (d3.event.pageY -30) + "px");
    };
 var tipout =  function() {
     d3.select(this)
     .transition().duration(300).style('opacity', 0.8);
     div.transition().duration(300)
     .style('opacity', 0);
 };

 // Resume

function colorme(d) { return color (pairDataWithId[d.id]);
}

     svg.append('g')
     .attr('class', 'region')
     .selectAll('path')
     .data(topojson.feature(world,world.objects.countries).features)
     .enter()
     .append('path')
     .attr('d', path)
     .style('fill', colorme)
     .style('opacity', 0.8)
     .on ('mouseover', tipin)
     .on('mouseout', tipout);    
 };

Note: I stopped using d3.tip for a simpler version with full code above.

Upvotes: 1

Related Questions