Reputation: 12718
I'm trying to add a click
and mouseout
handler to a group of circles and text. The click handler works fine, but the mouse out seems to fire even if I haven't left the circleGroup
area (shown below).
Note: there are multiple of these circle groups which are added in a grid.
Here is the SVG as it appears in the browser:
The code to produce the circleGroup, containing an outer green-ish circle, an inner white circle, and a text element, is as follows:
let circleGroup = leftPanelGroup.append('g');
let outerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.attr("r", 15);
let innerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.style('stroke', '#fff')
.attr("r", 7);
let text = circleGroup.append('text')
.style('color', '#fff')
.attr("x", x)
.attr("y", y - 25)
.style('fill', '#fff')
.attr("font-size", 12)
.attr('font-weight', 'bold')
.attr("font-family", "sans-serif")
.attr('id', 'circle-text')
.style("text-anchor", "middle")
.text('Thank you');
...
On click
anywhere within the circleGroup
, the circleClick
should fire. This works fine. The issue is, the circleMouseout
function seems to randomly fire even if I haven't yet left the bounding area of the circleGroup
:
circleGroup.on('click', circleClick).on('mouseout', circleMouseout);
function circleClick() {
// Do something
}
function circleMouseout() {
// Do something else
}
The output HTML in console shows the area of the <g>
svg group element. I'd expect that click anywhere in this highlighted area would fire the click
event, and only when I mouse out of this highlighted area would the mouseout
event fire. Again, the click
works fine, the mouseout
does not.
<g>
<circle cx="252.99037499999997" cy="340.938" r="15" style="fill: rgb(108, 160, 123);">
</circle>
<circle cx="252.99037499999997" cy="340.938" r="7" style="fill: rgb(108, 160, 123); stroke: rgb(255, 255, 255);">
</circle>
<text x="252.99037499999997" y="315.938" font-size="12" font-weight="bold" font-family="sans-serif" id="circle-text" style="color: rgb(255, 255, 255); fill: rgb(255, 255, 255); text-anchor: middle;">Thank you
</text>
</g>
Upvotes: 1
Views: 140
Reputation: 38161
The bounding box of a g
doesn't affect where a mouse interacts with it.The mouse only interacts with the "interaction area" of an element. This is generally the stroke or fill of rendered elements. So, whenever your mouse leaves the circles or the text, you trigger the mouseout event, not when you leave the bounding box of the parent g
.
If you want the bounding box of the parent g
to interact with the mouse, then we need to add a new rectangle. We can extract the g
's bbox and use this to draw a new rectangle over the bounding box of the g
. This rectangle can be given the fill of none
to make it invisible, but also given a pointer-events property of all to ensure it interacts with the mouse:
let boundingBox = circleGroup.append('rect')
.each(function() {
var bbox = this.parentNode.getBBox(); // get parent `g` bounding box
d3.select(this)
.attr("width", bbox.width) // size rect based on bounding box.
.attr("height", bbox.height)
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("fill","none")
.attr("pointer-events","all")
})
Now we can assign event listeners to the g
, or the rect
for that matter, and the entire g
bounding box will respond to mouse events:
let circleGroup = d3.select("body")
.append("svg")
.append("g");
let x = 50;
let y = 50;
let color = "steelblue";
let outerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.attr("r", 15);
let innerCircle = circleGroup.append("circle")
.attr("cx", x)
.attr("cy", y)
.style('fill', color)
.style('stroke', '#fff')
.attr("r", 7);
let text = circleGroup.append('text')
.attr("x", x)
.attr("y", y - 25)
.style('fill', color)
.attr("font-size", 12)
.attr('font-weight', 'bold')
.attr("font-family", "sans-serif")
.attr('id', 'circle-text')
.style("text-anchor", "middle")
.text('Thank you');
let boundingBox = circleGroup.append('rect')
.each(function() {
var bbox = this.parentNode.getBBox();
d3.select(this)
.attr("width", bbox.width)
.attr("height", bbox.height)
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("fill","none")
.attr("pointer-events","all")
})
circleGroup.on("mouseover", function() {
console.log("mouseover");
}).on("mouseout", function() {
console.log("mouseout");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Upvotes: 2