I want to add click event to a d3 circle element that is static. I dynamically loaded this d3 code:
var svgContainer = d3.select("#vis").append("svg")
.attr("width", 200)
.attr("height", 200);
//Draw the Circle
var circle = svgContainer.append("circle")
.attr("class","item")
.attr("cx", 30)
.attr("cy", 30)
.attr("r", 20);
Somewhere in my code, I want to add the click event to circles that contain a class called "item". I am using following code but it does not work
$('.item').on('click', function(){
console.log("click added");
});
I looked at this post as well but it did not help me.
Related
I am currently appending text nodes to an SVG as follows:
var node = svg.selectAll(...);
node.append("text")
.attr("dy", 10)
.attr("dx", 4)
.text(function (d) { return d["name"]; });
There are currently about 10 nodes, each with a name.
I want to add a rectangle below each of the text nodes, using the correct width, I have tried this:
node.select<SVGTSpanElement>("text")
.each(function(x, i) {
console.log(this.getComputedTextLength());
node.append("rect")
.attr("fill", "#cccccc05")
.attr("width", this.getComputedTextLength())
.attr("height", 20)
});
My problem is that (kind of obviously) I am creating 10 rectangles per node, not one for each node.
How do I include the calculation for text width, and add a single rectangle per text element?
Without refactoring your code too much, simply change where you're appending the rectangles. In this case, the parent node of the texts themselves:
node.select<SVGTSpanElement>("text")
.each(function(x, i) {
console.log(this.getComputedTextLength());
d3.select(this.parentNode).append("rect")
.attr("fill", "#cccccc05")
.attr("width", this.getComputedTextLength())
.attr("height", 20)
});
However, the most idiomatic way is using each() for the node selection, not for the selection of texts inside it. Then, you get the text length for each node element, something like this:
node.each(function(x, i) {
d3.select(this).append("rect")
.attr("fill", "#cccccc05")
.attr("width", d3.select(this).select("text").node().getComputedTextLength())
.attr("height", 20)
});
I've created a DOM element which, when it fires a mouseover event, creates new DOM elements such as labels or a tooltip. Unfortunately, sometimes those elements are created underneath the mouse's current position. This causes the mouseleave event of that DOM element to fire, which is often responsible for removing the newly created DOM element.
In other words, when the new elements are created, the mouse is no longer "hovering" over the DOM element that originally fired the event, but the new DOM element. The browser reads this as a "mouseleave" event, which then fires the "mouseleave" function. This event, as it so happens, then removes the new element – even though the user has not moved the mouse.
This causes a cycle to occur, where the new elements are created and removed in rapid-fire, causing the blinking effect when a certain part of the DOM element is moused over. Here's a simplified version of the problem:
https://jsfiddle.net/KingOfCramers/8wbfjap4/2/
var svg = d3.select("svg").style("background-color","grey")
var data = ["This is a circle"]
var circle = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx",100)
.attr("cy",100)
.attr("r", 20)
.style("fill","red")
circle
.on("mouseover",function(d){
var xPos = d3.select(this).attr("cx")
var yPos = d3.select(this).attr("cy")
svg
.append("text").text(d)
.attr("x",xPos)
.attr("y",yPos)
})
.on("mouseleave",function(){
d3.select("text").remove();
})
Obviously this example is silly, but in instances where the data is much more crowded, simply moving the label by 10 or 15 pixels up or down is not a practical solution. I also cannot just create the label relative to the mouse cursor, because I will often be creating many at once using D3 data, for multiple DOM elements. What do most people do to solve this issue?
Thanks.
If you don't need to interact with that new element, just use pointer-events: none;:
.attr("pointer-events", "none")
Here is your code with that change:
var svg = d3.select("svg").style("background-color", "grey")
var data = ["This is a circle"]
var circle = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
.style("fill", "red")
circle
.on("mouseover", function(d) {
var xPos = d3.select(this).attr("cx")
var yPos = d3.select(this).attr("cy")
svg
.append("text").text(d)
.attr("x", xPos)
.attr("y", yPos)
.attr("pointer-events", "none")
})
.on("mouseleave", function() {
d3.select("text").remove();
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width=500 height=500></svg>
My task is to animate a series of circles on specific coordinates on top of a map created using Leaflet.js. I obtain the coordinates to place the circles at using websockets.
I am able to receive the events(coordinates) from a websocket connection and within the event handler I would like to add the code to add the circle to that coordinate.
The issue is that I am unable to find any resource which can help me accomplish what I need to do. There are approaches where they have used overlay panes, there is an approach where they have used the inbuilt markers from leaflet. But I have not seen any resource which can help me create and animate what I need to do.
This is the client side file I am dealing with :
<div class = "container">
<div class = "row">
<div class = "col-md-12" style="width:1000px; height:1000px;">
<div id='map' class = "map" style="border : 2px solid #182e69"></div>
</div>
</div>
</div>
<script>
var watercolorLayer = new L.TileLayer("http://tile.stamen.com/watercolor/{z}/{x}/{y}.jpg", {noWrap : true});
var map = L.map('map',{scrollWheelZoom:true, maxZoom:5, minZoom:2}).setView([0,0], 2).addLayer(watercolorLayer);
map.setMaxBounds(map.getBounds()).fitWorld();
map._initPathRoot();
</script>
<script>
var socket = io.connect();
socket.on('connect', function() {
console.log("Connection established");
});
socket.on('message', function(data) {
console.log(data[0]); //latitude obtained
console.log(data[1]); //longitude obtained
});
</script>
</body>
Just wanted to update and post an answer to my question in case someone else needs it later on. I tried using CircleMarker objects from leaflet but it turns out I could not find any information about how to animate them.
So I finally figured it out using D3.
I created the SVG element on top of the map and then selected it like so :
map._initPathRoot();
var svg = d3.select("#map").select("svg");
Then within the event handler for my websocket connection (receiving messages) I posted the following code :
var Point = map.latLngToLayerPoint(data); //data is the websocket
//event it receives and its a
//list with the 2 coordinates e.g "[lattitude, longtitude]"
var circle = svg.append("circle") //Adding circle element on coordinates
.attr("cx", Point.x )
.attr("cy", Point.y)
.attr("r", 0)
.style("fill", "#182e69")
.attr("opacity", 0.5)
.transition() //adding first transition
.attr("r", 50)
.attr("opacity", 0.3)
.delay(1000)
.duration(1000)
.on("end", function() {
d3.select(this)
.transition() //adding second transition
.attr("r", 0)
.attr("opacity", 0.0)
.duration(500)
.on("end", function() {
d3.select(this).remove(); //removing the circle
//element from the map
//after the transitions are done
});
});
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.
I have attached a mouseover event to an element - say, a circle - within the SVG element. I also need a "mousemove" event handler associated with the SVG element/"background" itself. However, they seem to conflict: when mousing over the circle, the handler attached to the circle does not supersede that associated with the SVG element itself.
How do I get the circle's mouseover to supersede the SVG element's event handler? I need them both, but only want the mouseover to be triggered over the circle and the mousemove to be triggered by movement anywhere else in the SVG element.
A simplified example can be seen in this JSFiddle: http://jsfiddle.net/aD8x2/ (JS code below). If you click on a circle (starting a line) and then mouse over another circle, you will see the flickering of color associated with both events being triggered when mousing over the circle.
var svg = d3.select("div#design")
.append("svg")
.attr("width", "500").attr("height", "500");
svg.selectAll("circle").data([100, 300]).enter().append("circle")
.attr("cx", function(d) { return d; })
.attr("cy", function(d) { return d; })
.attr("r", 30)
.on("mouseover", function () {
d3.select(this).attr("fill", "red");
})
.on("mouseout", function() {
d3.select(this).attr("fill", "black");
})
.on("click", function() {
svg.append("line")
.attr(
{
"x1": d3.select(this).attr("cx"),
"y1": d3.select(this).attr("cy"),
"x2": d3.select(this).attr("cx"),
"y2": d3.select(this).attr("cy")
})
.style("stroke-width", "10")
.style("stroke", "rgb(255,0,0)");
});
svg.on("mousemove", function() {
var m = d3.mouse(this);
svg.selectAll("line")
.attr("x2", m[0])
.attr("y2", m[1]);
});
In your case, it is actually the line causing the problem and not the SVG. That is, you're moving the mouse over the line you're drawing and thus a mouseout event is triggered for the circle.
You can prevent this by setting pointer-events to none for the line so it's "transparent" with respect to mouse events. Modified example here.