I'm calling a function on the "mousemove" event of a DOM element, using D3.js 's .on()
, like so :
d3.select("#myelement").on("mousemove", myfunc);
function myfunc(){
// Just to show that I need to get the mouse coordinates here
console.log(d3.mouse(this));
}
I need the function I'm calling to be aware of the event, i.e. the mouse coordinates.
Since the rest of my code is quite computationally expensive, I'd like to throttle the calls to myfunc
, say every 200 ms.
How can I do that while preserving the value of this
in myfunc
(so that d3.mouse(this)
still works) ?
I've tried this debounce function : https://davidwalsh.name/javascript-debounce-function
And also this : https://remysharp.com/2010/07/21/throttling-function-calls
But I'm unable to get those to work the way I want.
The problem is not passing this
to the debounce function, which is quite easy, as you can see in this JSFiddle (I'm linking a JSFiddle because the Stack snippet freezes when logging this
or a D3 selection).
The real problem is passing the D3 event: since d3.event
is null after the event has finished, you have to keep a reference to it. Otherwise, you'll have a Cannot read property 'sourceEvent' of null
error when trying to use d3.mouse()
.
So, using the function of your second link, we can modify it to keep a reference to the D3 event:
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments,
evt = d3.event;
//we get the D3 event here
clearTimeout(timer);
timer = setTimeout(function() {
d3.event = evt;
//and use the reference here
fn.apply(context, args);
}, delay);
};
}
Here is the demo, hover over the big circle, slowly moving your mouse:
var circle = d3.select("circle");
circle.on("mousemove", debounce(function() {
console.log(d3.mouse(this));
}, 250));
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments,
evt = d3.event;
clearTimeout(timer);
timer = setTimeout(function() {
d3.event = evt;
fn.apply(context, args);
}, delay);
};
}
.as-console-wrapper { max-height: 30% !important;}
<script src="https://d3js.org/d3.v4.js"></script>
<svg>
<circle cx="120" cy="80" r="50" fill="teal"></circle>
</svg>
PS: In both the JSFiddle and in the Stack snippet the function is called only when you stop moving the mouse, which is not the desired behaviour for a mousemove
. I'll keep working on it.
Thanks to Gerardo Furtado's answer, I managed to solve my problem by adapting the throttle function from this page like so :
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments,
event = d3.event;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
d3.event = event;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
d3.event = event;
fn.apply(context, args);
}
};
}
});
Now the callback is aware of the d3.event and d3.mouse(this) can be used normally inside the function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With