StopPropagation() with SVG element and G - d3.js

I created an SVG element with an .on("click") behavior and appended g elements with .on("click") and thought that I could use d3.event.stopPropagation() to keep the SVG click event from firing with the g click event. Instead, both events continue to fire. So I must be placing stopPropagation in the wrong place.
svg = d3.select("#viz").append("svg:svg")
.attr("width", 800)
.attr("height", 800)
.on("mousedown", mousedown);
sites = svg.selectAll("g.sites")
.data(json.features)
.enter()
.append("svg:g")
.on("click", siteClick)
;
sites.append("svg:circle")
.attr('r', 5)
.attr("class", "sites")
;
function mousedown() {
console.log("mouseDown");
}
function siteClick(d, i) {
d3.event.stopPropagation();
console.log("siteClick");
}

You seem to be mixing up the click and mousedown events. Calling stopPropagation will only prevent propagation of a single event at a time, and these are separate events.
Typically, a click gesture will cause mousedown, mouseup and click events, in that order.
You can keep the click event handler on the child elements and add a mousedown event handler with a stopPropagation call, and that should achieve what you're after.
Here is an example demonstrating its use in a similar situation to yours.

Related

D3 Selection Highlight (efficiency?)

I have a simple visual of many rects, over 100 I'd say. For aesthetic purposes I want to create a high light effect on mouse click. I also wanted to make this effect somewhat intuitive by removing that effect once the user clicks on a new rect. However I couldn't get this to work without resorting to a d3.selectAll() call, so I'm thinking this approach might not be ideal if this project gets any bigger. Here is the code:
.on('click.highlight', function() {
//set any previously highlighted rects back to normal color/brightness
d3.selectAll('.highlight').transition().duration(250)
.style('fill', function(d) { return d3.rgb(d.color)})
d3.select(this).classed('highlight',true);
//now it's safe to assign the current highlighted rect a brighter hue... i think
d3.select(this).transition().duration(250)
.style('fill', function(d) { return d3.rgb(d.color).brighter(.5)})
})
Though this code does what I wanted it to do, but presumably there could only ever be 1 other highlight rect to worry about at any give time. So again, I'm not sure that using d3.selectAll() is warranted here.
So anyway, is there a more efficient way? I'd like to keep it all within one .on('click') function if possible.
If you are looking to avoid use of .selectAll, you could create a selection of one rect that contains the last clicked rectangle. Each time you click on a rectangle:
unhighlight the previously selected highlighted rect
update that selection to reflect the most recently clicked rectangle
highlight the newly selected rect
I use the variable highlightedRect to hold the selection that will allow the above workflow:
var svg = d3.select("body").append("svg")
.attr("width",600)
.attr("height",400);
var highlightedRect = d3.select(null);
var rects = svg.selectAll("rect")
.data(d3.range(1600))
.enter()
.append("rect")
.attr("y",function(d) { return Math.floor(d/50)*12; })
.attr("x",function(d) { return d%50 * 12 })
.attr("width",11)
.attr("height",11)
.attr("stroke","white")
.on("click",function(d) {
// Recolor the last clicked rect.
highlightedRect.attr("fill","black");
// Color the new one:
highlightedRect = d3.select(this);
highlightedRect.attr("fill","steelblue");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

Is it expensive to call D3.js transition multiple times during scroll?

I have a scroll event listener that triggers a D3.js transition when the page hits a specific scroll target. The animation works nicely and doesn't lag, but the transition is called on each scroll position. Is this expensive? After the first draw() the #mySvgShape is set to fill-opacity:0.8. Is it an issue to keep calling the transition during the scroll?
scrollPosition is the percentage (0 to 1) of the page that has been scrolled.
function draw(scrollPosition) {
if (scrollPosition > 0.2 && scrollPosition < 0.4) {
el.select('#mySvgShape')
.transition()
.style("fill-opacity", "0.8");
}else{
el.select('#mySvgShape')
.transition()
.style("fill-opacity", "1e-6");
}
You can check if there is any transition happening and, if yes, do not call the transition again.
To do that, use d3.active:
d3.active(node[, name]):
returns the active transition on the specified node with the specified name, if any.
So, your if can be changed to this:
if (percentage > 0.2 && percentage < 0.4) {
if (!d3.active(d3.select("#dam-fill").node())) {
d3.select("#dam-fill")
.transition()
.duration(5000)
.style("fill-opacity", "1")
.attr("transform", "translate(-50, -200) scale(1.2,1.2)");
}
}
Here is your updated fiddle: https://jsfiddle.net/gerardofurtado/ze2dc976/
I changed the transition to 5000 ms just to show that, if you scroll up or down while the transition is happening, it doesn't stop (as in your fiddle). Thus, not only calling transition multiple times in the scroll is expansive, it will mess the transition.

How to disable brush selection on right click in d3.js

The question is how do I prevent brush events (brushstart, brush and brushend) from firing if the right mouse click was pressed. In other words I want the d3 brush to act only if the left mouse or the middle mouse button was pressed. I haven’t found a direct answer to this question neither here nor by googling for it.
This is what I've come up with:
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brush", function() {
//brush code
})
.on("brushend", function() {
//brushend code
})
svg.append("g")
.attr("class", "brush")
.on("mousedown", function(){
if(d3.event.button === 2){
d3.event.stopImmediatePropagation();
};
})
.call(brush)
Basically you need to add mouse-down event to the "g" element before calling the brush function. While searching on how to do this I've come to "No Zoom on Context Menu" example shown here: http://bl.ocks.org/mbostock/6140181 And after figuring it out, it was easy to do what I wanted.

How do I capture keystroke events in D3 force layout?

I would like to respond to keystroke events directed at nodes in my force layout. I've tried adding all the variants of "keystroke", "keypress", "keyup", "keydown" that I could think of, but none of them is firing. My mouse events fire just fine. I couldn't find any keystroke events in the d3 source.... is there a way to capture key strokes?
nodes.enter().append("circle")
.on("click", function(d) { return d.clickHandler(self); })
.on("mouseover", function(d) { return d.mouseOverHandler(self); })
.on("mouseout", function(d) { return d.mouseOutHandler(self); })
.on("keyup", function(d) {
console.log("keypress", d3.event); // also tried "keyup", "keydown", "key"
})
.classed("qNode", true)
.call(force.drag);
I think the problem here is you are trying to add keyboard events to elements that are not focusable, try adding a keydown event to a focusable element (body in this case):
d3.select("body")
.on("keydown", function() { ...
here you can use properties of d3.event, for instance d3.event.keyCode, or for more specialized cases, d3.event.altKey, d3.event.ctrlKey, d3.event.shiftKey, etc..
Looking at the KeyboardEvent Documentation might be helpful as well.
I've made a simple fiddle with keyboard interaction here: http://jsfiddle.net/qAHC2/292/
You can extend this to apply these keyboard interactions to svg elements by creating a variable to 'select' the current object:
var currentObject = null;
Then update this current object reference during appropriate mouse event methods:
.on("mouseover", function() {currentObject = this;})
.on("mouseout", function() {currentObject = null;});
Now you can use this current object in your keyboard interactions set up earlier.
here's a jsfiddle of this in action: http://jsfiddle.net/qAHC2/295/

Dragging on force layout prevents other mouseup listeners

I want to enable dragging in a d3.js force layout. When dragging a circle and release the mouse button, I want to call a specific function via callback, like this:
this.force = d3.layout.force()
.nodes(this.nodes)
.size([this.width, this.height]);
// enable dragging
this.circle
.call(this.force.drag)
.on("dragend", function() {
console.log("You should see this, when releasing a circle.");
})
.on("mouseup.drag",function(d,i) {
console.log("Or see this.");
});
Unfortunately the event is never fired/consumed completely by the force.drag handler.
So how can I execute a given callback function in a d3 force layout at the end of a drag?
You are not calling the "dragend" event on this.force.drag here.
This also depends on how you have defined this.force.drag.
This should work for you
myCustomDrag = d3.behavior.drag()
.on("dragstart", function(d,i){
//do something when drag has just started
})
.on("drag", function(d,i){
//do something while dragging
})
.on("dragend", function(d,i){
//do something just after drag has ended
});
In the above code, just use call(myCustomDrag) on an element (circle here) on which you want this drag behaviour to be present.

Resources