Show tooltip on the D3 map - javascript

I've created a basic map using D3 with countries geojson. Here is the demo.
Now when the user clicks on any coordinate on the map, I am showing a weather info within a tooltip with weather icon as a marker.
countries = countriesGroup
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.attr("id", function(d, i) {
return "country" + d.properties.iso_a3;
})
.attr("class", "country")
// add a mouseover action to show name label for feature/country
.on("mouseover", function(d, i) {
d3.select("#countryLabel" + d.properties.iso_a3).style("display", "block");
})
.on("mouseout", function(d, i) {
d3.select("#countryLabel" + d.properties.iso_a3).style("display", "none");
})
// add an onclick action to zoom into clicked country
.on("click", function(d, i) {
var eventLocation = d3.mouse(this);
var coordinates = projection.invert(eventLocation);
var proxy = "https://cors-anywhere.herokuapp.com/";
var wetherInfoUrl =
"https://api.darksky.net/forecast/c68e9aaf0d467528b9363e383bde6254/" + coordinates[1] + "," + coordinates[0] + "?exclude=minutely,hourly,daily";
$.ajax({
url: proxy + wetherInfoUrl,
success: function(response) {
tooltipDiv
.transition()
.duration(200)
.style("opacity", 0.9);
tooltipDiv
.html('<h3>Dynamic Weather info: '+ response.currently.humidity +'</h3><br/><img src="https://darksky.net/images/weather-icons/' + response.currently.icon + '.png" alt="clear-night Icon" width="50" height="50">')
.style("left", (eventLocation[0] - 250) + "px")
.style("top", (eventLocation[1] - 100) + "px");
}
});
d3.selectAll(".country").classed("country-on", false);
d3.select(this).classed("country-on", true);
boxZoom(path.bounds(d), path.centroid(d), 20);
});
Now the issue is, the tooltip remains at the absolute position relative to the body (It stays at the exact position on the screen which is misleading if we pan and zoom the map) whereas I want the tooltip to be relative to the coordinate clicked on the map and should be fixed to the clicked coordinate no matter where we zoom or pan in the map, similar to this leaflet example (In short I want tooltip to behave similarly to the typical map pin markers).

