Why Tooltip is not moving with mouse? - javascript

This is a code for making pie chart.
I have added 2 functionalities to pie chart. First one is to increase the arc size when mouse hovers on it.
Second is that when i hover, tooltip displays y position of mouse and this tooltip moves with mouse.
But problem with me is that. Tooltip Text is not moving with movement of mouse.
I have seen various stack overflow links for this but i am unable to find solution. Below i have also attached links that so that the question is not marked duplicate.
Please do not give me a new code for tooltip. Correct this one.
Below are the links that i have already visited
Why is my D3.js tooltip not working
D3 - Positioning tooltip on SVG element not working
Tooltips not showing on mouse move and mouse over
Link of jsfiddle :- https://jsfiddle.net/uoh1bsrp/
Thanks,
Shubham Sharma
width = 600
height = 600
svg = d3.select('body').append('svg').attr('width',width).attr('height',height).append('g').attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
a = [2,5,7]
pie = d3.pie()
radius = 150
arc = d3.arc().innerRadius(0)
.outerRadius(radius);
arcs = svg.selectAll('arc').data(pie(a)).enter().append('g')
arcs.append("path").attr('d',function(d){return arc(d)})
tooltip = svg.append('text')
color = ['red','green','blue']
path = d3.selectAll('path').style('fill',function(d,i){return color[i]})
path.on('mouseover',handlemouseover)
path.on('mouseout',handlemouseout)
path.on('mousemove',handlemousemove)
function handlemouseover(){
d3.select(this).attr('d',function(d){return d3.arc().innerRadius(0)
.outerRadius(180)(d)});
}
function handlemousemove(){
// svg.append('text').text(function(){return 'shubham'}).style('top',(d3.event.layerY + 10)+'px').style('left',(d3.event.layerX + 10) + 'px')
tooltip.text(function(){return d3.event.layerX}).style('top',(d3.event.layerY + 10)+'px').style('left',(d3.event.layerX + 10) + 'px').style('display','block');
}
function handlemouseout(){
d3.select(this).attr('d',function(d){return d3.arc().innerRadius(0).outerRadius(radius)(d)});
tooltip.style('display', 'none');
}
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js"></script>
<script src="https://d3js.org/d3-shape.v1.min.js"></script>
</head>

That's because you are trying to increase top property of text tag in SVG. This porperty just doesn't work. You can use translate instead of it or just use tags like div or span.
Check the fiddle https://jsfiddle.net/fr71t0o3/3/:
function handlemousemove() {
// svg.append('text').text(function(){return 'shubham'}).style('top',(d3.event.layerY + 10)+'px').style('left',(d3.event.layerX + 10) + 'px')
tooltip
.text(function() {
return d3.event.layerX;
})
.style('transform', `translate(${d3.event.layerX - 300}px, ${d3.event.layerY - 300}px)`)
.style('display', 'block').style('color','red');
}
I'm not sure about offset of tooltip, if you want it moving with the mouse move you can use mouse coordinates.

Instead of .style('left', /*[...]*/) and .style('top', /*[...]*/) you can use .attr('x', [...]) and .attr('y', [...])
Also, you have to substract height/2 from y and width/2 from x, as the x and y attributes move text relative to its parent:
width = 600
height = 600
svg = d3.select('body').append('svg').attr('width',width).attr('height',height).append('g').attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
a = [2,5,7]
pie = d3.pie()
radius = 150
arc = d3.arc().innerRadius(0)
.outerRadius(radius);
arcs = svg.selectAll('arc').data(pie(a)).enter().append('g')
arcs.append("path").attr('d',function(d){return arc(d)})
tooltip = svg.append('text')
color = ['red','green','blue']
path = d3.selectAll('path').style('fill',function(d,i){return color[i]})
path.on('mouseover',handlemouseover)
path.on('mouseout',handlemouseout)
path.on('mousemove',handlemousemove)
function handlemouseover(){
d3.select(this).attr('d',function(d){return d3.arc().innerRadius(0)
.outerRadius(180)(d)});
}
function handlemousemove(){
// svg.append('text').text(function(){return 'shubham'}).style('top',(d3.event.layerY + 10)+'px').style('left',(d3.event.layerX + 10) + 'px')
tooltip.text(function(){return d3.event.layerX}).attr('y',(d3.event.layerY - height/2 + 10)+'px').attr('x',(d3.event.layerX - width/2 + 10) + 'px').style('display','block');
}
function handlemouseout(){
d3.select(this).attr('d',function(d){return d3.arc().innerRadius(0).outerRadius(radius)(d)});
tooltip.style('display', 'none');
}
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js"></script>
<script src="https://d3js.org/d3-shape.v1.min.js"></script>
</head>

