d3.js text events registered but not triggering - javascript

I have the following enter section which is transitioned later to set the opacity to 1. The 'click' on the circle works. The 'click' on the text does not.
If I substitute the 'text' for a 'rect' and set the appropriate attributes on the rect then both clicks function correctly.
When I inspect the dom tree, the listeners are correctly visible on both the circle and the text elements.
Could somebody possibly point out any obvious mistakes or tell me why using 'text' won't work with a listener?
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr('transform', function () {
return 'translate(' + source.y0 + ',' + source.x0 + ')';
})
.style('opacity', 1e-6);
nodeEnter.append('circle')
.attr('r', 1e-6)
.style('fill', function (d: any) {
return d._children ? 'lightsteelblue' : '#fff';
})
.on('click', this.circle_click);
nodeEnter.append('text')
.attr('dx', 3.5)
.attr('dy', 5.5)
.text(function (d: any) { return d.data.name; })
.style('fill-opacity', 1e-6)
.on('click', this.text_click);

Discovered that for some reason 'pointer-events "none"' was always set on the element - when I specifically set the style to "visible" during the update transition then it worked. Would be interesting to know why this happened - the style certainly wasn't set in either my code or css files.

Related

Different Tooltip for each SVG circle on D3 Map

I am making a world map of businesses linked to their performance rating: so each business will be represented by a point, that has a tooltip with the performance (and other info.) I'm using the map example here
The Map Data:
pointData = {
"businessName": businessName,
"location": location,
"performance": currperformance
}
pointsData.push(pointData);
Therefore the pointsData JSON object is of the form
[{"business":"b1","location":[long1, lat1]},{"businessName":"b2","location":[long2, lat2]}...]
The Tooltip Problem:
I can display the map with points and the same tooltip perfectly until I try to have differing tooltips. Many D3 examples that I've researched with dynamic tooltips are only applicable to charts - and my struggle is to append the JSON data for the tooltip on each SVG circle on a map.
Here is my attempt thus far, which displays no points and shows no console errors (Adding in the .each(function (d, i) {..}doesn't draw the parts anymore but it's necessary for linking each location to it's subsequent business and performance rating.)
d3.json("https://raw.githubusercontent.com/d3/d3.github.com/master/world-110m.v1.json", function (error, topo) {
if (error) throw error;
gBackground.append("g")
.attr("id", "country")
.selectAll("path")
.data(topojson.feature(topo, topo.objects.countries).features)
.enter().append("path")
.attr("d", path);
gBackground.append("path")
.datum(topojson.mesh(topo, topo.objects.countries, function (a, b) { return a !== b; }))
.attr("id", "country-borders")
.attr("d", path);
//Tooltip Implementation
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style('opacity', 0)
.style('position', 'absolute')
.style('padding', '0 10px');
gPoints.selectAll("circle")
.each(function (d, i) {
this.data(pointsData.location).enter()
.append("circle")
.attr("cx", function (d, i) { return projection(d)[0]; })
.attr("cy", function (d, i) { return projection(d)[1]; })
.attr("r", "10px")
.style("fill", "yellow")
.on('mouseover', function (d) {
tooltip.transition()
.style('opacity', .9)
.style('background', 'black')
.text("Business" + pointsData.businessName + "performance" + pointsData.performance)
.style('left', (d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 30) + 'px')
})
.on('mouseout', function (d) {
tooltip.transition()
.style("visibility", "hidden");
})
});
});
The enter selection does what you are trying to do without the use of .each(). Bostock designed D3 to join data to elements so that:
Instead of telling D3 how to do something, tell D3 what you want. You
want the circle elements to correspond to data. You want one circle
per datum. Instead of instructing D3 to create circles, then, tell D3
that the selection "circle" should correspond to data. This concept is
called the data join (Thinking with Joins).
I suggest that you take a look at some examples on the enter, update, and exit selections. Though, it is possible that you were originally doing this with the plain circles (and identical tooltips):
svg.selectAll("circle")
.data([[long,lat],[long,lat]])
.enter()
.append("circle")
.attr("cx", function(d,i) { return projection(d)[0]; })
.attr("cy", function(d,i) { return projection(d)[1]; })
If you were, then it is just a matter of accessing the datum for additional properties. If not, then it is a matter of properly using an enter selection.
In any event, here is a possible implementation using an enter selection and the data format you specified:
var pointsData = [
{ "businessName": "Business A",
"location": [50,100],
"performance": "100" },
{ "businessName": "Business B",
"location": [100,50],
"performance": "75"
},
{ "businessName": "Business C",
"location": [150,150],
"performance": "50"
}];
var svg = d3.select("body")
.append("svg")
.attr("width",300)
.attr("height",300);
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style('opacity', 0)
.style('position', 'absolute')
.style('padding', '0 10px');
svg.selectAll("circle") // empty selection
.data(pointsData) // data to bind to selection
.enter() // the enter selection holds new elements in the selection
.append("circle") // append a circle for each new element
// Manage the style of each circle based on its datum:
.attr("cx",function(d) { return d.location[0]; })
.attr("cy",function(d) { return d.location[1]; })
.attr("r",10)
.on("mouseover", function(d) {
tooltip.transition()
.style('opacity', .9)
.style('background', 'steelblue')
.text("Business: " + d.businessName + ". performance " + d.performance)
.style('left', (d3.event.pageX - 35) + 'px')
.style('top', (d3.event.pageY - 30) + 'px')
.duration(100);
})
.on("mouseout",function(d) {
tooltip.transition()
.style("opacity", "0")
.duration(50);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
The original selection svg.selectAll("circle") is empty. When binding data to this empty selection, the enter selection will hold one item for each item in the data array that does not have a corresponding element in the selection (in this case, a circle, and since there are none, the enter array has one item for each item in the data array). We then append one element for each item in the enter selection, and stylize it based on the properties of each datum.
Note that this required a few modifications from your original code (I've also skipped a projection to make it a more concise snippet).
Accessing the datum's properties:
I imagine that your initial dataset looked like this: [[long,lat], [long,lat], [long,lat]]. When accessing each datum from this dataset, you could center a circle like:
.attr("cx", function(d,i) { return projection(d)[0]; })
.attr("cy", function(d,i) { return projection(d)[1]; })
Those are the lines you used above in your example. However, your datum now looks like your variable pointData in your example code, so you need to modify it to look like:
.attr("cx", function(d,i) { return projection(d.location)[0]; })
.attr("cy", function(d,i) { return projection(d.location)[1]; })
I've also accessed the appropriate property of each datum for the text of the tooltip, rather than accessing data that is not bound to each element (even if it comes from the same source).
Opacity vs Visibility:
You set the opacity of the tooltip to be zero initially, then modify it to be 0.9 on mouseover: .style('opacity', .9), rather than toggling visibility (which only the mouseout function does) change opacity back to zero on mouseout.

Why is my tooltip not showing up?

I have made a map with D3 and using some data from nasa.gov(https://data.nasa.gov/resource/y77d-th95.geojson)
Here is the codepen
http://codepen.io/redixhumayun/full/VPepqM/
I have tried making a tooltip with the following code.
//setting up the tooltip here
var div = svg.append('div')
.attr('class', 'tooltip')
.style('opacity', 0.7);
var meteorites = meteorite.selectAll('circle')
.data(data.features)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.properties.reclong, d.properties.reclat])[0]
})
.attr('cy', function(d) {
return projection([d.properties.reclong, d.properties.reclat])[1]
})
.attr('fill', function(d) {
return color_scale(d.properties.mass)
})
.attr('stroke', 'black')
.attr("stroke-width", 1)
.attr('r', function(d) {
return weight_scale(d.properties.mass);
})
.attr('fill-opacity', function(d) {
if (weight_scale(d.properties.mass) > 7) {
return 0.5
}
return 1;
})
.on('mouseover', function(d) {
div.transition().duration(200)
.style('opacity', 0.9)
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY / 1.5) + 'px')
div.html('<p>Please show up</p>');
}).on('mouseout', function(d){
div.transition().duration(200)
.style('opacity', 0);
})
However, the tooltip does not show up. I even tried changing the z-index of the tooltip to be greater than that of the underlying map so that it wouldn't be hidden by the map, but no luck.
When I inspect the tooltip in the elements inspector, it shows that the style, left and top attributes of the tooltip div are changing, but I can't seem to see it on the screen. Not sure what I'm doing wrong here.
You have three problems here:
First, set the position of the <div> to absolute in the CSS:
position: absolute;
Second, the biggest problem: you cannot append a <div> to an SVG. The good news is that you don't need to (since we just set the tooltip div to an absolute position). So, append the div to the body:
var div = d3.select("body")
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0.7);
Third problem: set the pointer-events to none or move the tooltip a little bit to the right, otherwise it will get in the way of your mouseover event:
.style('left', d3.event.pageX + 10 + 'px')
This is your updated CodePen: http://codepen.io/anon/pen/GrqKBY?editors=0110
http://codepen.io/anon/pen/YNWKpr
var div = svg.append('foreignObject').append('xhtml:div')
.attr('class', 'tooltip')
.style('opacity', 0.7);
You have to wrap non-svg elements in a foreignObject tag, and you have to specify the html namespace when appending html elements.

