I am creating a heatmap for Ukraine with some economic indicators.
The Map
Depending on what radio-button is checked, a different colorscale is used to visualize
the different values/indicators.
d3.selectAll('.radio').on('change', function(){
if (document.getElementById('none').checked) {
areas.transition().duration(250)
.attr('fill','steelblue');}
else if (document.getElementById('agr').checked) {
areas.transition().duration(250)
.attr('fill', function(d){return colorScaleagr(d.properties.agricindx)});}
.... and so on.
Right now the tooltip (div) only shows the name of the region that is hovered over. I'd like to display also the value for the region, corresponding to the indicator that is selected at the moment. The tooltip's content (the name) is determined within an event-handler of the path/map element.
var areas = group.append('path')
.attr('d',path)
.attr('class', function(d) { return "subunit" + d.id; })
.attr('fill','steelblue')
.attr('stroke', 'white')
.attr('stroke-width', '1px')
.attr('opacity','0.8')
// Hover & Tooltip
.on("mouseover", function(d) {
d3.select(this).transition().duration(200).style("opacity", 1);
div.transition().duration(300).style("opacity", 1)
div.html(d.properties.name )
.style("left", (d3.mouse(this)[0] + 330) + "px")
.style("top", (d3.mouse(this)[1] + 15) + "px");
})
.on("mousemove", function(d) {
div.html(d.properties.name)
.style("left", (d3.mouse(this)[0] + 350) + "px")
.style("top", (d3.mouse(this)[1] + 25) + "px");
})
.on("mouseout", function(d) {
d3.select(this).transition().duration(200).style("opacity", 0.8);
div.transition().duration(300).style("opacity", 0)
});
So my question now is: How can i take into account the status of the radio-button,
whithin the path-element (area) , since the data for the single entities (regions)
is stored there. If I try to manipulate the tooltip within the radio-buttion selection, the data is not available to me. I hope I made myself understand :). I appreciate any help.
I think I understand what you are trying to do so here's an attempt of what I would do:
format you data as an object with key/value pairs as follow: 'none': 10, 'agr': 4, etc...
Store the key from the different radio buttons as a global variable that you update on the radio change function.
When creating your tooltip on the mouseover function, print the correct value by calling d[key], i.e. d['agr'].
Hope that helps!
Related
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.
I'm trying to include tooltips on a map I'm making in D3, imitating this code:
http://bl.ocks.org/lhoworko/7753a11efc189a936371
And here is the map I'm working on:
https://pantherfile.uwm.edu/schro333/public/2016_electoral_map/
As you can see here, I have tooltips working, and they display the correct name when the user hovers over a state, but the position relative to the cursor is really off. I'm not sure why this is.
Relevant code:
svgContainer.selectAll("pathCodes")
.data(json.features)
.enter()
.append("path")
.attr("id",
function(d){
var stateNameId = d.properties.name.toString();
stateNameId = stateNameId.replace(/\s+/g, '');
return stateNameId;
}) // this function returns the name of the state with spaces stripped and assigns it to individual polygon as id
.attr("d", pathCodes)
.attr("stroke", "black") // state outline color
.attr("stroke-width", "1") // state outline width
.attr("class", "noparty") // default to no party
.style("fill", politicalParties[0].color) // default fill is that of no party
/////////////
.on('mousemove', function(d) {
var mouse = d3.mouse(svgContainer.node());
tooltip.classed('hidden', false)
.attr('style', 'left:' + (mouse[0]) +
'px; top:' + (mouse[1]) + 'px')
.html(d.properties.name);
})
.on('mouseout', function() {
tooltip.classed('hidden', true);
});
/////////////
You get the wrong position because the X/Y position you are using is based off the SVG and not the actual location of the SVG on the page.
You can use
var loc = document.getElementById("states-map").getBoundingClientRect();
console.log(loc.top); //add this to the top
to get the offset. Not sure the d3 way to do it.
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...)
I have two identical charts. The graphics for them are built like so:
circles.enter().append("circle")
.attr("r", 0)
.attr("fill", function(d) { return fill_color; })
.attr("class", function(d) { return "circle_" + d.id; })
.on("mouseover", function(d, i) { build_tooltip(d, i, this); })
.on("mouseout", function(d, i) { hide_tooltip(d, i, this); });
On mouseover, it triggers the following function:
build_tooltip = function(data, i, element) {
var content = "Title: " + data.title;
show_tooltip(content, d3.event);
}
My question is: How can I make it so mousing over a circle in Chart #1 triggers the same mouseover event in Chart #2, but with unique data for each chart? Chart #2 must generate its own set of data (in this example, just a title). So, how can I make Chart #2's mouseover event fire whenever Chart #1's does?
In jQuery, this would be quite simple -- there is a literal .trigger() event. But how can I go about accomplishing the same with D3?
Have you tried using D3's dispatch? If not, see through this example for more details on how to use it.
So I have a visualization and I'm trying to use d3.tip() - https://github.com/Caged/d3-tip/blob/master/docs/initializing-tooltips.md#d3tip
This is my code-
this.svg = d3.select(".timechart").append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
svg.selectAll('.point')
.data(newData)
.enter()
.append("svg:circle")
.attr("cx", function(d,i){
var date = d["date"].match(/(\d+)/g);
date = new Date(date[2], date[0], date[1]);
return xScale(date);
})
.attr("cy", function(d,i){
var quantitySold = yScale(d["quantity-sold"]);
return quantitySold;
})
.attr("fill", "red")
.attr("r", 4)
.on("mouseover", function(d){
tooltip.show();
})
.on("mouseout", function(d){
tooltip.hide();
});
var tooltip = d3.tip()
.attr("class", "tooltip")
.offset([0,5])
.html(function(d){
console.log(d);
return "<strong> 20 </strong>";
});
svg.call(tooltip);
The console.log(d) gives me undefined, when it should give me the datum.
Why?
I also realize - I'm not sure what code I should post here to help - just let me know what would be useful.
The tooltip library that you're using (d3.tip) creates a single html tooltip for the entire visualization. The data for a particular element is passed to the tooltip using the tooltip's .show(d,i) method.
This example from the plug-in's creator shows how it is supposed to work. In particular, note that the show and hide methods are given directly as parameters to the .on(event, function) method of the rectangle selection:
svg.selectAll(".bar")
/* ... */
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
When the event occurs, d3 will therefore call these methods and pass the data object to them as a parameter.
In contrast, in your code:
.on("mouseover", function(d){
tooltip.show();
})
.on("mouseout", function(d){
tooltip.hide();
});
d3 will pass the data to your anonymous function, but you do not pass it on to the show/hide functions. So the data is undefined when the tooltip's show function tries to set the html content of the tooltip.
If you find that all confusing still, you might appreciate this write-up about passing functions as parameters.
Finally, although it isn't your main problem right now, you should be defining the tooltip before assigning its functions to an event handler. If you tried to do .on('mouseover', tooltip.show) before defining tooltip, you would get an error. You only avoided it by wrapping that function call in another function.