How to implement a transition duration using D3.js? - javascript

A text box should pop up upon mouseenter. It currently works, however there's no gradual transition, its instantaneous.
Here's my code so far:
let svgTarget = document.querySelector('svg #target' + newId);
let tr = d3.transition()
.duration(5000)
.ease(d3.easeLinear);
svgTarget.addEventListener('mouseenter', function (e) {
d3.select('svg #tooltip' + newId)
.transition(tr)
.duration(8000)
.ease(d3.easeLinear)
.style("display", "inline");
},)
The mouseenter functionality works and displays the box but again it is instantaneous. Any ideas or suggestions? Thanks!

To add a listener, use selection.on().
Like a lot of things in D3, event listeners act on selections:
d3.select('svg #tooltip' + newId)
.on('mouseenter', function() {
d3.select(this) // this being the prior selection
.transition(tr)
.duration(8000)
.ease(d3.easeLinear)
.style("display", "inline");
});

Related

d3.js hiding popups with opacity not working properly with pointer events

I have a map in d3 with points overlaid. I want the user to be able to click the points and bring up a popup that will be scrollable. Then when user clicks off (anywhere on the body) I want the popup to disappear. The same is true for mouseover - show popup on mouseover, hide on mouseout.
Here’s the website : https://shmoss.github.io/Town_Sounds/#
My issue:
My code works absolutely fine on desktop, as well as my iPhone 11. However, when testing on other iPhones, if I open a popup, click off, and then re-click on another one, the popup is frozen. It doesn't scroll, i.e. it appears pointer events are disabled. Video at bottom.
Here's my code, tried to make as concise as possible:
//build d3 events (circles)
var events = mapG.selectAll("circle")
.data(eventArray)
.enter().append("circle")
.style("class", 'events')
.on("mouseover", function(d) {
//add popup - set opacity to make visible
LeafletDiv.transition()
.duration(200)
.style("opacity", .9)
.style("scrollTop", 0)
var popInfo = '<br>' + d.Venue + '<br>'
LeafletDiv
.html(popInfo)
.style("top", "1.5vh")
.style("text-align", 'left')
}
//on-click event
.on("click", function(d) {
$('body').css({
overflow: 'hidden'
});
//disable hover event listeners
d3.selectAll(".events").on("mouseout", null);
d3.selectAll(".events").on("mouseover", null);
//add popup
var value2014 = currentMap.get(d.location);
LeafletDiv.transition()
.duration(200)
.style("opacity", .9);
selections = d3.selectAll(".events").filter(function(d){
return d.Date == this_date
})
//populate html for popup
var appendText = []
selections.each(function(d){
var popInfo = '<br>' + d.Venue + '<br>'
appendText.push(popInfo+ '<br/>' + '<br/>')
})
//append html to popup
LeafletDiv
.html( appendText.join(""))
.style("top", "1.5vh")
.style("text-align", 'left')
.style("pointer-events", 'auto')
$('.county2014Tooltip').scrollTop(0);
d3.event.stopPropagation();
// if user clicks a SECOND time, anywhere, make popup disappear
d3.select("body").on("click", function(d) {
console.log("clicking off popup")
//hide popup
var elements = d3.select(LeafletDiv)
elements.scrollTop = 0
LeafletDiv.transition()
.duration(200)
.style("opacity", 0)
.style("pointer-events", 'none')
.attr("scrollTop", 0)
//revert back to hover, unless user clicks again!
d3.selectAll(".events").on("mouseout", true);
d3.selectAll(".events").on("mouseover", true);
d3.selectAll(".events").on("mouseout", function(d) {
//mousing out, hide popup!
LeafletDiv.transition()
.duration(200)
.style("opacity", 0);
})
// mouseover event listers added back in
d3.selectAll(".events").on("mouseover", function(d) {
LeafletDiv.transition()
.duration(200)
.style("opacity", .9);
LeafletDiv .html('<br>' + d.Venue + '<br>'
)
.style("top", "1.5vh")
.style("text-align", 'left')
})
})
})
//on mouseout, hide popup
.on("mouseout", function(d) {
LeafletDiv.transition()
.duration(200)
.style("opacity", 0)
.style("scrollTop", 0)
})
Videos documenting the behavior:
An iPhone 11 (same exact code, but working as expected):
https://www.youtube.com/watch?v=_3MA4bJYiYM
Other iPhone 11 (same exact code, not working:)
https://www.youtube.com/watch?v=OfbboDnIw1E
What I've tried:
For some reason, using transform scale(0) with a duration of 200 ms works. As an alternative to the current method of opacity, this work. But it looks unprofessional and I'm baffled why the above code doesn't work universally.
If you are using Leaflet and need only circles and popup, you can use circleMarker and popup from leaflet itself. It maybe simpler to implement than d3.
https://leafletjs.com/reference-1.7.1.html#circlemarker
https://leafletjs.com/reference-1.7.1.html#popup
You can get all the styles/transitions that you need using css.

How to add a link inside a D3 element?

So I am trying to add links on a radial calendar using D3, where each day on the calendar contains a link that would show more details about that specific day. I am using this calendar as a base: http://jsfiddle.net/dmann99/q63WN/
For example, let's say I want to add a link to "google.com" on the days that are filled in (weekends).
// Draw faint arcs for each day (weekends filled, else outlined).
vis.selectAll("g.AllDays")
.data(dates)
.enter().append("svg:g")
.attr("class", "AllDays")
.attr("transform", "translate(" + r1 + "," + r1 + ")")
.append("svg:path")
.attr("stroke", function(d, i) { return d3.hsl(0,0.25,0.75) })
.attr("fill", function(d, i) {
return (d.getDay()==5||d.getDay()==6)?"#cccccc":"#ffffff";
})
.attr("d", arc)
;
Is there a way for me to add a link on the specific days that are filled in and not the whole calendar?
I tried adding something like this, but it didn't work:
.on("click", function() { window.open("http://google.com"); });
Any help is appreciated.
the pointer-events style property seems to be the key here (i.e. it doesn't work without it)
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointer-events
.filter (function(d) { return d.getDay() ==5 || d.getDay() ==6; })
.on("click", function() { window.open("http://google.com", "_blank"); })
css rule needed:
.WeekLine {
pointer-events: none
}
It was originally working with just the javascript change above when clicking weekend nodes around the edge of the display, but the .WeekLine circles were intercepting the mouse events for clicks in the interior of the display.
http://jsfiddle.net/q63WN/5/
(so pointer-events was still the thing to fiddle with, I just focused on the wrong elements to start with...)

How to stop d3.drag triggering mouseover/mouseout events in chrome

I have the following d3 visualisation. The darker colour at the top indicates that a node has been selected. When I mouseover a non selected node it changes opacity so a user can see which node would be selected if I click.
This is achieved via a CSS style sheet and the following js/d3:
nodeSelection.select("circle").on('mouseover', function(e) {
d3.select(this).classed("hover", true);
_this.fireDataEvent("mouseOverNode", this);
});
nodeSelection.select("circle").on('mouseout', function(e) {
d3.select(this).classed("hover", false);
_this.fireDataEvent("mouseOutNode", this);
});
So, far, so good. However, when I drag, the drag function seems to randomly trigger mouse over and mouse out events on the nodes that I am not dragging. This causes the node opacity to flicker. If I watch on the development tools in chrome I can see that this is because it is causing nodes to gain the class "hover". The code above to add this CSS class appears nowhere else, and by use of the console logging, I have confirmed that mouseover and mouseout events are being fired. These nodes are often far from the cursor.
This issue does not occur in Firefox.
UPDATE: I actually managed to fix this almost immediately after posting this. I just explicitly de-register the listeners inside drag start, and re register in drag end. It might still be interesting to some people if they are having similar issues.
My drag function now looks like:
var drag = d3.behavior.drag()
.on("dragstart", function(d) {
console.log("dragstart");
d.dragstart = d3.mouse(this); // store this
d.fixedbeforedrag = d.fixed;
d.fixed=true;
// deregister listeners
nodeSelection.select("circle").on("mouseover", null).on("mouseout", null);
})
.on("drag", function(d) {
d.px = d.x; // previous x
d.py = d.y;
var m = d3.mouse(this);
d.x += m[0] - d.dragstart[0];
d.y += m[1] - d.dragstart[1];
nodeSelection.attr("transform", "translate(" + [d.x, d.y] + ")");
_this.getForce().start();
})
.on("dragend", function(d) {
console.log("dragend");
delete d.dragstart;
d.fixed = d.fixedbeforedrag;
//reregisters listeners
_this.updateSVG();
});

Adding OnClick event to a bar chart

This is all the code that I have, there is something wrong with the "on click" part that I couldn't figure out.
It errors and says "drill not defined"
Isn't it the way we can call a method on click event of one of those bar charts that I am drawing in the D3 section?
$( document ).ready(function() {
gon.data.pop(); // get rid of query_time element.
var dataset = gon.data;
function drill(d, i) {
console.log(d.total_count); //data object
var url = "http://localhost:4567/drill/" + d.brand_name + "/" + d.generic_name;
window.location.href = url;
}
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.attr("onclick", "drill()")
.style("height", function(d) {
return d.brand_name + "px";
});
});
To add event listeners to selections you should use selection's on method.
You can write
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.on("click", drill)
.style("height", function(d) {
return d.brand_name + "px";
});
Move your declaration of the drill function to outside of your document-ready handler.
Better still, use the on method that d3 provides, rather than the onclick attribute.
you can also use jQuery .click() if you want, here's an example
$("#test").click(function(){
sayHello();
});
jsfiddle here:
http://jsfiddle.net/SGRmX/1/

D3: d3.behavior.drag

I'm trying this example and I applied the d3.behavior.drag with the function
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
})
});
Please see my example here.
My problem is after dragging the svg.
When I click on an element the zoom isn't well apllied.
For instance, the root disappear...
How can I fix this situation?
Thanks,
Carlos.
The problem is that when you try to drag the elements the click event is also fired and both the event handlers are getting executed.
You need to ignore the click event if it was suppressed (i.e. when you are dragging).
Modify your click event handler as
function click(d) {
// Ignore the click event if it was suppressed
if (d3.event.defaultPrevented){
return;
}
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
};
Given in your case that an element can be dragged and clicked, in addition to prashant's answer, you'll want to suppress the click on drag as follows:
drag.on("dragstart", function() {
d3.event.sourceEvent.stopPropagation(); // silence other listeners
});
See http://bl.ocks.org/mbostock/6123708 for an example :-)
use this,
d3.event.sourceEvent.stopPropagation();

Categories