Reputation: 4721
I have two overlapping svg.g
groups with different onclick
events. I periodically blend the groups in or out of the visualization using the opacity
attribute. Currently, only the onclick
event of the group that is rendered on top is called, but I would like to call the event for the group that is currently visible. Alternatively, I could always call both events and use a conditional statement inside the called function which depended on the opacity
attribute.
Here is an example
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<div id="body"></div>
<script type="text/javascript">
var canvas_w = 1280 - 80,
canvas_h = 800 - 180;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var visible_group = svg.append("g")
.attr("opacity", 1)
.on("click", function(d){console.log("Click")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "blue");
var invisible_group = svg.append("g")
.attr("opacity", 0)
.on("click", function(d){console.log("Invisiclick")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "red");
</script>
</body>
</html>
This code will render a blue rectangle, the visible group. The group with the red rectangle is hidden. If you click on the blue rectangle, "Invisiclick" will be printed to the console, the onclick
event of the hidden group. I would like to print "Click" to the console, or alternatively, both "Invisiclick" and "Click".
How can I do this?
Upvotes: 2
Views: 2355
Reputation: 2994
Opacity does make the elements translucent, it doesn't make them disappear. Just as you can tap a piece of glass, you can click an element with opacity:0
.
Now, there are two options, based on whether the shapes are different in both views. If they aren't (say, you're drawing a world map, the countries stay the same, just the color changes), it might be easiest to listen to the topmost layer and then run an if-statement which part to execute. Like this
var state = "blue";
var clickHandler = function() {
if(state === "blue") {
console.log("Blue clicked");
} else {
console.log("Red clicked");
}
}
var toggleState = function() {
state = (state === "blue") ? "red" : "blue";
}
var updateDisplay = function() {
blueGroup
.transition()
.duration(400)
.attr("opacity", state === "blue" ? 1 : 0);
redGroup
.transition()
.duration(400)
.attr("opacity", state === "red" ? 1 : 0);
}
var canvas_w = 1280 - 80,
canvas_h = 120;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var blueGroup = svg.append("g")
.append("rect")
.attr("opacity", 1)
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "blue");
var redGroup = svg.append("g")
.on("click", clickHandler)
.append("rect")
.attr("opacity", 0)
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "red");
d3.select("button").on("click", function() {
toggleState();
updateDisplay();
});
<script src="https://samizdat.cz/tools/d3/3.5.3.min.js" charset="utf-8"></script>
<div id="body"></div>
<button>change!</button>
If on the other hand the shapes change, you will need to first make the elements translucent with opacity:0
and then make them disappear with display:none
(otherwise, they will flash out instantly). An alternative is pointer-events
, but only if you don't need to support old browsers.
The transition would then look like this:
var state = "blue";
var toggleState = function() {
state = (state === "blue") ? "red" : "blue";
}
var updateDisplay = function() {
blueGroup
.style("display", state === "blue" ? "block" : "none")
.transition()
.duration(400)
.attr("opacity", state === "blue" ? 1 : 0)
.each("end", function() {
blueGroup.style("display", state === "blue" ? "block" : "none");
});
redGroup
.style("display", state === "red" ? "block" : "none")
.transition()
.duration(400)
.attr("opacity", state === "red" ? 1 : 0)
.each("end", function() {
redGroup.style("display", state === "red" ? "block" : "none");
});
}
var canvas_w = 1280 - 80,
canvas_h = 120;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var blueGroup = svg.append("g")
.on("click", function() {
console.log("Blue clicked");
})
.append("rect")
.attr("opacity", 1)
.attr("x", 0)
.attr("y", 0)
.attr("width", 150)
.attr("height", 100)
.style("fill", "blue");
var redGroup = svg.append("g")
.on("click", function() {
console.log("Red clicked");
})
.append("rect")
.attr("opacity", 0)
.style("display", "none")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 120)
.style("fill", "red");
d3.select("button").on("click", function() {
toggleState();
updateDisplay();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="body"></div>
<button>change!</button>
Note that on each transition, we now have to handle both opacity
and display
, and in correct order. Also note that now we have listeners on both rect
s.
The example would be quite a bit simpler if it could be used with .enter()
and .exit()
selections, as you could make away with the .on("end")
and instead use .remove()
on the exiting transitions.
Update: practically identical to display:none
is also visibility: hidden
.
Upvotes: 3
Reputation: 4721
If you use the style visibility
rather than the attribute opacity
to set the groups as hidden or visible, you can also use the style pointer-events
to restrict events to visible elements.
var canvas_w = 1280 - 80,
canvas_h = 800 - 180;
var svg = d3.select("#body").append("div")
.append("svg:svg")
.attr("width", canvas_w)
.attr("height", canvas_h)
var visible_group = svg.append("g")
.style("visibility", "visible")
.style("pointer-events", "visible")
.on("click", function(d){console.log("Click")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "blue");
var invisible_group = svg.append("g")
.style("visibility", "hidden")
.style("pointer-events", "visible")
.on("click", function(d){console.log("Invisiclick")})
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.style("fill", "red");
</script>
This example will print "Click" to the console when you click the blue rectangle.
Upvotes: 2