I like to use a div position:absolute as a tooltip for my d3.js charts as seen below:
<style>
.d3-tip {
display:inline-block;
position: absolute;
pointer-events:none;
}
</style>
<body>
<script src='https://d3js.org/d3.v5.min.js'></script>
<script type="text/javascript">
var tooltip = d3.select("body")
.append("div")
.attr("class", "d3-tip")
.style("opacity", 0);
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 800);
svg.append("circle")
.attr("cx", 100)
.attr("cy", 300)
.attr("r", 45)
.style("fill", "gray")
.on("mousemove", function() {
tooltip.style("opacity", .9)
.style("left", d3.event.pageX + 40 + "px")
.style("top", d3.event.pageY - 40 + "px")
.html("this is a tooltip");
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
})
.on("click", function() {
d3.select(this).style("fill", "green");
});
</script>
</body>
This shows a tooltip when the cursor is over the circle, and also changes the color of the circle when it is clicked. It works with touches too (except that I can't get the tooltip to disappear, but that's another question). My problem, is that if there are any html tags in the tooltip, the touch does not seem to register as a click touchscreens.
For some reason I can't replicate this problem on codepen or jsfiddle. It works when I try it there. But here's a page with a live example that fails on my iPhone in Chrome and Safari: https://datavis.blob.core.windows.net/lmi/tooltip%20example.html
Related
I have a created a stacked and grouped bar chart with react + d3 and i want to add tooltip, i have tried different ways but none of them did work. the code for tooltip looks like:
const tooltip = select(svgRef.current)
.append("div")
.style("position", "absolute")
.style("visibility", "visible")
.style("background-color", "black")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.html("<p>I'm a tooltip written in HTML</p>");
svg
.select(".barreact")
.on("mouseover", function () {
return tooltip.style("visibility", "visible");
})
.on("mousemove", function () {
return tooltip
.style("top", event.pageY + "px")
.style("left", event.pageX + "px");
})
.on("mouseout", function () {
return tooltip.style("visibility", "hidden");
});
you can check the full code and demo in demo
any help to make it work will be appreciated
Use SVG foreignObjects to append HTML to an SVG:
const tooltip = select(svg)
.append("foreignObject")
.attr("width", 180);
const tooltipDiv = tooltip
.append("xhtml:div")
.html("<p>I'm a tooltip written in HTML</p>");
Note that we only set the width. The height is calculated dynamically by looking at the height of the div with the width set: you can also do the opposite, setting an height and introspecting for the width:
bars.on("mousemove", function () {
const htmlContentHeight = tooltipDiv.node().getBoundingClientRect()
.height;
tooltip.attr("height", htmlContentHeight);
tooltip.raise();
return tooltip
.attr("x", event.pageX)
.attr("y", event.pageY - htmlContentHeight);
})
Since SVG has no z-index, we must put the last element at the top of the SVG, and we can do so with selection.raise.
CodeSandbox
I am trying to add a tooltip to the d3 Choropleth map with hover effect from this page https://www.d3-graph-gallery.com/graph/choropleth_hover_effect.html
The tooltip seems to be appendend to the svg, as I can inspect the map and see an inner div with the suppossed text that should appear when hovering the countries, but when doing so, nothing shows up, even though the tooltip parameters seem to be updated on each different hover.
I've taken the tooltip from another graph as the current map I am trying to implement does not have tooltip. I've tried creating the tooltip variable within function ready(error, topo)and the styles/data inside the mouseover and mouseleave functions but it does not display the tooltip over the countries
Here is the coode in my xhtml
<head>
<!-- Load d3.js and the geo projection plugin -->
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script>
jQuery(document).ready(function() {
// The svg
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
// Map and projection
var path = d3.geoPath();
var projection = d3.geoMercator()
.scale(70)
.center([0,20])
.translate([width / 2, height / 2]);
// Data and color scale
var data = d3.map();
var colorScale = d3.scaleThreshold()
.domain([100000, 1000000, 10000000, 30000000, 100000000, 500000000])
.range(d3.schemeBlues[7]);
// Load external data and boot
d3.queue()
.defer(d3.json, "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson")
.defer(d3.csv, "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world_population.csv", function(d) { data.set(d.code, +d.pop); })
.await(ready);
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
function ready(error, topo) {
let mouseOver = function(d) {
d3.selectAll(".Country")
.transition()
.duration(200)
.style("opacity", .5)
d3.select(this)
.transition()
.duration(200)
.style("opacity", 1)
.style("stroke", "black")
tooltip.style("opacity", 1)
.html("The exact value of<br/>this cell is: " + d.pop)
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
let mouseLeave = function(d) {
d3.selectAll(".Country")
.transition()
.duration(200)
.style("opacity", .8)
d3.select(this)
.transition()
.duration(200)
.style("stroke", "transparent")
tooltip.style("opacity", 0)
}
// Draw the map
svg.append("g")
.selectAll("path")
.data(topo.features)
.enter()
.append("path")
// draw each country
.attr("d", d3.geoPath()
.projection(projection)
)
// set the color of each country
.attr("fill", function (d) {
d.total = data.get(d.id) || 0;
return colorScale(d.total);
})
.style("stroke", "transparent")
.attr("class", function(d){ return "Country" } )
.style("opacity", .8)
.on("mouseover", mouseOver )
.on("mouseleave", mouseLeave )
}
})
</script>
</head>
<div>
<div>
<h1>Graphs</h1>
<!-- Create an element where the map will take place -->
<svg id="my_dataviz" width="400" height="300"></svg>
</div>
</div>
After rendering the map (working with just hovering function) I inspect it and can see the following.
If I change the country, the data on the tooltip div varies.
--UPDATE--
Following #enxaneta 's advice, I tried taking the div outside svg. The result is having the tooltip in a separate div. This time the tooltip appears in the page but is displayed at the bottom of the map, not over it, how I would like to see it.
I am still figuring out how to implement foreignObject.
--UPDATE 2--
Adding tooltip position absolute toke the div and put it relative to the mouse hovering. However, even though it is vertically aligned with the country selected, it is way up at the top of the page.
<style>
.tooltip{position:absolute;}
</style>
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);
This is my first question ever.
So, I tried to run a mouseover in my D3 script to trigger a hovered textfield (which is a div object) when moving the mouse over a certain button. Works pretty well so far.
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
.
.
.
node.append("foreignObject")
.attr("class", "info")
.attr("x", 55)
.attr("y", -85)
.attr("width", 30)
.attr("height", 30)
.append("xhtml:body")
.html('<img src="images/information-icon.png" width=20 height=20>')
.on("mouseover", function (d) {
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html("text")
.style("left", (d3.event.pageX - 5) + "px")
.style("top", (d3.event.pageY - 10) + "px");
})
.on("mouseout", function (d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
The problem now is that the textfield vanishes once I move my mouse away from the button. I somehow want to "extend" my mouseover target from the button to the textfield also, so that I'm still able to click links on it etc. Does someone have an idea how to establish that? I already tried to put the mouseout event on my textfield instead like that:
.on("mouseover", function (d) {
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html("text")
.style("left", (d3.event.pageX - 5) + "px")
.style("top", (d3.event.pageY - 10) + "px")
.on("mouseout", function (d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
This won't work though, as the textfield won't vanish at all then. Does someone have an idea?
**EDIT:**Defined my question a bit different, I hope it won't be that confusing now...
Ok,
when the text is displayed you should un-register the mouse over event from the button, so that the text does not move when you run your mouse over the div.
When the text is opacity 0 then register the mouse over event back to the div.
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;
}