diff --git a/R/image-interact-opts.R b/R/image-interact-opts.R index 53a6653ce..e6952a88a 100644 --- a/R/image-interact-opts.R +++ b/R/image-interact-opts.R @@ -65,9 +65,13 @@ dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) { #' @param clip Should the hover area be clipped to the plotting area? If FALSE, #' then the server will receive hover events even when the mouse is outside #' the plotting area, as long as it is still inside the image. +#' @param nullOutside If \code{FALSE} (the default), the value will stop +#' changing when the cursor exits the plotting area. If \code{TRUE}, the value +#' will be set to \code{NULL} when the mouse exits the plotting area. #' @export hoverOpts <- function(id = NULL, delay = 300, - delayType = c("debounce", "throttle"), clip = TRUE) { + delayType = c("debounce", "throttle"), clip = TRUE, + nullOutside = FALSE) { if (is.null(id)) stop("id must not be NULL") @@ -75,7 +79,8 @@ hoverOpts <- function(id = NULL, delay = 300, id = id, delay = delay, delayType = match.arg(delayType), - clip = clip + clip = clip, + nullOutside = nullOutside ) } diff --git a/man/hoverOpts.Rd b/man/hoverOpts.Rd index d97ace3f8..f8d923bf2 100644 --- a/man/hoverOpts.Rd +++ b/man/hoverOpts.Rd @@ -5,7 +5,7 @@ \title{Create an object representing hover options} \usage{ hoverOpts(id = NULL, delay = 300, delayType = c("debounce", "throttle"), - clip = TRUE) + clip = TRUE, nullOutside = FALSE) } \arguments{ \item{id}{Input value name. For example, if the value is \code{"plot_hover"}, @@ -23,6 +23,10 @@ while the cursor is moving, and wait until the cursor has been at rest for \item{clip}{Should the hover area be clipped to the plotting area? If FALSE, then the server will receive hover events even when the mouse is outside the plotting area, as long as it is still inside the image.} + +\item{nullOutside}{If \code{FALSE} (the default), the value will stop +changing when the cursor exits the plotting area. If \code{TRUE}, the value +will be set to \code{NULL} when the mouse exits the plotting area.} } \description{ This generates an object representing hovering options, to be passed as the diff --git a/srcjs/output_binding_image.js b/srcjs/output_binding_image.js index c67ade325..828582eb8 100644 --- a/srcjs/output_binding_image.js +++ b/srcjs/output_binding_image.js @@ -44,6 +44,7 @@ $.extend(imageOutputBinding, { hoverClip: OR($el.data('hover-clip'), true), hoverDelayType: OR($el.data('hover-delay-type'), 'debounce'), hoverDelay: OR($el.data('hover-delay'), 300), + hoverNullOutside: OR(strToBool($el.data('hover-null-outside')), false), brushId: $el.data('brush-id'), brushClip: OR(strToBool($el.data('brush-clip')), true), @@ -107,8 +108,10 @@ $.extend(imageOutputBinding, { if (opts.hoverId) { var hoverHandler = imageutils.createHoverHandler(opts.hoverId, - opts.hoverDelay, opts.hoverDelayType, opts.hoverClip, opts.coordmap); + opts.hoverDelay, opts.hoverDelayType, opts.hoverClip, + opts.hoverNullOutside, opts.coordmap); $el.on('mousemove.image_output', hoverHandler.mousemove); + $el.on('mouseout.image_output', hoverHandler.mouseout); $img.on('remove', hoverHandler.onRemoveImg); } @@ -396,8 +399,9 @@ imageutils.initCoordmap = function($el, coordmap) { // Returns a function that sends mouse coordinates, scaled to data space. // If that function is passed a null event, it will send null. - coordmap.mouseCoordinateSender = function(inputId, clip) { - clip = clip || true; + coordmap.mouseCoordinateSender = function(inputId, clip, nullOutside) { + if (clip === undefined) clip = true; + if (nullOutside === undefined) nullOutside = false; return function(e) { if (e === null) { @@ -406,7 +410,15 @@ imageutils.initCoordmap = function($el, coordmap) { } var offset = coordmap.mouseOffset(e); - // Ignore events outside of plotting region + // If outside of plotting region + if (!coordmap.isInPanel(offset)) { + if (nullOutside) { + exports.onInputChange(inputId, null); + return; + } + if (clip) + return; + } if (clip && !coordmap.isInPanel(offset)) return; var panel = coordmap.getPanel(offset); @@ -550,8 +562,10 @@ imageutils.createClickHandler = function(inputId, clip, coordmap) { }; -imageutils.createHoverHandler = function(inputId, delay, delayType, clip, coordmap) { - var sendHoverInfo = coordmap.mouseCoordinateSender(inputId, clip); +imageutils.createHoverHandler = function(inputId, delay, delayType, clip, + nullOutside, coordmap) +{ + var sendHoverInfo = coordmap.mouseCoordinateSender(inputId, clip, nullOutside); var hoverInfoSender; if (delayType === 'throttle') @@ -559,8 +573,16 @@ imageutils.createHoverHandler = function(inputId, delay, delayType, clip, coordm else hoverInfoSender = new Debouncer(null, sendHoverInfo, delay); + // What to do when mouse exits the image + var mouseout; + if (nullOutside) + mouseout = function() { hoverInfoSender.normalCall(null); }; + else + mouseout = function() {}; + return { mousemove: function(e) { hoverInfoSender.normalCall(e); }, + mouseout: mouseout, onRemoveImg: function() { hoverInfoSender.immediateCall(null); } }; };