Skip to content

Commit 129714b

Browse files
committed
Allow shared brush IDs
1 parent 6782355 commit 129714b

File tree

9 files changed

+113
-20
lines changed

9 files changed

+113
-20
lines changed

R/bootstrap.R

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,10 @@ imageOutput <- function(outputId, width = "100%", height="400px",
927927
#' be able to draw a rectangle in the plotting area and drag it around. The
928928
#' value will be a named list with \code{xmin}, \code{xmax}, \code{ymin}, and
929929
#' \code{ymax} elements indicating the brush area. To control the brush
930-
#' behavior, use \code{\link{brushOpts}}.
930+
#' behavior, use \code{\link{brushOpts}}. Multiple
931+
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
932+
#' value; brushing one image or plot will cause any other brushes with the
933+
#' same \code{id} to disappear.
931934
#' @inheritParams textOutput
932935
#' @note The arguments \code{clickId} and \code{hoverId} only work for R base
933936
#' graphics (see the \pkg{\link{graphics}} package). They do not work for

R/image-interact-opts.R

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ hoverOpts <- function(id = NULL, delay = 300,
9191
#' \code{\link{plotOutput}}.
9292
#'
9393
#' @param id Input value name. For example, if the value is \code{"plot_brush"},
94-
#' then the coordinates will be available as \code{input$plot_brush}.
94+
#' then the coordinates will be available as \code{input$plot_brush}. Multiple
95+
#' \code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
96+
#' value; brushing one image or plot will cause any other brushes with the
97+
#' same \code{id} to disappear.
9598
#' @param fill Fill color of the brush.
9699
#' @param stroke Outline color of the brush.
97100
#' @param opacity Opacity of the brush

inst/www/shared/shiny.js

Lines changed: 46 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.min.js

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/www/shared/shiny.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/brushOpts.Rd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ brushOpts(id = NULL, fill = "#9cf", stroke = "#036", opacity = 0.25,
1010
}
1111
\arguments{
1212
\item{id}{Input value name. For example, if the value is \code{"plot_brush"},
13-
then the coordinates will be available as \code{input$plot_brush}.}
13+
then the coordinates will be available as \code{input$plot_brush}. Multiple
14+
\code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
15+
value; brushing one image or plot will cause any other brushes with the
16+
same \code{id} to disappear.}
1417

1518
\item{fill}{Fill color of the brush.}
1619

man/plotOutput.Rd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ accessible via \code{input$plot_brush}. Brushing means that the user will
6363
be able to draw a rectangle in the plotting area and drag it around. The
6464
value will be a named list with \code{xmin}, \code{xmax}, \code{ymin}, and
6565
\code{ymax} elements indicating the brush area. To control the brush
66-
behavior, use \code{\link{brushOpts}}.}
66+
behavior, use \code{\link{brushOpts}}. Multiple
67+
\code{imageOutput}/\code{plotOutput} calls may share the same \code{id}
68+
value; brushing one image or plot will cause any other brushes with the
69+
same \code{id} to disappear.}
6770

6871
\item{clickId}{Deprecated; use \code{click} instead. Also see the
6972
\code{\link{clickOpts}} function.}

srcjs/output_binding_image.js

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ $.extend(imageOutputBinding, {
1111
// * Bind those event handlers to events.
1212
// * Insert the new image.
1313

14+
var outputId = this.getId(el);
15+
1416
var $el = $(el);
1517
// Load the image before emptying, to minimize flicker
1618
var img = null;
@@ -126,7 +128,7 @@ $.extend(imageOutputBinding, {
126128
$el.on('selectstart.image_output', function() { return false; });
127129

128130
var brushHandler = imageutils.createBrushHandler(opts.brushId, $el, opts,
129-
opts.coordmap);
131+
opts.coordmap, outputId);
130132
$el.on('mousedown.image_output', brushHandler.mousedown);
131133
$el.on('mousemove.image_output', brushHandler.mousemove);
132134

@@ -590,14 +592,33 @@ imageutils.createHoverHandler = function(inputId, delay, delayType, clip,
590592

591593
// Returns a brush handler object. This has three public functions:
592594
// mousedown, mousemove, and onRemoveImg.
593-
imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
595+
imageutils.createBrushHandler = function(inputId, $el, opts, coordmap, outputId) {
594596
// Parameter: expand the area in which a brush can be started, by this
595597
// many pixels in all directions. (This should probably be a brush option)
596598
var expandPixels = 20;
597599

598600
// Represents the state of the brush
599601
var brush = imageutils.createBrush($el, opts, coordmap, expandPixels);
600602

603+
// Brush IDs can span multiple image/plot outputs. When an output is brushed,
604+
// if a brush with the same ID is active on a different image/plot, it must
605+
// be dismissed (but without sending any data to the server). We implement
606+
// this by sending the shiny-internal:brushed event to all plots, and letting
607+
// each plot decide for itself what to do.
608+
//
609+
// The decision to have the event sent to each plot (as opposed to a single
610+
// event triggered on, say, the document) was made to make cleanup easier;
611+
// listening on an event on the document would prevent garbage collection
612+
// of plot outputs that are removed from the document.
613+
$el.on("shiny-internal:brushed.image_output", function(e, coords) {
614+
// If the new brush shares our ID but not our output element ID, we
615+
// need to clear our brush (if any).
616+
if (coords.brushId === inputId && coords.outputId !== outputId) {
617+
$el.data("mostRecentBrush", false);
618+
brush.reset();
619+
}
620+
});
621+
601622
// Set cursor to one of 7 styles. We need to set the cursor on the whole
602623
// el instead of the brush div, because the brush div has
603624
// 'pointer-events:none' so that it won't intercept pointer events.
@@ -614,6 +635,10 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
614635
// We're in a new or reset state
615636
if (isNaN(coords.xmin)) {
616637
exports.onInputChange(inputId, null);
638+
// Must tell other brushes to clear.
639+
imageOutputBinding.find(document).trigger("shiny-internal:brushed", {
640+
brushId: inputId, outputId: null
641+
});
617642
return;
618643
}
619644

@@ -632,8 +657,14 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
632657

633658
coords.direction = opts.brushDirection;
634659

660+
coords.brushId = inputId;
661+
coords.outputId = outputId;
662+
635663
// Send data to server
636664
exports.onInputChange(inputId, coords);
665+
666+
$el.data("mostRecentBrush", true);
667+
imageOutputBinding.find(document).trigger("shiny-internal:brushed", coords);
637668
}
638669

639670
var brushInfoSender;
@@ -800,17 +831,26 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
800831

801832
}
802833

834+
// Brush maintenance: When an image is re-rendered, the brush must either
835+
// be removed (if brushResetOnNew) or imported (if !brushResetOnNew). The
836+
// "mostRecentBrush" bit is to ensure that when multiple outputs share the
837+
// same brush ID, inactive brushes don't send null values up to the server.
838+
803839
// This should be called when the img (not the el) is removed
804840
function onRemoveImg() {
805841
if (opts.brushResetOnNew) {
806-
brush.reset();
807-
brushInfoSender.immediateCall();
842+
if ($el.data("mostRecentBrush")) {
843+
brush.reset();
844+
brushInfoSender.immediateCall();
845+
}
808846
}
809847
}
810848

811849
if (!opts.brushResetOnNew) {
812-
brush.importOldBrush();
813-
brushInfoSender.immediateCall();
850+
if ($el.data("mostRecentBrush")) {
851+
brush.importOldBrush();
852+
brushInfoSender.immediateCall();
853+
}
814854
}
815855

816856
return {

0 commit comments

Comments
 (0)