How do I capture keystroke events in D3 force layout? - d3.js

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/

Related

Add graph in dc.js tooltip

I'm building a number of graphs using crossfilter and dc.js. Among others, there is a row chart and an histogram (a bar chart).
What I am trying to do is to create a tooltip on the row chart which will show the histogram.
Looking at this SO-question I saw an example using d3-tip. I have made an attempt in this jsfiddle. However, I cannot see how to embed a div in the tooltip.
Any suggestion? (If using plain d3 is better, I'm ok with that.)
Snippet of code is:
function draw_row(div_id){ ...; return row_chart; }
function draw_hist(div_id){ ...; return bar_chart; }
var rate_chart = draw_row('#rate').title(function(){return'';});
dc.renderAll();
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function () {
// What to put in here???
draw_hist('#distr').render();
return "<div id='distr'>Distribution<br></div>"
});
d3.selectAll("#rate g.row")
.call(tip)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
Fun project!
Yes, as you noticed, you're not going to be able to render the chart while you're in the .html() callback - that only returns static HTML, and I don't think you can give it an element instead.
So we'll have to find a place to render after the HTML has already been generated. Luckily, d3-tip doesn't try to handle mouse events or anything like that - the code which displays the tip is right there in the code you've posted:
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
So we can wrap tip.show in a function of our own, and then render the chart into the tip once it's on the screen.
We have to watch out because mouseover will fire every time the mouse moves, and we probably don't want to replace the tip-chart until we hover over another bar. So we'll remember the id of the last bar we hovered:
var last_tip = null;
d3.selectAll("#rate g.row")
.call(tip)
.on('mouseover', function(d) {
if(d.key !== last_tip) {
tip.show(d);
draw_hist('#distr').render();
last_tip = d.key;
}
})
.on('mouseout', function(d) {
last_tip = null;
tip.hide(d);
});
Finally, d3-tip needs to know the size of the tip content in order to render in the right place. (If it accidentally renders on top of the element, this can cause horrible flickering when the mouse goes over the tip, registering mouseout on the element.)
So we'll just hard-code that, since we're hard-coding the chart size anyway. 20 extra pixels to fit the title:
.html(function (d) {
return "<div id='distr' style='min-width:300px; min-height: 320px'>Distribution<br></div>"
});
Looks pretty cool with the default translucent black style from d3-tip:
Here's the fork of your fiddle: https://jsfiddle.net/gordonwoodhull/hkx7j3r5/10/

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.

Is there a select query to grab all but my current selection?

In my d3js app, when the user hovers over a particular circle, I have it enlarge. That is no problem. At the same time, I want to select "all the others" and make them smaller. What is a good query to grab "all the other circles"?
You can use selection.filter or the lesser known functional form of the commonly used selection.select depending on your needs.
If you bind your DOM elements to data using key functions, which is the recommended way, then you can filter on a selection's key: http://jsfiddle.net/9TmXs/
.on('click', function (d) {
// The clicked element returns to its original size
d3.select(this).transition() // ...
var circles = d3.selectAll('svg circle');
// All other elements resize randomly.
circles.filter(function (x) { return d.id != x.id; })
.transition() // ...
});
Another general approach is comparing the DOM elements themselves: http://jsfiddle.net/FDt8S/
.on('click', function (d) {
// The clicked element returns to its original size
d3.select(this).transition() // ..
var self = this;
var circles = d3.selectAll('svg circle');
// All other elements resize randomly.
circles.filter(function (x) { return self != this; })
.transition()
// ...
});

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.

StopPropagation() with SVG element and G

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.

Resources