@@ -11,6 +11,8 @@ $.extend(imageOutputBinding, {
11
11
// * Bind those event handlers to events.
12
12
// * Insert the new image.
13
13
14
+ var outputId = this . getId ( el ) ;
15
+
14
16
var $el = $ ( el ) ;
15
17
// Load the image before emptying, to minimize flicker
16
18
var img = null ;
@@ -126,7 +128,7 @@ $.extend(imageOutputBinding, {
126
128
$el . on ( 'selectstart.image_output' , function ( ) { return false ; } ) ;
127
129
128
130
var brushHandler = imageutils . createBrushHandler ( opts . brushId , $el , opts ,
129
- opts . coordmap ) ;
131
+ opts . coordmap , outputId ) ;
130
132
$el . on ( 'mousedown.image_output' , brushHandler . mousedown ) ;
131
133
$el . on ( 'mousemove.image_output' , brushHandler . mousemove ) ;
132
134
@@ -590,14 +592,33 @@ imageutils.createHoverHandler = function(inputId, delay, delayType, clip,
590
592
591
593
// Returns a brush handler object. This has three public functions:
592
594
// mousedown, mousemove, and onRemoveImg.
593
- imageutils . createBrushHandler = function ( inputId , $el , opts , coordmap ) {
595
+ imageutils . createBrushHandler = function ( inputId , $el , opts , coordmap , outputId ) {
594
596
// Parameter: expand the area in which a brush can be started, by this
595
597
// many pixels in all directions. (This should probably be a brush option)
596
598
var expandPixels = 20 ;
597
599
598
600
// Represents the state of the brush
599
601
var brush = imageutils . createBrush ( $el , opts , coordmap , expandPixels ) ;
600
602
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
+
601
622
// Set cursor to one of 7 styles. We need to set the cursor on the whole
602
623
// el instead of the brush div, because the brush div has
603
624
// 'pointer-events:none' so that it won't intercept pointer events.
@@ -614,6 +635,10 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
614
635
// We're in a new or reset state
615
636
if ( isNaN ( coords . xmin ) ) {
616
637
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
+ } ) ;
617
642
return ;
618
643
}
619
644
@@ -632,8 +657,14 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
632
657
633
658
coords . direction = opts . brushDirection ;
634
659
660
+ coords . brushId = inputId ;
661
+ coords . outputId = outputId ;
662
+
635
663
// Send data to server
636
664
exports . onInputChange ( inputId , coords ) ;
665
+
666
+ $el . data ( "mostRecentBrush" , true ) ;
667
+ imageOutputBinding . find ( document ) . trigger ( "shiny-internal:brushed" , coords ) ;
637
668
}
638
669
639
670
var brushInfoSender ;
@@ -800,17 +831,26 @@ imageutils.createBrushHandler = function(inputId, $el, opts, coordmap) {
800
831
801
832
}
802
833
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
+
803
839
// This should be called when the img (not the el) is removed
804
840
function onRemoveImg ( ) {
805
841
if ( opts . brushResetOnNew ) {
806
- brush . reset ( ) ;
807
- brushInfoSender . immediateCall ( ) ;
842
+ if ( $el . data ( "mostRecentBrush" ) ) {
843
+ brush . reset ( ) ;
844
+ brushInfoSender . immediateCall ( ) ;
845
+ }
808
846
}
809
847
}
810
848
811
849
if ( ! opts . brushResetOnNew ) {
812
- brush . importOldBrush ( ) ;
813
- brushInfoSender . immediateCall ( ) ;
850
+ if ( $el . data ( "mostRecentBrush" ) ) {
851
+ brush . importOldBrush ( ) ;
852
+ brushInfoSender . immediateCall ( ) ;
853
+ }
814
854
}
815
855
816
856
return {
0 commit comments