You have to append a div and add css styles to it.
Working jsfiddle: https://jsfiddle.net/amp42fjn/
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
path.on("mouseover",handlemouseover)
path.on("mousemove", handlemousemove)
path.on("mouseout", handlemouseout);
function handlemouseover(){
d3.select(this).attr('d',function(d){return d3.arc().innerRadius(0)
.outerRadius(180)(d)});
tooltip.style("visibility", "visible");
}
function handlemousemove(){
tooltip.text(function(){return d3.event.layerX}).style('top',(d3.event.layerY + 10)+'px').style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");
}
function handlemouseout(){
d3.select(this).attr('d',function(d){return d3.arc().innerRadius(0).outerRadius(radius)(d)});
tooltip.style("visibility", "hidden");
}

Related

Stop zooming of bubbles in D3 World Map

I am currently working on a D3 world map in which I have brought in a zoom functionality up-to the boundary level of any country or county based on its click.
I have Added Bubbles pointing the counties in Kenya,which gets enlarged on the zoom functionality that I have added.But I want to stop the zooming of bubbles,on zooming of the Map.
Here is a plunker for my current work.
https://plnkr.co/edit/nZIlJxvU74k8Nmtpduzc?p=preview
And below is the code for zooming and zoom out
function clicked(d) {
var conditionalChange = d;
if(d.properties.hasOwnProperty("Country")){
var country = d.properties.Country;
var obj = data.objects.countries.geometries;
$.each(obj, function(key, value ) {
if(countries[key].properties.name == "Kenya")
{
conditionalChange = countries[key].geometry;
}
});
}
d = conditionalChange;
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = 1.2/ Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1/ scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
g.transition()
.duration(750)
.style("stroke-width", "1px")
.attr("transform", "");
}
You are scaling the entire g element, this effectively zooms the map. Everything will increase in size; however, for the map lines you have adjusted the stroke to reflect the change in g scale factor:
g.transition()
.duration(750)
.style("stroke-width", 1/ scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
To keep the circles the same size, you have to do the same adjustment for your circles by modifying the r attribute for each circle according to the g scale factor:
g.selectAll(".city-circle")
.transition()
.attr("r", 5 / scale )
.duration(750);
Though, since you don't actually apply the class city-circle on your circles you'll need to do that too when you append them:
.attr("class","city-circle")
And, just as you reset the stroke width on reset, you need to reset the circles' r :
g.selectAll(".city-circle")
.transition()
.attr("r", 5)
.duration(750);
Together that gives us this.

D3.js draw each point at a time

I'm building a map plotting tool with D3. I'm using this example.
Everything works but I want to draw each point with a 10ms difference, like its drawing.
I tried to make an interval but didn't work. I also was thinking to make css animation and each point to have an animation-delay but that doesn't seem work well.
Can someone explain to me how to draw the data one by one?
function redrawSubset(subset) {
var radius = 2;
var bounds = path.bounds({ type: 'FeatureCollection', features: subset });
var topLeft = bounds[0];
var bottomRight = bounds[1];
var start = new Date();
var points = g.selectAll('path')
.data(subset, function(d) {
return d.id;
});
path.pointRadius(radius);
svg.attr('width', bottomRight[0] - topLeft[0] + radius * 2)
.attr('height', bottomRight[1] - topLeft[1] + radius * 2)
.style('left', topLeft[0] + 'px')
.style('top', topLeft[1] + 'px');
g.attr('transform', 'translate(' + (-topLeft[0] + radius) + ',' + (-topLeft[1] + radius) + ')');
points.enter().append('path');
points.exit().remove();
points.attr('d', path);
}
It is possible to render circle by circle, but it's a little complicated. Maybe a workaround is to draw all of them transparent, and setting the opacity to 1 with a delay of 10ms:
points.enter().append("path").attr("opacity", 0)
.transition()
.duration(10)
.delay(function(d,i){ return i*10})
.attr("opacity", 1);
Here is your plunker: http://plnkr.co/edit/ys4VofukQOvA4pY0TQX7?p=preview

Finding width of div hidden by CSS

I have a div tooltip that I would like to centre over SVG circles created using D3. My mouseover event triggers the div's 'hidden' class to false.
Plunker
Because the div is hidden, I am unable to calculate it's width and am therefore unable to centre the div on the circle the way I would like:
var width = document.getElementById("tooltip").offsetWidth;
...
.style("left", xPosition - (width/2) + "px" )
Is there a way I can get around this?
Its probably worth noting that I don't want the div to have a fixed width.
var w = 300, h = 500;
var svg = d3.select('body').append('svg').attr('width', w).attr('height', h)
svg.append('circle').attr('r', 20)
.attr('cx', 200)
.attr('cy', 300)
.on("mouseover", function(d) {
var xPosition = parseFloat(d3.select(this).attr("cx"));
var yPosition = parseFloat(d3.select(this).attr("cy"));
var width = document.getElementById("tooltip").offsetWidth;
d3.select("#tooltip")
.style("left", xPosition - (width/2) + "px" )
.style("top", yPosition + "px")
.select("#value")
.text(d);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
d3.select("#tooltip").classed("hidden", true);
})
You have to first give the div a display value other than "none". Then you get its width. Then you can position it.
If you don't want the user to see the div jump, give it the style visibility: hidden while you're positioning it:
CSS:
.hidden {
display: none;
visibility: hidden;
}
JS:
d3.select("#tooltip").style("display", "block");
// your div is still invisible, but now has a width.
// get its width and position it now
d3.select("#tooltip").classed("hidden", false); // now it will appear
If you hide it again, don't forget to reset the display style as well!

Rotating pie chart using d3.js [duplicate]

I am looking for an example for to rotate a pie chart on mouse down event. On mouse down, I need to rotate the pie chart either clock wise or anti clock wise direction.
If there is any example how to do this in D3.js, that will help me a lot. I found an example using FusionChart and I want to achieve the same using D3.js
Pretty easy with d3:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.age);
});
var curAngle = 0;
var interval = null;
svg.on("mousedown", function(d) {
interval = setInterval(goRotate,10);
});
svg.on("mouseup", function(d){
clearInterval(interval);
})
function goRotate() {
curAngle += 1;
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ") rotate(" + curAngle + "," + 0 + "," + 0 + ")");
}
Working example.
I did a similar thing with a compass instead of pie chart. You mainly need three methods - each bound to a different mouse event.
Bind this to the mousedown event on your compass circle:
function beginCompassRotate(el) {
var rect = compassCircle[0][0].getBBox(); //compassCircle would be your piechart d3 object
compassMoving = true;
compassCenter = {
x: (rect.width / 2),
y: (rect.height / 2)
}
}
Bind this to the mouse move on your canvas or whatever is holding your pie chart - you can bind it to the circle (your pie chart) but it makes the movement a little glitchy. Binding it to the circle's container keeps it smooth.
function rotateCompass() {
if (compassMoving) {
var mouse = d3.mouse(svg[0][0]);
var p2 = {
x: mouse[0],
y: mouse[1]
};
var newAngle = getAngle(compassCenter, p2) + 90;
//again this v is your pie chart instead of compass
compass.attr("transform", "translate(90,90) rotate(" + newAngle + "," + 0 + "," + 0 + ")");
}
}
Finally bind this to the mouseup on your canvas - again you can bind it to the circle but this way you can end the rotation without the mouse over the circle. If it is on the circle you will keep rotating the circle until you have a mouse up event over the circle.
function endCompassRotate(el) {
compassMoving = false;
}
Here is a jsfiddle showing it working: http://jsfiddle.net/4oy2ggdt/

