Reputation: 33
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
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
Reputation: 33
In case anyone faces the same issue, I finally found the answer. There were 2 problems:
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;
});
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 id
so 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