Skip to content

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

Merged
merged 10 commits into from
Sep 28, 2017
7 changes: 3 additions & 4 deletions src/components/dragelement/unhover.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@


var Events = require('../../lib/events');
var throttle = require('../../lib/throttle');

var hoverConstants = require('../fx/constants');

var unhover = module.exports = {};

Expand All @@ -20,10 +22,7 @@ unhover.wrapped = function(gd, evt, subplot) {
if(typeof gd === 'string') gd = document.getElementById(gd);

// Important, clear any queued hovers
if(gd._hoverTimer) {
clearTimeout(gd._hoverTimer);
gd._hoverTimer = undefined;
}
throttle.clear(gd._fullLayout._uid + hoverConstants.HOVERID);

unhover.raw(gd, evt, subplot);
};
Expand Down
5 changes: 4 additions & 1 deletion src/components/fx/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ module.exports = {
HOVERFONT: 'Arial, sans-serif',

// minimum time (msec) between hover calls
HOVERMINTIME: 50
HOVERMINTIME: 50,

// ID suffix (with fullLayout._uid) for hover events in the throttle cache
HOVERID: '-hover'
};
24 changes: 5 additions & 19 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,12 @@ var HOVERTEXTPAD = constants.HOVERTEXTPAD;
// The actual rendering is done by private function _hover.
exports.hover = function hover(gd, evt, subplot, noHoverEvent) {
if(typeof gd === 'string') gd = document.getElementById(gd);
if(gd._lastHoverTime === undefined) gd._lastHoverTime = 0;

// If we have an update queued, discard it now
if(gd._hoverTimer !== undefined) {
clearTimeout(gd._hoverTimer);
gd._hoverTimer = undefined;
}
// Is it more than 100ms since the last update? If so, force
// an update now (synchronously) and exit
if(Date.now() > gd._lastHoverTime + constants.HOVERMINTIME) {
_hover(gd, evt, subplot, noHoverEvent);
gd._lastHoverTime = Date.now();
return;
}
// Queue up the next hover for 100ms from now (if no further events)
gd._hoverTimer = setTimeout(function() {
_hover(gd, evt, subplot, noHoverEvent);
gd._lastHoverTime = Date.now();
gd._hoverTimer = undefined;
}, constants.HOVERMINTIME);
Lib.throttle(
function() { _hover(gd, evt, subplot, noHoverEvent); },
constants.HOVERMINTIME,
gd._fullLayout._uid + constants.HOVERID
Copy link
Collaborator Author

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loving this ❤️

);
};

/*
Expand Down
39 changes: 39 additions & 0 deletions src/lib/get_graph_div.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

/**
* Allow referencing a graph DOM element either directly
* or its id string
*
* @param {HTMLDivElement|string} gd: a graph element or its id
*
* @returns {HTMLDivElement} the DOM element of the graph
*/
// 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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, this file was supposed to go in the next commit aa434dd

docs cleaned up in 0861736

var gdElement;

if(typeof gd === 'string') {
gdElement = document.getElementById(gd);

if(gdElement === null) {
throw new Error('No DOM element with id \'' + gd + '\' exists on the page.');
}

return gdElement;
}
else if(gd === null || gd === undefined) {
throw new Error('DOM element provided is null or undefined');
}

return gd; // otherwise assume that gd is a DOM element
};
4 changes: 4 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ lib.error = loggersModule.error;
var regexModule = require('./regex');
lib.counterRegex = regexModule.counter;

var throttleModule = require('./throttle');
lib.throttle = throttleModule.throttle;
lib.clearThrottle = throttleModule.clear;

lib.notifier = require('./notifier');

lib.filterUnique = require('./filter_unique');
Expand Down
80 changes: 80 additions & 0 deletions src/lib/throttle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var timerCache = {};

/**
* Throttle a callback. `callback` executes synchronously only if
* more than `minInterval` milliseconds have already elapsed since the latest
* call (if any). Otherwise we wait until `minInterval` is over and execute the
* last callback received while waiting.
* So the first and last events in a train are always executed (eventually)
* but some of the events in the middle can be dropped.
*
* @param {function} callback: the function to throttle
* @param {string} id: an identifier to mark events to throttle together
* @param {number} minInterval: minimum time, in milliseconds, between
* invocations of `callback`
*/
exports.throttle = function throttle(callback, minInterval, id) {
Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> b6d64be

var cache = timerCache[id];
var now = Date.now();

if(!cache) {
/*
* Throw out old items before making a new one, to prevent the cache
* getting overgrown, for example from old plots that have been replaced.
* 1 minute age is arbitrary.
*/
for(var idi in timerCache) {
if(timerCache[idi].ts < now - 60000) {
delete timerCache[idi];
}
}
cache = timerCache[id] = {ts: 0, timer: null};
}

_clearTimeout(cache);

if(now > cache.ts + minInterval) {
callback();
cache.ts = now;
return;
}

cache.timer = setTimeout(function() {
callback();
cache.ts = Date.now();
cache.timer = null;
}, minInterval);
};