D3js Geo unzoom element when zoom map

I have a D3js map built with topojson.js.
var projection = d3.geo.mercator();
Everything works fine, but I am looking for an effect that I cannot manage to achieve. When, zooming the map I would like the pins over it to scale down, But if I scale it down, I need to recalculate their coordinates and I can't find the formula to do so.
Zoom handler
scale = d3.event.scale;
if (scale >= 1) {
main.attr("transform", "translate(" + d3.event.translate + ")
scale(" + d3.event.scale + ")");
}
else {
main.attr("transform", "translate(" + d3.event.translate + ")scale(1)");
}
//43x49 is the size initial pine size when scale = 1
var pins = main.select("#pins").selectAll("g").select("image")
.attr("width", function () {
return 43 - (43 * (scale - 1));
})
.attr("height", function () {
return 49 - (49 * (scale - 1));
})
.attr("x", function () {
//Calculate new image coordinates;
})
.attr("y", function () {
//Calculate new image coordinates;
});
My question is : how do I calculate x and y based on new scale?
I hope I am clear enough.
Thanks for your help
EDIT :
Calculation of initial pins coordinates :
"translate(" + (projection([d.lon, d.lat])[0] - 20) + ","
+ (projection([d.lon, d.lat])[1] - 45) + ")"
-20 and -45 to have the tip of the pin right on the target.
You need to "counter-scale" the pins, i.e. as main scales up/down you need to scale the pins down/up, in the opposite direction. The counter-scale factor is 1/scale, and you should apply it to each of the <g>s containing the pin image. This lets you remove your current width and height calculation from the <image> nodes (since the parent <g>'s scale will take care of it).
However, for this to work properly, you'll also need to remove the x and y attributes from the <image> and apply position via the counter-scaled parent <g> as well. This is necessary because if there's a local offset (which is the case when x and y are set on the <image>) that local offset gets scaled as the parent <g> is scaled, which makes the pin move to an incorrect location.
So:
var pinContainers = main.select("#pins").selectAll("g")
.attr("transform", function(d) {
var x = ... // don't know how you calculate x (since you didn't show it)
var y = ... // don't know how you calculate y
var unscale = 1/scale;
return "translate(" + x + " " + y + ") scale(" + unscale + ")";
})
pinContainers.select("image")
.attr("width", 43)
.attr("height", 49)
// you can use non-zero x and y to get the image to scale
// relative to some point other than the top-left of the
// image (such as the tip of the pin)
.attr("x", 0)
.attr("y", 0)

Categories