Reputation: 411
I am creating a choropleth for Australian food waste in D3 v5, and I am intending to create tooltip that is displayed when users hover in each state, and multiple values could be seen such as waste used for energy recovery, recycling and disposal, taken from this CSV file:
States,Energy Recovery,Disposal,Recycling,Total
ACT,53,70,0,123
New South Wales,28,80,48,156
Northern Territory,34,203,0,237
Queensland,50,143,10,203
South Australia,36,75,7,118
Tasmania,47,138,0,185
Victoria,51,108,14,173
Western Australia,29,163,29,221
Here is my code I used to create such type of tooltip. When the users hover in, the total amount, as well as the amount on columns of Energy_Recovery, Disposal and Recycling should be displayed:
const second_width = 1000;
const second_height = 850;
const projection = d3.geoMercator().center([132, -28])
.translate([second_width/2, second_height/2])
.scale(1000);
const second_color = d3.scaleQuantize().range(['#fef0d9','#fdd49e','#fdbb84','#fc8d59','#e34a33','#b30000']);
const path = d3.geoPath().projection(projection);
const second_svg = d3.select("#chart2")
.append("svg")
.attr("width", second_width)
.attr("height", second_height);
d3.csv("data/Waste_Per_State_Per_Capita(1).csv").then(function(data) {
//Set input domain for color scale
second_color.domain([
d3.min(data, function(d) { return d.Total; }),
d3.max(data, function(d) { return d.Total; })
]);
d3.json("data/aust.json").then(function(json) {
for (var i = 0; i < data.length; i++) {
var data_state = data[i].States;
//Grab data value, and convert from string to float
var dataTotal = parseFloat(data[i].Total);
var dataEnergy = parseFloat(data[i].Energy_Recovery);
var dataDisposal = parseFloat(data[i].Disposal);
var dataRecycling = parseFloat(data[i].Recycling);
for (var j = 0; j < json.features.length; j++) {
var json_state = json.features[j].properties.STATE_NAME;
if (data_state == json_state) {
//Copy the data value into the JSON
json.features[j].properties.value = dataTotal;
//Stop looking through the JSON
break;
}
}
}
// create tooltip
var second_tooltip = second_svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
second_tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
second_tooltip.append("text")
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px");
second_svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.attr("class", "state")
.style("fill", function(d) {
//Get data value
var value = d.properties.value;
if (value) {
//If value exists…
return second_color(value);
} else {
//If value is undefined…
return "#ccc";
}
})
.on('mouseover', function(d) {
var second_xPosition = parseFloat(d3.select(this).attr("x"));
var second_yPosition = parseFloat(d3.select(this).attr("y"));
second_tooltip.attr("x", second_xPosition)
.attr("y", second_yPosition);
second_tooltip.style("display", "block");
second_tooltip.select("text").text(d.properties.STATE_NAME + " "
+ "Amount of food waste per capita: " + d.properties.value + "(kg/year)");
})
.on('mouseout', function(d) {
second_tooltip.style('display', 'none');
});
})
(my JSON file is taken from here:https://gist.github.com/GerardoFurtado/02aa65e5522104cb692e)
However, when I tried to run that in the server, not only my tooltip displayed in a wrong position (in the upper-left corner of the svg file rather than in the same place when I hover the mouse in), it could only shows the total value of waste per capita, as in this image:
I would like to ask that, are there any method that I could use to fix this code, so that I could display tooltip in the place that I hover my mouse in, with all necessary data that I cited earlier?
Thank you!
Upvotes: 2
Views: 562
Reputation: 7210
Try this (see the snippet in full-page):
const w = 850;
const h = 700;
//Define map projection // geoEqualEarth
const projection = d3.geoMercator()
.center([ 132, -28 ])
.translate([ w/2, h/2 ])
.scale(1000);
//Define path generator
const path = d3.geoPath()
.projection(projection);
const color = ['#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69','#fccde5','#d9d9d9'];
//Create SVG
const svg = d3.select("svg")
.attr('width', w)
.attr('height', h)
//Load in GeoJSON data
d3.json('https://gist.githubusercontent.com/GerardoFurtado/02aa65e5522104cb692e/raw/8108fbd4103a827e67444381ff594f7df8450411/aust.json')
.then(json => onGeoJsonLoaded(json))
.catch(err => console.log('ERROR: ', err));
const onGeoJsonLoaded = json => {
//Bind data and create one path per GeoJSON feature
const states = svg.selectAll('g.state')
.data(json.features)
.enter()
.append('g')
.classed('state', true);
states.append('path')
.attr("d", path)
.attr("stroke", 'white')
.attr("fill", (d, i) => color[i]);
//States
states.append("text")
.attr("fill", "darkslategray")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("text-anchor", "middle")
.attr("dy", 15)
.text(function(d) {
return d.properties.STATE_NAME;
});
//Append the name
/*
svg.append("text")
.attr("x", 446)
.attr("y", 340)
.attr("font-size", 90)
.attr("font-weight", "bold")
.attr("font-family", "Roboto")
.attr("text-anchor", "middle")
.attr("opacity", 0.10)
.text("AUSTRALIA");
*/
d3.json('https://api.jsonbin.io/b/60af2dc3d0f4985540524d62')
.then(dataJson => onDataJsonLoaded(dataJson))
.catch(err => console.log('ERR: ', err));
}
const tooltipPath = (width, height, offset, radius) => {
const left = -width / 2
const right = width / 2
const top = -offset - height
const bottom = -offset
return `M 0,0
L ${-offset},${bottom}
H ${left + radius}
Q ${left},${bottom} ${left},${bottom - radius}
V ${top + radius}
Q ${left},${top} ${left + radius},${top}
H ${right - radius}
Q ${right},${top} ${right},${top + radius}
V ${bottom - radius}
Q ${right},${bottom} ${right - radius},${bottom}
H ${offset}
L 0,0 z`
}
const onDataJsonLoaded = json => {
console.log('DATA: ', json);
const rows = Object.keys(json[0]).filter(n => n !== 'State');
const tooltip = svg.append('g')
.classed('tooltip', true)
.style('visibility', 'hidden');
tooltip.append('path')
.attr('d', tooltipPath(150, 80, 5, 5))
rows.forEach((row, index) => {
tooltip.append('text')
.text(`${row} :`)
.attr('x', -70)
.attr('y', -68 + index * 18);
tooltip.append('text')
.classed(row.replace(' ', '_'), true)
.attr('x', 40)
.attr('y', -68 + index * 18)
}
);
svg.selectAll('g.state')
.on('mouseenter', d => {
const stateData = json.find(s => s.State === d.properties.STATE_NAME);
rows.forEach(row => tooltip.select(`.${row.replace(' ', '_')}`).text(stateData[row]));
tooltip.attr('transform', `translate(${path.centroid(d)})`);
tooltip.style('visibility', 'visible');
})
.on('mouseleave', () => tooltip.style('visibility', 'hidden'));
};
.tooltip > path {
fill: white;
stroke: black;
}
.tooltip > text {
font-family: "Ubuntu";
font-size: 12px;
fill: black;
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg/>
Upvotes: 1