diff --git a/R/controller.R b/R/controller.R index a7ed5bc..06271f5 100644 --- a/R/controller.R +++ b/R/controller.R @@ -1,7 +1,7 @@ Controller <- setRefClass( "Controller", fields = c("inputList", "envs", "session", "output", "expr", "ncharts", "charts", - "autoUpdate", "renderFunc"), + "autoUpdate", "renderFunc", "useCombineWidgets"), methods = list( initialize = function(expr, inputs, autoUpdate = TRUE) { @@ -13,6 +13,7 @@ Controller <- setRefClass( renderFunc <<- NULL session <<- NULL output <<- NULL + useCombineWidgets <<- FALSE charts <<- list() }, @@ -20,6 +21,10 @@ Controller <- setRefClass( session <<- session output <<- output inputList$session <<- session + for (env in envs) { + assign(".initial", FALSE, envir = env) + assign(".session", session, envir = env) + } }, getValue = function(name, chartId = 1) { @@ -59,6 +64,9 @@ Controller <- setRefClass( updateChart = function(chartId = 1) { catIfDebug("Update chart", chartId) charts[[chartId]] <<- eval(expr, envir = envs[[chartId]]) + if (useCombineWidgets) { + charts[[chartId]] <<- combineWidgets(charts[[chartId]]) + } renderShinyOutput(chartId) }, @@ -67,7 +75,8 @@ Controller <- setRefClass( }, renderShinyOutput = function(chartId) { - if (!is.null(renderFunc) & !is.null(output)) { + if (!is.null(renderFunc) & !is.null(output) & + is(charts[[chartId]], "htmlwidget")) { outputId <- get(".output", envir = envs[[chartId]]) output[[outputId]] <<- renderFunc(charts[[chartId]]) } @@ -104,6 +113,7 @@ Controller <- setRefClass( ) res$renderFunc <- renderFunc res$charts <- charts + res$useCombineWidgets <- useCombineWidgets res } ) diff --git a/R/inputs.R b/R/inputs.R index df8b4ee..d761799 100644 --- a/R/inputs.R +++ b/R/inputs.R @@ -568,7 +568,7 @@ mwGroup <- function(..., .display = TRUE) { tags$div( class="panel-body collapse", id=paste0("panel-body-", id), - tagList(htmlElements) + shiny::tagList(htmlElements) ) ) } diff --git a/R/manipulateWidget.R b/R/manipulateWidget.R index acf40c6..3642ec8 100644 --- a/R/manipulateWidget.R +++ b/R/manipulateWidget.R @@ -265,6 +265,11 @@ manipulateWidget <- function(.expr, ..., .updateBtn = FALSE, } controller$renderFunc <- renderFunction + if (useCombineWidgets) { + controller$useCombineWidgets <- TRUE + controller$charts <- lapply(controller$charts, combineWidgets) + } + dims <- .getRowAndCols(.compareOpts$ncharts, .compareOpts$nrow, .compareOpts$ncol) @@ -281,12 +286,15 @@ manipulateWidget <- function(.expr, ..., .updateBtn = FALSE, controller$setShinySession(output, session) controller$renderShinyOutputs() + message("Click on the 'OK' button to return to the R session.") + observe({ for (id in names(controller$inputList$inputs)) { controller$setValueById(id, input[[id]]) } }) + observeEvent(input$done, onDone(controller, .return, dims$nrow, dims$ncol)) } if (interactive()) { @@ -304,6 +312,6 @@ manipulateWidget <- function(.expr, ..., .updateBtn = FALSE, } else { # Other cases (Rmarkdown or non interactive execution). We return the initial # widget to not block the R execution. - mwReturn(controller$widgets, .return, controls$env$ind, dims$nrow, dims$ncol) + mwReturn(controller$charts, .return, controller$envs, dims$nrow, dims$ncol) } } diff --git a/R/mwServer_helpers.R b/R/mwServer_helpers.R index 2eac373..e3f7c52 100644 --- a/R/mwServer_helpers.R +++ b/R/mwServer_helpers.R @@ -93,14 +93,14 @@ getUpdateInputFun <- function(type) { #' #' @return a htmlwidget #' @noRd -onDone <- function(.expr, controls, .return = function(w, e) {w}, nrow = NULL, ncol = NULL) { - widgets <- lapply(controls$env$ind, function(e) { - assign(".initial", TRUE, envir = e) - assign(".session", NULL, envir = e) - eval(.expr, envir = e) - }) +onDone <- function(controller, .return = function(w, e) {w}, nrow = NULL, ncol = NULL) { + for (env in controller$envs) { + assign(".initial", TRUE, envir = env) + assign(".session", NULL, envir = env) + } + controller$updateCharts() - shiny::stopApp(mwReturn(widgets, .return, controls$env$ind, nrow, ncol)) + shiny::stopApp(mwReturn(controller$charts, .return, controls$env$ind, nrow, ncol)) } #' Function that takes a list of widgets and returns the first one if there is diff --git a/R/zzz.R b/R/zzz.R index fa8420a..74225d6 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,87 +1,87 @@ -# Copyright © 2016 RTE Réseau de transport d’électricité - -#' @name manipulateWidget-package -#' -#' @title Add even more interactivity to interactive charts -#' -#' @description -#' This package is largely inspired by the \code{manipulate} package from -#' Rstudio. It can be used to easily create graphical interface that let the -#' user modify the data or the parameters of an interactive chart. It also -#' provides the \code{\link{combineWidgets}} function to easily combine multiple -#' interactive charts in a single view. -#' -#' @details -#' \code{\link{manipulateWidget}} is the main function of the package. It -#' accepts an expression that generates an interactive chart (and more precisely -#' an \code{htmlwidget} object. See \url{http://www.htmlwidgets.org/} if you -#' have never heard about it) and a set of controls created with functions -#' \code{mwSlider}, \code{mwCheckbox}... which are used to dynamically change -#' values within the expression. Each time the user modifies the value of a -#' control, the expression is evaluated again and the chart is updated. Consider -#' the following code: -#' -#' \code{manipulateWidget(myPlotFun(country), country = mwSelect(c("BE", "DE", "ES", "FR")))} -#' -#' It will generate a graphical interface with a select input on its left with -#' options "BE", "DE", "ES", "FR". By default, at the beginning the value of the -#' variable \code{country} will be equal to the first choice of the -#' corresponding input. So the function will first execute -#' \code{myPlotFun("BE")} and the result will be displayed in the main panel of -#' the interface. If the user changes the value to "FR", then the expression -#' \code{myPlotFun("FR")} is evaluated and the new result is displayed. -#' -#' The interface also contains a button "Done". When the user clicks on it, the -#' last chart is returned. It can be stored in a variable, be modified by the -#' user, saved as a html file with \code{\link[htmlwidgets]{saveWidget}} from package -#' \code{htmlwidgets} or converted to a static image file with package -#' \code{webshot}. -#' -#' Finally one can easily create complex layouts thanks to function -#' \code{\link{combineWidgets}}. For instance, assume we want to see a map that -#' displays values of some variable for a given year, but on its right side we also -#' want to see the distributions of three variables. Then we could write: -#' -#' \preformatted{ -#' myPlotFun <- function(year, variable) { -#' combineWidgets( -#' ncol = 2, colSize = c(3, 1), -#' myMap(year, variable), -#' combineWidgets( -#' ncol = 1, -#' myHist(year, "V1"), -#' myHist(year, "V2"), -#' myHist(year, "V3"), -#' ) -#' ) -#' } -#' -#' manipulateWidget( -#' myPlotFun(year, variable), -#' year = mwSlider(2000, 2016, value = 2000), -#' variable = mwSelect(c("V1", "V2", "V3")) -#' ) -#' } -#' -#' Of course, \code{\link{combineWidgets}} can be used outside of -#' \code{\link{manipulateWidget}}. For instance, it can be used in an -#' Rmarkdown document to easily put together interactive charts. -#' -#' For more concrete examples of usage, you should look at the documentation and -#' especially the examples of \code{\link{manipulateWidget}} and -#' \code{\link{combineWidgets}}. -#' -#' @seealso \code{\link{manipulateWidget}}, \code{\link{combineWidgets}} -#' -#' @rdname manipulateWidget-package -#' @docType package -#' @importFrom shiny tags observe observeEvent reactive isolate icon tagAppendChild -#' @importFrom shiny tagAppendChildren fillPage fillRow -#' @importFrom miniUI miniContentPanel miniPage miniTabPanel miniTabstripPanel gadgetTitleBar -#' @importFrom htmlwidgets getDependency -#' @importFrom methods is -#' @importFrom utils getFromNamespace -#' @importFrom stats runif -NULL -# -globalVariables(c("mod", "multiple", "name", "type")) +# Copyright © 2016 RTE Réseau de transport d’électricité + +#' @name manipulateWidget-package +#' +#' @title Add even more interactivity to interactive charts +#' +#' @description +#' This package is largely inspired by the \code{manipulate} package from +#' Rstudio. It can be used to easily create graphical interface that let the +#' user modify the data or the parameters of an interactive chart. It also +#' provides the \code{\link{combineWidgets}} function to easily combine multiple +#' interactive charts in a single view. +#' +#' @details +#' \code{\link{manipulateWidget}} is the main function of the package. It +#' accepts an expression that generates an interactive chart (and more precisely +#' an \code{htmlwidget} object. See \url{http://www.htmlwidgets.org/} if you +#' have never heard about it) and a set of controls created with functions +#' \code{mwSlider}, \code{mwCheckbox}... which are used to dynamically change +#' values within the expression. Each time the user modifies the value of a +#' control, the expression is evaluated again and the chart is updated. Consider +#' the following code: +#' +#' \code{manipulateWidget(myPlotFun(country), country = mwSelect(c("BE", "DE", "ES", "FR")))} +#' +#' It will generate a graphical interface with a select input on its left with +#' options "BE", "DE", "ES", "FR". By default, at the beginning the value of the +#' variable \code{country} will be equal to the first choice of the +#' corresponding input. So the function will first execute +#' \code{myPlotFun("BE")} and the result will be displayed in the main panel of +#' the interface. If the user changes the value to "FR", then the expression +#' \code{myPlotFun("FR")} is evaluated and the new result is displayed. +#' +#' The interface also contains a button "Done". When the user clicks on it, the +#' last chart is returned. It can be stored in a variable, be modified by the +#' user, saved as a html file with \code{\link[htmlwidgets]{saveWidget}} from package +#' \code{htmlwidgets} or converted to a static image file with package +#' \code{webshot}. +#' +#' Finally one can easily create complex layouts thanks to function +#' \code{\link{combineWidgets}}. For instance, assume we want to see a map that +#' displays values of some variable for a given year, but on its right side we also +#' want to see the distributions of three variables. Then we could write: +#' +#' \preformatted{ +#' myPlotFun <- function(year, variable) { +#' combineWidgets( +#' ncol = 2, colSize = c(3, 1), +#' myMap(year, variable), +#' combineWidgets( +#' ncol = 1, +#' myHist(year, "V1"), +#' myHist(year, "V2"), +#' myHist(year, "V3"), +#' ) +#' ) +#' } +#' +#' manipulateWidget( +#' myPlotFun(year, variable), +#' year = mwSlider(2000, 2016, value = 2000), +#' variable = mwSelect(c("V1", "V2", "V3")) +#' ) +#' } +#' +#' Of course, \code{\link{combineWidgets}} can be used outside of +#' \code{\link{manipulateWidget}}. For instance, it can be used in an +#' Rmarkdown document to easily put together interactive charts. +#' +#' For more concrete examples of usage, you should look at the documentation and +#' especially the examples of \code{\link{manipulateWidget}} and +#' \code{\link{combineWidgets}}. +#' +#' @seealso \code{\link{manipulateWidget}}, \code{\link{combineWidgets}} +#' +#' @rdname manipulateWidget-package +#' @docType package +#' @importFrom shiny tags observe observeEvent reactive isolate icon tagAppendChild +#' @importFrom shiny tagAppendChildren fillPage fillRow +#' @importFrom miniUI miniContentPanel miniPage miniTabPanel miniTabstripPanel gadgetTitleBar +#' @importFrom htmlwidgets getDependency +#' @importFrom methods is new setRefClass +#' @importFrom utils getFromNamespace +#' @importFrom stats runif +NULL +# +globalVariables(c("mod", "multiple", "name", "type"))