/**
* Clear the throttle cache for one or all timers
* @param {optional string} id:
* if provided, clear just this timer
* if omitted, clear all timers (mainly useful for testing)
*/
exports.clear = function(id) {
if(id) {
_clearTimeout(timerCache[id]);
delete timerCache[id];
}
else {
for(var idi in timerCache) exports.clear(idi);
}
};

function _clearTimeout(cache) {
if(cache && cache.timer !== null) {
clearTimeout(cache.timer);
cache.timer = null;
}
}
2 changes: 0 additions & 2 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1327,8 +1327,6 @@ plots.purge = function(gd) {
delete gd.hmlumcount;
delete gd.hmpixcount;
delete gd.numboxes;
delete gd._hoverTimer;
delete gd._lastHoverTime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

delete gd._transitionData;
delete gd._transitioning;
delete gd._initialAutoSize;
Expand Down
2 changes: 1 addition & 1 deletion test/jasmine/tests/cartesian_interact_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smooth ⛵

resolve(d);
});

Expand Down
6 changes: 3 additions & 3 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ describe('hover info', function() {
describe('\'hover info for x/y/z traces', function() {
function _hover(gd, xpx, ypx) {
Fx.hover(gd, { xpx: xpx, ypx: ypx }, 'xy');
delete gd._lastHoverTime;
Lib.clearThrottle();
}

function _assert(nameLabel, lines) {
Expand Down Expand Up @@ -811,7 +811,7 @@ describe('hover after resizing', function() {
}

function assertLabelCount(pos, cnt, msg) {
delete gd._lastHoverTime;
Lib.clearThrottle();
mouseEvent('mousemove', pos[0], pos[1]);

var hoverText = d3.selectAll('g.hovertext');
Expand Down Expand Up @@ -1097,7 +1097,7 @@ describe('Test hover label custom styling:', function() {

function _hover(gd, opts) {
Fx.hover(gd, opts);
delete gd._lastHoverTime;
Lib.clearThrottle();
}

it('should work for x/y cartesian traces', function(done) {
Expand Down
2 changes: 1 addition & 1 deletion test/jasmine/tests/hover_spikeline_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('spikeline', function() {

function _hover(evt, subplot) {
Fx.hover(gd, evt, subplot);
delete gd._lastHoverTime;
Lib.clearThrottle();
}

function _assert(lineExpect, circleExpect) {
Expand Down
2 changes: 1 addition & 1 deletion test/jasmine/tests/pie_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('pie hovering', function() {

function _hover() {
mouseEvent('mouseover', 223, 143);
delete gd._lastHoverTime;
Lib.clearThrottle();
}

function assertLabel(content, style) {
Expand Down
3 changes: 1 addition & 2 deletions test/jasmine/tests/plots_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,7 @@ describe('Test Plots', function() {
'data', 'layout', '_fullData', '_fullLayout', 'calcdata', 'framework',
'empty', 'fid', 'undoqueue', 'undonum', 'autoplay', 'changed',
'_promises', '_redrawTimer', 'firstscatter', 'hmlumcount', 'hmpixcount',
'numboxes', '_hoverTimer', '_lastHoverTime', '_transitionData',
'_transitioning'
'numboxes', '_transitionData', '_transitioning'
];

Plots.purge(gd);
Expand Down
6 changes: 3 additions & 3 deletions test/jasmine/tests/sankey_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ describe('sankey tests', function() {
function _hover(px, py) {
mouseEvent('mousemove', px, py);
mouseEvent('mouseover', px, py);
delete gd._lastHoverTime;
Lib.clearThrottle();
}

Plotly.plot(gd, mockCopy).then(function() {
Expand Down Expand Up @@ -454,7 +454,7 @@ describe('sankey tests', function() {
function _hover(px, py) {
mouseEvent('mousemove', px, py);
mouseEvent('mouseover', px, py);
delete gd._lastHoverTime;
Lib.clearThrottle();
}

Plotly.plot(gd, mockCopy)
Expand Down Expand Up @@ -489,7 +489,7 @@ describe('sankey tests', function() {
return function(elType) {
return new Promise(function(resolve, reject) {
gd.once(eventType, function(d) {
delete gd._lastHoverTime;
Lib.clearThrottle();
resolve(d);
});

Expand Down
2 changes: 1 addition & 1 deletion test/jasmine/tests/ternary_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe('ternary plots', function() {
'hoverlabel.font.family': [['Gravitas', 'Arial', 'Roboto']]
})
.then(function() {
delete gd._lastHoverTime;
Lib.clearThrottle();
mouseEvent('mousemove', pointPos[0], pointPos[1]);

var path = d3.select('g.hovertext').select('path');
Expand Down
2 changes: 1 addition & 1 deletion test/jasmine/tests/transform_sort_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ describe('Test sort transform interactions:', function() {
function hover(gd, id) {
return new Promise(function(resolve, reject) {
gd.once('plotly_hover', function(eventData) {
delete gd._lastHoverTime;
Lib.clearThrottle();
resolve(eventData);
});

Expand Down