Wrapping path elements in an anchor tag

I am trying to make the <text> and <path> elements in a donut chart I have in d3 clickable, but am running into some issues. Here is the code I am using:
var g = svg.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + ((height / 2)) + ')');
var arcs = g.selectAll('path');
arcs = arcs.data(pie(formattedData));
arcs.exit().remove();
arcs.enter().append('path')
.style('stroke', 'white')
.attr('fill', function (d, i) { return color(i) });
arcs.attr('d', arc);
arcs.append("text")
.attr("transform", function (d) { return "translate(" + labelArc.centroid(d) + ")"; })
.attr("dy", "0em")
.attr("style", "font-size: 1.5em;")
.attr("fill", "white")
.text(function (d) { return d.value; })
I can add the <a> tag around the <path> and <text> elements by editing the DOM directly in dev tools, but I can't do a .wrap() or change the code to use this either:
.append("a").attr("href", "http://wwww.gooogle.com")
What am I missing?
You should be able to do this just by appending the a element before the path or text element:
arcs.enter()
.append('a')
.attr('href', 'http://www.google.com')
.append('path')
.style('stroke', 'white')
.attr('fill', function (d, i) { return color(i) })
.attr('d', arc);
See https://jsfiddle.net/m4mj8u7L/1/
It looks like you're trying to edit SVG DOM elements with jQuery. If so, I ran into this problem myself not long ago. SVG elements aren't treated the same as normal HTML elements, and thus many of the jQuery functions won't affect them. There's some alternate syntax that you can use to work around the limitations though. Look at this answer for some direction.

Update d3 body text

How do I best update a body text element from a SVG tree node mouseover event? When I try the following the text is updated, but the SVG is removed from the display. Here is a the code:
var svg = d3.select('body')
.append('text').text('The Entry Point and M Code: ')
.attr('class', 'centralText')
.attr('x', 10)
.attr('y', 10)
.attr('text-anchor', 'middle')
.append('svg')
here is my event code:
var nodeEnter = node.enter().append('g')
.attr('class', node_class)
.attr('transform', function(d) {
return 'translate(' + source.x0 + ',' + source.y0 + ')'; })
.style('cursor', function(d) {
return (d.children || d._children) ? 'pointer' : '';})
.on('click', click)
.on("mouseover", function(d) {
d3.select('body')
.text('M Code: is this')
There are two separate issues that are coming up. First, your initialization of svg is appending it to a text element, which is a bad idea. Second, to update the text you're replacing the body text and not the text element.
Two changes need to be made. First, change your mouseover function to this:
d3.select('body').select('text')
.text('M Code: is this');
This would normally fix it, but the second problem is that the svg is appended to the text element so it will still be erased. To fix this, change your declaration to the following:
d3.select('body')
.append('text').text('The Entry Point and M Code: ')
.attr('class', 'centralText')
.attr('x', 10)
.attr('y', 10)
.attr('text-anchor', 'middle');
var svg = d3.select('body').append('svg');

D3 enable mouseover AFTER transition

How can I add a d3-tip / mouseover event AFTER a transition on a histogram / bar chart?
I create a bar chart / plot:
canvas.call(tip);
var sampleBars = canvas.selectAll(".sampleBar")
.data(data)
.enter().insert("rect", ".axis")
.attr("class", "sampleBar")
.attr("x", function(d) { return x(d.x) + 1; })
.attr("y", function(d) { return y(d.y); })
.attr("width", x(data[0].dx + data[0].x) - x(data[0].x) - 1)
.attr("height", 0)
.transition()
.duration(2500)
.delay(500)
.attr("height", function(d) { return height - y(d.y); });
I want to add:
sampleBars
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
And this is the tip:
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<span style='color:white'>" + d3.round((d.y * 100))
+ "%" + "</span>" + " infected"; })
So that the mouseover event occurs after the transition is complete. But I get the error:
Uncaught TypeError: undefined is not a function
The error is for the line:
.on('mouseover', tip.show)
I think there is a simple flaw in my logic. I seem to be confused about how these events or attributes interact with each other.
Again: I want to 'activate' the mouseover tip AFTER the transition is complete so that after the transition does its thing the tip will appear if the user puts their mouse over each bar. I have no problem creating the mouseover event and having it work on user mouseover to display the data I want, but I am having no luck with making this work with a transition of those bars.
Instead of adding/removing events, one approach is to simply set/unset the pointer-events attribute so that the events don't fire when you want them suppressed.
var myselection = d3.select('body')
.append('svg').attr({height: 200, width: 200})
.append('circle')
.attr( {cx: 100, cy: 100, r: 0})
.attr('pointer-events', 'none')
.on('mouseover', function() { console.log('mouseover'); })
.on('mouseout', function() { console.log('mouseout'); })
myselection
.transition().duration(4000).attr('r', 100)
.transition().attr('pointer-events', 'auto')
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
If you have the console window open you'll notice that mouse over/out logs nothing until the circle stops growing.

Categories

Resources