I was able to do this using CSS and the following snippets:
var tooltipDiv = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
div.tooltip {
position: absolute;
text-align: center;
width: 80px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
... and then in the svgs,
.on("mouseover", function(d) {
tooltipDiv.transition()
.duration(200)
.style("opacity", .9);
tooltipDiv.html(d.vessel_name)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
Definitely an imperfect solution, but hopefully it helps. Full source:
Demo:
http://Ares513.github.io/04-MapsAndViews/
Source:
https://github.com/Ares513/MapsAndViews

You should add the element to the map's SVG, not the body. like this:
svg.select("#map")
.append("text")
.data(json)
.classed("geopath", true);

Related

Extend D3 Chart to add tooltip from HTML page

I have a D3 graph showing tooltip on mouseover. The graph is currently created from a JS file.
D3 graph code:
var tooltip = d3.select('body')
.append('div')
.style('position', 'absolute')
.style('padding', '0 10px')
.style('background', 'white')
.style('opacity', 0);
var svgE = svg
.append("g")
.selectAll()
.data(graph.data)
.join("rect")
.attr("x", xFunc)
.attr("y", yFunc)
.on('mouseover', function (d) {
tooltip.transition().duration(200)
.style('opacity', .9);
var toolTipText = d.x + " - " + d.y;
tooltip.html(toolTipText)
.style('left', (d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 30) + 'px');
})
.on('mouseout', function (d) {
tooltip.transition().duration(200)
.style('opacity', 0);
tooltip.html("");
});
Is it possible to remove the tooltip code from the JS file and move it to an HTML page so I can add tooltip functionality whenever needed?
D3 code in JS file will be:
var svgE = svg
.append("g")
.selectAll()
.data(graph.data)
.join("rect")
.attr("x", xFunc)
.attr("y", yFunc);
What do I add in the HTML block to add tooltip functionality?
The example you provided creates a tooltip entirely in JavaScript, but if you for whatever reason prefer creating a tooltip in HTML you can do that as well.
const mainSvg = d3.select('svg#mainSvg');
const tooltip = d3.select('div.tooltip');
const aRectangle = mainSvg.append('rect')
.attr('x', 50)
.attr('y', 50)
.attr('height', 100)
.attr('width', 100)
.attr('fill', 'green');
aRectangle.on('mouseover', (d) => {
tooltip.style('visibility', 'visible');
tooltip.html('You are mousing over a rectangle.');
tooltip.style('left', `${d3.event.pageX}px`)
.style('top', `${d3.event.pageY}px`)
});
aRectangle.on('mouseout', (d) => {
tooltip.style('visibility', 'hidden');
});
svg#mainSvg {
height: 200px;
width: 200px;
background-color: gray;
}
div.tooltip {
z-index: 1;
background-color: white;
position: absolute;
padding: 0 10px;
border: 1px solid black;
pointer-events: none;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div>
<svg id='mainSvg'></svg>
</div>
<div class='tooltip'>
This is a tooltip
</div>

Tooltip doesn't disappear on mouseout

I'm trying to add a tooltip to a rect. It does pop up on mouse pointer hover over a bar but it doesn't want to disappear on mouseout event. I've also tried to use div.style("display", "none"), but it doesn't work either. For some reason it doesn't want to trigger mouseover event again after mouseout. It just keep displaying a tooltip.
http://bl.ocks.org/edkiljak/dc85bf51a27122380c68909cdd09d388
div.tooltip {
position: absolute;
text-align: left;
padding: 4px;
font-family: Lato, arial, sans-serif;
font-size: 14px;
background: #eee;
border-radius: 2px;
border: 1px solid gray;
pointer-events: none;
}
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var bars = barGroup.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function (d) {
return heightScale(d.Vendor);
})
.attr("width", function (d) {
return widthScale(+d.Share2016)
})
.attr("height", heightScale.bandwidth() / 1.1)
.style("fill", function (d, i) {
return color(i);
})
.on("mouseover",function (d){
div.transition()
.duration(200)
div
.style("opacity", .9)
.html("Vendor: " + "<strong>" + d.Vendor + "</strong>" + "<br>" + "Market share in 2016: " + d.Share2016 + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
d3.select(this)
.style("fill", "#93ceff")
})
.on("mouseout", function(){
d3.select(this)
.transition()
.duration(50)
.style("fill", function(d,i){
return color(i);
})
d3.select(div).remove()
})
What am I doing wrong here?
The problem lies here:
d3.select(div).remove()
As div is itself a selection, you're selecting a selection, and that makes little sense.
Instead of that, just use div in the mouseout:
div.remove()
Or, even better, just set its opacity to zero:
div.style("opacity", 0)
Here is the updated bl.ocks with just that change: http://bl.ocks.org/anonymous/raw/13ce2445b248fb9e44dcd33cfc3dff36/dff0c60423927960cab8aaf9e613c2c3ae205808/

Interacting with a tooltip in d3js v4

I'm creating a proof of concept in v4 of D3JS. One of the things I'm trying to do is have a tooltip display when hovering over a data point. I found a good example of this here.
What I now need to do is add a link (or any clickable element) to the tooltip. I created a plunkr based on the example above and added a link to the tooltip. I can't click on the link and the tooltip appears to be below the line-chart as far as z-index goes.
I've tried setting the z-index on the chart and the tooltip to no avail. Can anyone point me in the right direction to sort this?
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 150px;
height: 100px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
var formatTime = d3.timeFormat("%e %B");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Get the data
d3.csv("data.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// add the dots with tooltips
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.close); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(formatTime(d.date) + "<br/>" + d.close + "<br/><a href='www.google.com'>Test it</a>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
});
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
});
</script>
</body>
When creating your tooltip based on the example in the link, you copied its CSS:
div.tooltip {
pointer-events: none;
...
}
The reason we generally set pointer-events to none in a <div> tooltip, as the linked example did, is that we want to get the mouseout event on the element that fired the mouseover (normally to set the tooltip's opacity to zero), and if the tooltip is positioned to close from the element (sometimes even directly over it) the pointer can hover over the div and ruin the mouseout. Besides that, another important reason to set pointer-events to none is that it allows other elements behind the tooltip to get mouseover events, just like if the tooltip was not there.
However, because in your code there is no mouseout, the easier solution here is simply eliminating the pointer-events: none in the CSS. That way the <div> get the click event.
This is the updated plunker: https://plnkr.co/edit/xfa8cjQd3tHYNu0dUla4?p=preview

D3.js tool tips on hover

I,m following this tutorial for tool tips for my graph :
http://bl.ocks.org/d3noob/c37cb8e630aaef7df30d
and its working like a charm !
However there is one issue...
The current graph showing in the tutorial has black dots on the line...
I want the tool tips and black dots to appear only when i hover on them and not always like it is currently showing.
Is there a way to do that ?
Updated answer with mouseout:
https://plnkr.co/edit/9Ej1MYpGqxBdeWO2FUNO?p=preview
.on("mouseover", function(d) {
// show circle selected
d3.select(this)
.transition()
.duration(200)
.style("opacity", 0.9);
.on('mouseout', function(d) {
// hide the circle
d3.select(this)
.transition()
.duration(100)
.style("opacity", 0);
// hide the tooltip
d3.selectAll(".tooltip")
.transition()
.duration(100)
.style("opacity",0);
To use mouseout, you need to move the tooltip slightly up, and move the whole svg a bit downwards.
div.html(
'<a href= "http://google.com">' + // The first <a> tag
formatTime(d.date) +
"</a>" + // closing </a> tag
"<br/>" + d.close)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 42) + "px"); // up a bit
var margin = {top: 50, right: 20, bottom: 30, left: 50}, // down a bit
Since mouseout is very sensitive, the circle will disappear immediately after you move away your mouse, so it is better to increase the radius a bit:
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 8) // slightly bigger for human reaction
Still, I think without mouseout is a better and more intuitive approach:
Old working example (takes a few seconds to load): https://plnkr.co/edit/IitMgKW0jDYlWifokcZB?p=preview
The changes you need to make is in .on("mouseover", function(d), add the following code:
.on("mouseover", function(d) {
// hide other circles
d3.selectAll('circle')
.style("opacity", 0);
// show circle selected
d3.select(this)
.transition()
.duration(200)
.style("opacity", 0.9);
.on("mouseout", function(d) would not work for this case because the circles overlap with the tooltip.
This is a simple d3 tooltip you can look the code! (It's very little )
https://github.com/cbertelegni/tooltip_d3js/
function tooltipd3(tltp_name){
"use strict";
var s = {};
s.name = tltp_name ? tltp_name : "tooltipd3";
s.w = 0; // width tooltip
s.h = 0; // height tooltip
s.t = d3.select("body").append("div") // tooltip html node
.attr("class", s.name)
.style("opacity", 1e-6)
.style("position", "absolute");
s.mouseover = function(html) {
/** #param {string} html - Is the content for tooltip */
s.t.html(html)
.transition()
.duration(300)
.style("opacity", 1);
/** After innerhtml on tooltip get w & h */
s.get_t_size();
};
s.mousemove = function(){
s.t.style("left", (d3.event.pageX - s.w/2) + "px")
.style("top", (d3.event.pageY - s.h - 5) + "px")
.style("opacity", 1);
};
s.mouseout = function() {
s.t.transition()
.duration(300)
.style("opacity", 1e-6)
.each("end", function(){
s.t.html("");
});
};
/** Get width and height of tooltip and set w & h of Tooltip class */
s.get_t_size = function(){
var size = s.t.node().getBoundingClientRect();
s.w = size.width;
s.h= size.height;
};
return s;
}
As Eric said in the comments, this approach is not exactly user-friendly. But, if you want it, here it is:
First, set the opacity of the circles to 0:
.attr("opacity", 0)
Then, on mousemove:
.on("mouseover", function(d) {
d3.select(this).attr("opacity", 1);
div.transition()//the rest of the code
Don't forget to create a mouse out to make them transparent again:
.on("mouseout", function(d) {
d3.select(this).attr("opacity", 0);

Positioning SVG elements with CSS. Having some problems

Ive added 20 SVG squares to a trend graph I made using D3.js. Ech square is a clickable object which toggles lines on and off the graph. In Firefox the squares appear in the right lace, in Chrome, the CSS apparently stops working completely and the squares just drop to the bottom of the page.
Heres a link to the graph, http://www.andkensol.com/dataviz/TrendGraph/trendMov.html
And here is some of the JavaScript and CSS I used to make and then position the squares. Using position:relative is probably the cause. Would anyone have any advice or suggestions?
//JS
//AVENGERS CONTROLS
d3.select(".chart").append("svg")
.attr("class", "AvengeDot")
.on("mouseover", function(d) {
nameInfo.transition()
.duration(100)
.style("opacity", .9);
nameInfo.html("Marvel's The Avengers")
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
nameInfo.transition()
.duration(200)
.style("opacity", 0);
})
//HUNGER GAMES CONTROLS
d3.select(".chart").append("svg")
.attr("class", "HungerDot")
.on("mouseover", function(d) {
nameInfo.transition()
.duration(100)
.style("opacity", .9);
nameInfo.html("Hunger Games")
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 30) + "px");
})
.on("mouseout", function(d) {
nameInfo.transition()
.duration(200)
.style("opacity", 0);
})
//CSS
.AvengeDot{
position:relative;
left:77%;
bottom:118%;
width:15px;
height:15px;
background-color:#FF9604;
cursor:pointer;
}
.HungerDot{
position:relative;
left:75.5%;
bottom:114%;
width:15px;
height:15px;
background-color:#FF9F18;
cursor:pointer;
}

Categories