mirror of
https://github.com/rstudio/shiny.git
synced 2026-04-07 03:00:20 -04:00
Allow shared brush IDs
This commit is contained in:
@@ -927,7 +927,10 @@ imageOutput <- function(outputId, width = "100%", height="400px",
|
||||
#' be able to draw a rectangle in the plotting area and drag it around. The
|
||||
#' value will be a named list with \code{xmin}, \code{xmax}, \code{ymin}, and
|
||||
#' \code{ymax} elements indicating the brush area. To control the brush
|
||||
#' behavior, use \code{\link{brushOpts}}.
|
||||
#' behavior, use \code{\link{brushOpts}}. Multiple
|
||||
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
|
||||
#' value; brushing one image or plot will cause any other brushes with the
|
||||
#' same \code{id} to disappear.
|
||||
#' @inheritParams textOutput
|
||||
#' @note The arguments \code{clickId} and \code{hoverId} only work for R base
|
||||
#' graphics (see the \pkg{\link{graphics}} package). They do not work for
|
||||
|
||||
@@ -91,7 +91,10 @@ hoverOpts <- function(id = NULL, delay = 300,
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_brush"},
|
||||
#' then the coordinates will be available as \code{input$plot_brush}.
|
||||
#' then the coordinates will be available as \code{input$plot_brush}. Multiple
|
||||
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
|
||||
#' value; brushing one image or plot will cause any other brushes with the
|
||||
#' same \code{id} to disappear.
|
||||
#' @param fill Fill color of the brush.
|
||||
#' @param stroke Outline color of the brush.
|
||||
#' @param opacity Opacity of the brush
|
||||
|
||||
@@ -1407,6 +1407,8 @@ $.extend(imageOutputBinding, {
|
||||
// * Bind those event handlers to events.
|
||||
// * Insert the new image.
|
||||
|
||||
var outputId = this.getId(el);
|
||||
|
||||
var $el = $(el);
|
||||
// Load the image before emptying, to minimize flicker
|
||||
var img = null;
|
||||
@@ -1522,7 +1524,7 @@ $.extend(imageOutputBinding, {
|
||||
$el.on('selectstart.image_output', function() { return false; });
|
||||
|
||||
var brushHandler = imageutils.createBrushHandler(opts.brushId, $el, opts,
|
||||
opts.coordmap);
|
||||
opts.coordmap, outputId);
|
||||
$el.on('mousedown.image_output', brushHandler.mousedown);
|
||||
$el.on('mousemove.image_output', brushHandler.mousemove);
|
||||
|
||||
@@ -1986,7 +1988,7 @@ imageutils.createHoverHandler = function(inputId, delay, delayType, clip,
|
||||
|
||||
// Returns a brush handler object. This has three public functions:
|
||||
// mousedown, mousemove, and onRemoveImg.
|
||||
imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
imageutils.createBrushHandler = function(inputId, $el, opts, coordmap, outputId) {
|
||||
// Parameter: expand the area in which a brush can be started, by this
|
||||
// many pixels in all directions. (This should probably be a brush option)
|
||||
var expandPixels = 20;
|
||||
@@ -1994,6 +1996,25 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
// Represents the state of the brush
|
||||
var brush = imageutils.createBrush($el, opts, coordmap, expandPixels);
|
||||
|
||||
// Brush IDs can span multiple image/plot outputs. When an output is brushed,
|
||||
// if a brush with the same ID is active on a different image/plot, it must
|
||||
// be dismissed (but without sending any data to the server). We implement
|
||||
// this by sending the shiny-internal:brushed event to all plots, and letting
|
||||
// each plot decide for itself what to do.
|
||||
//
|
||||
// The decision to have the event sent to each plot (as opposed to a single
|
||||
// event triggered on, say, the document) was made to make cleanup easier;
|
||||
// listening on an event on the document would prevent garbage collection
|
||||
// of plot outputs that are removed from the document.
|
||||
$el.on("shiny-internal:brushed.image_output", function(e, coords) {
|
||||
// If the new brush shares our ID but not our output element ID, we
|
||||
// need to clear our brush (if any).
|
||||
if (coords.brushId === inputId && coords.outputId !== outputId) {
|
||||
$el.data("mostRecentBrush", false);
|
||||
brush.reset();
|
||||
}
|
||||
});
|
||||
|
||||
// Set cursor to one of 7 styles. We need to set the cursor on the whole
|
||||
// el instead of the brush div, because the brush div has
|
||||
// 'pointer-events:none' so that it won't intercept pointer events.
|
||||
@@ -2010,6 +2031,10 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
// We're in a new or reset state
|
||||
if (isNaN(coords.xmin)) {
|
||||
exports.onInputChange(inputId, null);
|
||||
// Must tell other brushes to clear.
|
||||
imageOutputBinding.find(document).trigger("shiny-internal:brushed", {
|
||||
brushId: inputId, outputId: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2028,8 +2053,14 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
|
||||
coords.direction = opts.brushDirection;
|
||||
|
||||
coords.brushId = inputId;
|
||||
coords.outputId = outputId;
|
||||
|
||||
// Send data to server
|
||||
exports.onInputChange(inputId, coords);
|
||||
|
||||
$el.data("mostRecentBrush", true);
|
||||
imageOutputBinding.find(document).trigger("shiny-internal:brushed", coords);
|
||||
}
|
||||
|
||||
var brushInfoSender;
|
||||
@@ -2196,17 +2227,26 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
|
||||
}
|
||||
|
||||
// Brush maintenance: When an image is re-rendered, the brush must either
|
||||
// be removed (if brushResetOnNew) or imported (if !brushResetOnNew). The
|
||||
// "mostRecentBrush" bit is to ensure that when multiple outputs share the
|
||||
// same brush ID, inactive brushes don't send null values up to the server.
|
||||
|
||||
// This should be called when the img (not the el) is removed
|
||||
function onRemoveImg() {
|
||||
if (opts.brushResetOnNew) {
|
||||
brush.reset();
|
||||
brushInfoSender.immediateCall();
|
||||
if ($el.data("mostRecentBrush")) {
|
||||
brush.reset();
|
||||
brushInfoSender.immediateCall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.brushResetOnNew) {
|
||||
brush.importOldBrush();
|
||||
brushInfoSender.immediateCall();
|
||||
if ($el.data("mostRecentBrush")) {
|
||||
brush.importOldBrush();
|
||||
brushInfoSender.immediateCall();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
File diff suppressed because one or more lines are too long
5
inst/www/shared/shiny.min.js
vendored
5
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -10,7 +10,10 @@ brushOpts(id = NULL, fill = "#9cf", stroke = "#036", opacity = 0.25,
|
||||
}
|
||||
\arguments{
|
||||
\item{id}{Input value name. For example, if the value is \code{"plot_brush"},
|
||||
then the coordinates will be available as \code{input$plot_brush}.}
|
||||
then the coordinates will be available as \code{input$plot_brush}. Multiple
|
||||
\code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
|
||||
value; brushing one image or plot will cause any other brushes with the
|
||||
same \code{id} to disappear.}
|
||||
|
||||
\item{fill}{Fill color of the brush.}
|
||||
|
||||
|
||||
@@ -63,7 +63,10 @@ accessible via \code{input$plot_brush}. Brushing means that the user will
|
||||
be able to draw a rectangle in the plotting area and drag it around. The
|
||||
value will be a named list with \code{xmin}, \code{xmax}, \code{ymin}, and
|
||||
\code{ymax} elements indicating the brush area. To control the brush
|
||||
behavior, use \code{\link{brushOpts}}.}
|
||||
behavior, use \code{\link{brushOpts}}. Multiple
|
||||
\code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
|
||||
value; brushing one image or plot will cause any other brushes with the
|
||||
same \code{id} to disappear.}
|
||||
|
||||
\item{clickId}{Deprecated; use \code{click} instead. Also see the
|
||||
\code{\link{clickOpts}} function.}
|
||||
|
||||
@@ -11,6 +11,8 @@ $.extend(imageOutputBinding, {
|
||||
// * Bind those event handlers to events.
|
||||
// * Insert the new image.
|
||||
|
||||
var outputId = this.getId(el);
|
||||
|
||||
var $el = $(el);
|
||||
// Load the image before emptying, to minimize flicker
|
||||
var img = null;
|
||||
@@ -126,7 +128,7 @@ $.extend(imageOutputBinding, {
|
||||
$el.on('selectstart.image_output', function() { return false; });
|
||||
|
||||
var brushHandler = imageutils.createBrushHandler(opts.brushId, $el, opts,
|
||||
opts.coordmap);
|
||||
opts.coordmap, outputId);
|
||||
$el.on('mousedown.image_output', brushHandler.mousedown);
|
||||
$el.on('mousemove.image_output', brushHandler.mousemove);
|
||||
|
||||
@@ -590,7 +592,7 @@ imageutils.createHoverHandler = function(inputId, delay, delayType, clip,
|
||||
|
||||
// Returns a brush handler object. This has three public functions:
|
||||
// mousedown, mousemove, and onRemoveImg.
|
||||
imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
imageutils.createBrushHandler = function(inputId, $el, opts, coordmap, outputId) {
|
||||
// Parameter: expand the area in which a brush can be started, by this
|
||||
// many pixels in all directions. (This should probably be a brush option)
|
||||
var expandPixels = 20;
|
||||
@@ -598,6 +600,25 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
// Represents the state of the brush
|
||||
var brush = imageutils.createBrush($el, opts, coordmap, expandPixels);
|
||||
|
||||
// Brush IDs can span multiple image/plot outputs. When an output is brushed,
|
||||
// if a brush with the same ID is active on a different image/plot, it must
|
||||
// be dismissed (but without sending any data to the server). We implement
|
||||
// this by sending the shiny-internal:brushed event to all plots, and letting
|
||||
// each plot decide for itself what to do.
|
||||
//
|
||||
// The decision to have the event sent to each plot (as opposed to a single
|
||||
// event triggered on, say, the document) was made to make cleanup easier;
|
||||
// listening on an event on the document would prevent garbage collection
|
||||
// of plot outputs that are removed from the document.
|
||||
$el.on("shiny-internal:brushed.image_output", function(e, coords) {
|
||||
// If the new brush shares our ID but not our output element ID, we
|
||||
// need to clear our brush (if any).
|
||||
if (coords.brushId === inputId && coords.outputId !== outputId) {
|
||||
$el.data("mostRecentBrush", false);
|
||||
brush.reset();
|
||||
}
|
||||
});
|
||||
|
||||
// Set cursor to one of 7 styles. We need to set the cursor on the whole
|
||||
// el instead of the brush div, because the brush div has
|
||||
// 'pointer-events:none' so that it won't intercept pointer events.
|
||||
@@ -614,6 +635,10 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
// We're in a new or reset state
|
||||
if (isNaN(coords.xmin)) {
|
||||
exports.onInputChange(inputId, null);
|
||||
// Must tell other brushes to clear.
|
||||
imageOutputBinding.find(document).trigger("shiny-internal:brushed", {
|
||||
brushId: inputId, outputId: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -632,8 +657,14 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
|
||||
coords.direction = opts.brushDirection;
|
||||
|
||||
coords.brushId = inputId;
|
||||
coords.outputId = outputId;
|
||||
|
||||
// Send data to server
|
||||
exports.onInputChange(inputId, coords);
|
||||
|
||||
$el.data("mostRecentBrush", true);
|
||||
imageOutputBinding.find(document).trigger("shiny-internal:brushed", coords);
|
||||
}
|
||||
|
||||
var brushInfoSender;
|
||||
@@ -800,17 +831,26 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
|
||||
|
||||
}
|
||||
|
||||
// Brush maintenance: When an image is re-rendered, the brush must either
|
||||
// be removed (if brushResetOnNew) or imported (if !brushResetOnNew). The
|
||||
// "mostRecentBrush" bit is to ensure that when multiple outputs share the
|
||||
// same brush ID, inactive brushes don't send null values up to the server.
|
||||
|
||||
// This should be called when the img (not the el) is removed
|
||||
function onRemoveImg() {
|
||||
if (opts.brushResetOnNew) {
|
||||
brush.reset();
|
||||
brushInfoSender.immediateCall();
|
||||
if ($el.data("mostRecentBrush")) {
|
||||
brush.reset();
|
||||
brushInfoSender.immediateCall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.brushResetOnNew) {
|
||||
brush.importOldBrush();
|
||||
brushInfoSender.immediateCall();
|
||||
if ($el.data("mostRecentBrush")) {
|
||||
brush.importOldBrush();
|
||||
brushInfoSender.immediateCall();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user