I am using the d3 force directed graph animation.
Steps to reproduce problem:
- Fire up Firefox browser
- visit provemath.org
- click the x in the top right or log in (nodes should appear at this point)
- click on any node
- click on the back arrow on the top left
The result is that the node you clicked is still attached to your mouse as if you are dragging it around. The desired result is that this doesn't happen :)
Insights:
This only occurs in Firefox.
d3 relevant code:
When data is bound to nodes, we use .call(gA.drag)
where gA.drag = gA.force.drag()
, and in the d3 library itself, we have:
force.drag = function() {
if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
if (!arguments.length) return drag;
this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
};
function dragmove(d) {
d.px = d3.event.x, d.py = d3.event.y;
force.resume();
}
return d3.rebind(force, event, "on");
};
function d3_layout_forceDragstart(d) {
d.fixed |= 2;
}
function d3_layout_forceDragend(d) {
d.fixed &= ~6;
}
function d3_layout_forceMouseover(d) {
d.fixed |= 4;
d.px = d.x, d.py = d.y;
}
function d3_layout_forceMouseout(d) {
d.fixed &= ~4;
}
Also when data is bound to nodes, I use .on('mousedown', mousedown)
and .on('mouseup', mouseup)
. I wrote those functions and they are:
function mousedown(node) {
node.time_before = getShortTime(new Date())
node.client_x_before = d3.event.clientX
node.client_y_before = d3.event.clientY
// d3.event.stopPropagation() // need cancelBubble for MS
}
function mouseup(node) {
if( mod(getShortTime(new Date()) - node.time_before, 60) < 0.85
&& cartesianDistance([node.client_x_before, node.client_y_before], [d3.event.clientX, d3.event.clientY]) < 55
) {
$.event.trigger({ type: 'node-click', message: node.id })
}
delete node.time_before
delete node.client_x_before
delete node.client_y_before
}
function getShortTime(date) {
return date.getSeconds() + date.getMilliseconds()/1000
}
function mod(m, n) {
return (m % n + n) % n;
}
I have tried using both d3.event.stopPropagation()
and d3.event.dataTransfer.setData('text', 'anything')
as suggested in this question at various points in my code, to no avail. The setData
code seems to have the effect of halting events dead in their tracks as soon as the line is run, which doesn't make sense to me.
One possible, but not entirely satisfactory solution might be to manually find and destroy the drag event when a user clicks the back arrow.
UPDATE: I AM INCLUDING SOME MORE CODE EXCERPTS:
main.py
$(document).on('node-click', function(Event){
current_node = graph.nodes[Event.message] // graph.nodes is a DICTIONARY of nodes
updateNodeTemplateLearnedState()
blinds.open({ // in this module, new DOM elements are added with jQuery's .append() method
object: current_node,
})
hide('svg')
hide('#overlay')
show('#node-template') // This DOM element is the container that blinds.open() populated. Event WITHOUT adding new DOM elements, it is possible that the mere putting of this guy in front of the vertices is causing the issue
if( false /*mode !== 'learn'*/){
ws.jsend({ command: "re-center-graph", central_node_id: current_node.id })
}
})
function show(css_selector) { // this stuff fails for svg when using .addClass, so we can just leave show and hide stuff in the JS.
let $selected = $(css_selector)
if( !_.contains(css_show_hide_array, css_selector) ){
$selected.css('height', '100%')
$selected.css('width', '100%')
$selected.css('overflow', 'scroll')
}else{
// $selected.removeClass('hidden')
$selected.css('visibility', 'visible')
}
}
meetamit's suggestion of using a timeout, even with a time of "0":
setTimeout(function() {
$.event.trigger({ type: 'node-click', message: node.id })
}, 0);
is in fact working, so I think his theory is correct.