-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Throttle selectPoints #2040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Throttle selectPoints #2040
Conversation
turns out this leads to stack errors when used with very large arrays anyway it's redundant for the first trace. thx @rreusser!
src/components/fx/hover.js
Outdated
Lib.throttle( | ||
function() { _hover(gd, evt, subplot, noHoverEvent); }, | ||
constants.HOVERMINTIME, | ||
gd._fullLayout._uid + constants.HOVERID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using fullLayout._uid
to construct the key to what we're throttling means we can 🔪 gd._lastHoverTime
and gd._hoverTimer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loving this ❤️
src/lib/get_graph_div.js
Outdated
// Get the container div: we store all variables for this plot as | ||
// properties of this div | ||
// some callers send this in by DOM element, others by id (string) | ||
module.exports = function(gd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/plots/cartesian/select.js
Outdated
@@ -186,9 +186,15 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { | |||
selection = []; | |||
for(i = 0; i < searchTraces.length; i++) { | |||
searchInfo = searchTraces[i]; | |||
[].push.apply(selection, fillSelectionItem( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a reported bug AFAIK, but this use of push.apply
overloads the stack for large plots. Doesn't look to me as though any of the other places we use push.apply
are susceptible to this issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyone interested in writing a syntax test to 🔒 this down?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do use push.apply
in a few other places - but they get max a few items in the array so it's not a problem - so if we wanted to 🔒 it, we'd have to also remove those other uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexcjohnson funny enough I stumbled upon this stack overflow in selection for regl. I replaced it with .concat
here https://github.com/plotly/plotly.js/pull/1869/files#diff-8c5d8f688f2bb41a18d35397e5f58864R211, since it is being called only as many times as there is traces per subplot, which is small. (related tweet)
searchInfo = searchTraces[i]; | ||
searchInfo.selectPoints(searchInfo, false); | ||
} | ||
throttle.done(throttleID).then(function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to wait for any throttling-delayed move event before we process the mouseup event, so we have the correct data.
test/jasmine/tests/select_test.js
Outdated
.then(function() { | ||
// deselect gives an extra plotly_selected event on the first click | ||
// event data is undefined | ||
checkEventCounts(1, 2, 1, 'deselect'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The gd.once
we were using in this test before hid this behavior - which exists on master as well as in this branch. I imagine we would prefer to not emit these events with undefined data - which happen I guess when we get here but dragged === false
. @etpinard shall I remove that extra event and put this test back as it appeared to be previously?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine we would prefer to not emit these events with undefined data
Hmm. I'm thinking some users might find it useful to have plotly_selected
fired even when the selection polygon is smaller than the minimum allowed. In this case, plotly_selected
essentially mimics plotly_deselect
- which some users might not know about.
So, I'd vote for keeping the current behavior and updating the test (good catch by the way 🏅 )
But, maybe this is something we might want to change in v2, especially now that click selections are on the table for future development.
test/jasmine/tests/select_test.js
Outdated
assertPoints([{lon: 20, lat: 20}]); | ||
assertLassoPoints([ | ||
[13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97] | ||
]); | ||
checkEventCounts(4, 1, 0, 'lasso'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4 plotly_selecting
events is real - because there are 5 points in the lasso path.
Interestingly, I noticed today that selecting a box + heatmapgl can be very slow. I'm curious whether that's the same pathway. |
I didn't know you could select heatmapgl traces 😕 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. That new lib/throttle.js
module is very very nice.
Your patches in select_test.js
scare me a little bit as they will conflict with e53a908.
src/components/fx/hover.js
Outdated
Lib.throttle( | ||
function() { _hover(gd, evt, subplot, noHoverEvent); }, | ||
constants.HOVERMINTIME, | ||
gd._fullLayout._uid + constants.HOVERID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loving this ❤️
@@ -1327,8 +1327,6 @@ plots.purge = function(gd) { | |||
delete gd.hmlumcount; | |||
delete gd.hmpixcount; | |||
delete gd.numboxes; | |||
delete gd._hoverTimer; | |||
delete gd._lastHoverTime; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
@@ -446,7 +446,7 @@ describe('Event data:', function() { | |||
function _hover(px, py) { | |||
return new Promise(function(resolve, reject) { | |||
gd.once('plotly_hover', function(d) { | |||
delete gd._lastHoverTime; | |||
Lib.clearThrottle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smooth ⛵
|
||
var hoverConstants = require('../fx/constants'); | ||
|
||
var unhover = module.exports = {}; | ||
|
||
|
||
unhover.wrapped = function(gd, evt, subplot) { | ||
if(typeof gd === 'string') gd = document.getElementById(gd); | ||
gd = getGraphDiv(gd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIce 🌴
src/plots/cartesian/select.js
Outdated
@@ -186,9 +186,15 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { | |||
selection = []; | |||
for(i = 0; i < searchTraces.length; i++) { | |||
searchInfo = searchTraces[i]; | |||
[].push.apply(selection, fillSelectionItem( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyone interested in writing a syntax test to 🔒 this down?
SELECTDELAY: 100, | ||
|
||
// cache ID suffix for throttle | ||
SELECTID: '-select', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking, but we should start thinking about moving selection things out of plots/cartesian/
and into e.g. component/fx/
or a new component/select
module.
src/plots/cartesian/select.js
Outdated
if(selection.length) { | ||
for(var j = 0; j < thisSelection.length; j++) { | ||
selection.push(thisSelection[j]); | ||
throttle.throttle( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
searchInfo.selectPoints(searchInfo, false); | ||
} | ||
throttle.done(throttleID).then(function() { | ||
throttle.clear(throttleID); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe throttle.done()
should call throttle.clear()
before returning a promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
throttle.done()
should callthrottle.clear()
before returning a promise?
I don't think so. It doesn't come into play here, but you could imagine wanting to finish whatever event had been throttled when done
was called, without cutting off throttling of future events in the train. I guess that's saying I didn't mean done
to mean you were necessarily done forever, just done with whatever is in the queue right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Thanks!
test/jasmine/tests/select_test.js
Outdated
var selectingCnt, selectingData, selectedCnt, selectedData, deselectCnt, doubleClickData; | ||
var selectedPromise, deselectPromise; | ||
|
||
function resetEvents(gd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha. This will conflict massively with e53a908
Not sure who should get priority here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#2030 is epic enough already, you don't need to add a big merge resolution to it. You can merge first.
src/lib/throttle.js
Outdated
* @param {number} minInterval: minimum time, in milliseconds, between | ||
* invocations of `callback` | ||
*/ | ||
exports.throttle = function throttle(callback, minInterval, id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think throttle(id, minInterval, callback)
would a call signature more familiar to most (node.)js devs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> b6d64be
_clearTimeout(cache); | ||
|
||
function exec() { | ||
callback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So callback
can't itself be async - which is fine but deserves a mention in the docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> b6d64be
x1 = 150, | ||
y1 = 250, | ||
x2 = 50, | ||
y2 = 50; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(x2, y2)
is in the top margin of this plot, so the doubleclick didn't do anything. But since these tests didn't actually check that the handlers ever got called, it didn't cause a failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice investigation work. Thanks!
@etpinard I had to do (as expected) a fair amount of work pruning merge conflicts in |
and beef up its docstring
I'm looking forward to adding tests for bar/histogram traces tomorrow. 🍻 |
and increase tolerance to get all tests passing locally
Great. Tests are ✅ ↪️ 💃 |
closes #2005 - adds throttling to box and lasso selection. I opted to throttle just the
selectPoints
portion, because It's where nearly all the expense of that drag handler lives, so this way keeps the selection region feeling snappy even if it takes some time for the selected points to catch up.Here's what it looked like before:


and now with throttling:
Using this plot: