.panelRepopulated <- "Repopulated"
.panelReactivated <- "Reactivated"
.panelResaved <- "Resaved"
.panelNorender <- "Norender"
#' Child propagating observer
#'
#' This function sets up a central observer for regenerating all output based on
#' requests from \code{\link{.mark_panel_as_modified}}.
#' The observer will also decide whether child panels need to be regenerated
#' based on the panels that have been marked as modified.
#'
#' @param se A \linkS4class{SummarizedExperiment} object containing the current dataset.
#' @param session The Shiny session object from the server function.
#' @param pObjects An environment containing global parameters generated in the \code{\link{iSEE}} app.
#' @param rObjects A reactive list of values generated in the \code{\link{iSEE}} app.
#'
#' @return Observers are created in the server function in which this is called.
#' A \code{NULL} value is invisibly returned.
#'
#' @details
#' The architecture is that the other observers should request changes to panels via
#' \code{\link{.mark_panel_as_modified}} (or derivatives like \code{\link{.requestUpdate}}.
#' Once all of the requests are collated, the child propagating observer will run
#' to actually regenerate the modified panels via \code{\link{.generateOutput}}.
#' The output is cached in \code{pObjects$cached} for rapid re-use by the
#' \code{\link{.renderOutput}} functions via \code{\link{.retrieveOutput}}.
#'
#' We use this single-observer system to guarantee topological order of execution.
#' Previous attempts used observers to reactive variables to recurse through the multiple selection tree;
#' however, this would potentially hit the same node multiple times,
#' resulting in redundant execution of the same output generation.
#' An even more previous attempt moved output generation into \code{\link{.renderOutput}},
#' but this was even worse as it did not guarantee that \code{pObjects$contents} were regenerated for parents before children.
#'
#' When requesting a change to a panel \code{x}, the output is typically regenerated by \code{\link{.generateOutput}}.
#' The exception is when the requested mode has \dQuote{Norender} in which case regeneration is skipped.
#' This is useful in cases where the change involves an alteration of a selection that only affects children,
#' e.g., see \code{\link{.create_table_observers}}.
#'
#' We also have three different modes to determine what to do with that panel's children:
#' \itemize{
#' \item \dQuote{Repopulated}.
#' This is used when the population of points changes in \code{x},
#' usually due to restriction of selections from transmitters upstream of \code{x}.
#' Bumping may trigger replotting of the children of \code{x}, based on whether the type of selection they are receiving
#' (active, union, saved) is present in \code{x}.
#' It will also change the modification mode of the child to \dQuote{Repopulated} if it is selecting by restriction.
#' \item \dQuote{Reactivated}.
#' This is used when the current selection of points in \code{x} changes, e.g., due to changes in the lasso or brush.
#' It will trigger replotting of the children of \code{x} if they are receiving the active or union selection.
#' \item \dQuote{Resaved}.
#' This is used when the saved selection of points in \code{X} changes.
#' It will trigger replotting of the children of \code{x} if they are receiving the relevant saved or union selection.
#' }
#'
#' @author Aaron Lun
#'
#' @rdname INTERNAL_child_propagation_observer
#' @importFrom shiny observeEvent onFlushed
#' @importFrom igraph topo_sort adjacent_vertices
.create_child_propagation_observer <- function(se, session, pObjects, rObjects) {
# nocov start
if (!is.null(session)) {
# Run this just in case we haven't triggered the observer below on start-up
# (in which case the app will refuse to respond to the # first user input).
# This occasionally occurs for very well-behaved Panels that do not trigger
# further changes to their 'input' fields upon initialization.
onFlushed(function() pObjects$initialized <- TRUE, session=session)
}
observeEvent(rObjects$modified, {
if (!isTRUE(pObjects$initialized)) { # Avoid running this on app start and double-generating output.
pObjects$initialized <- TRUE
rObjects$modified <- list()
return(NULL)
}
modified <- rObjects$modified
if (length(modified)==0L) { # Avoid recursion from the wiping.
return(NULL)
}
rObjects$modified <- list()
# Looping over panels in topological order, accumulating changes so that
# we only ever call .generateOutput once. Note that we must loop over
# `ordering` rather than `modified` to ensure that any children of earlier
# panels are computing off up-to-date version of the parent panels.
graph <- pObjects$selection_links
ordering <- names(topo_sort(graph, mode="out"))
for (idx in seq_along(ordering)) {
current_panel_name <- ordering[idx]
if (!current_panel_name %in% names(modified)) {
next
}
instance <- pObjects$memory[[current_panel_name]]
status <- modified[[current_panel_name]]
if (!.panelNorender %in% status) {
# Generating self and marking it for re-rendering.
.safe_reactive_bump(rObjects, paste0(current_panel_name, "_", .flagOutputUpdate))
p.out <- .generateOutput(instance, se, all_memory=pObjects$memory, all_contents=pObjects$contents)
pObjects$contents[[current_panel_name]] <- p.out$contents
pObjects$cached[[current_panel_name]] <- p.out
}
# Setting up various parameters to decide how to deal with children.
if (!length(status)) {
next
}
re_populated <- .panelRepopulated %in% status
re_active <- .panelReactivated %in% status
re_saved <- .panelResaved %in% status
children <- names(adjacent_vertices(graph, v=current_panel_name, mode="out")[[1]])
if (!length(children)) {
next
}
transmit_dim <- .multiSelectionDimension(instance)
if (transmit_dim!="row" && transmit_dim!="column") {
next
}
has_active <- .multiSelectionHasActive(instance)
n_saved <- .any_saved_selection(instance, count=TRUE)
has_saved <- n_saved > 0L
# Looping over children and deciding whether they need to be regenerated.
for (child in children) {
child_instance <- pObjects$memory[[child]]
if (!.multiSelectionResponsive(child_instance, transmit_dim)) {
next
}
regenerate <- FALSE
if (re_populated && (has_active || has_saved)) {
regenerate <- TRUE
} else if (re_saved || re_active) {
regenerate <- TRUE
}
if (regenerate) {
# Implicit convertion to character(0), so as to trigger
# the call to .generateOutput later.
previous <- as.character(modified[[child]])
if (.multiSelectionRestricted(child_instance)) {
previous <- union(previous, .panelRepopulated)
}
# Wiping out selections in the child if receiving a new
# selection from the parent invalidates its own selections.
if (.multiSelectionInvalidated(child_instance)) {
if (.multiSelectionHasActive(child_instance)) {
pObjects$memory[[child]] <- .multiSelectionClear(pObjects$memory[[child]])
previous <- union(previous, .panelReactivated)
}
if (.any_saved_selection(child_instance)) {
slot(pObjects$memory[[child]], .multiSelectHistory) <- list()
previous <- union(previous, .panelResaved)
}
}
modified[[child]] <- previous
}
}
}
}, priority=-1L, ignoreInit=TRUE)
# nocov end
invisible(NULL)
}
#' Mark panel as modified
#'
#' Mark a panel as being modified, along with the modification mode that affects how the modification propagates to children.
#'
#' @param panel_name String containing the name of a panel.
#' @param mode Character vector of any length containing modification modes.
#' If empty, no change is propagated to the children.
#' See below for a description of valid modes.
#' @param rObjects A reactive list of values generated in the \code{\link{iSEE}} app.
#'
#' @section Modes of modification:
#' Valid modes of panel modifications are defined at the top of the file \code{R/observers_child.R}.
#'
#' \describe{
#' \item{\code{"Repopulated"}}{Used to indicate that the panel needs re-rendering following a change of restriction on the data shown in the panel or a change in incoming multiple selection.}
#' \item{\code{"Reactivated"}}{Used to indicate the panel needs re-rendering following a change that does not alter data shown in the panel (e.g., clear invalidated selections, zoom)}
#' \item{\code{"Resaved"}}{Used to indicate that the panel needs re-rendering following a change in saved selection (e.g., add active selection to saved selections, clear saved selections).}
#' \item{\code{"Norender"}}{Disable re-rendering of panel itself. Used to trigger re-rendering of panels receiving selections from the panel.}
#' }
#'
#' @return
#' \code{rObjects$modified} to include the new \code{mode} for \code{panel_name}.
#' A \code{NULL} is invisibly returned.
#'
#' @author Aaron Lun
#'
#' @seealso
#' \code{\link{.requestUpdate}} and \code{\link{.requestCleanUpdate}},
#' which call this function.
#'
#' @importFrom shiny isolate
#' @rdname INTERNAL_mark_panel_as_modified
.mark_panel_as_modified <- function(panel_name, mode, rObjects) {
# Do NOT simplify to `rObjects$modified[[panel_name]] <-`,
# as this performs an un-`isolate`d extraction that exposes
# a potential infinite recursion bug.
modified <- isolate(rObjects$modified)
modified[[panel_name]] <- union(modified[[panel_name]], mode)
rObjects$modified <- modified
invisible(NULL)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.