Skip to content

Commit

Permalink
84 qenv prints plots to the device when running shiny app (#124)
Browse files Browse the repository at this point in the history
this fixes #84
related PR
insightsengineering/teal.modules.general#540

To prevent the IDE from displaying plots when using the eval_code()
method containing any graphic element, I utilized dev.new() before and
dev.off() after the eval_code() method definition. This approach opens
and closes the graphic device, effectively suppressing the plot outputs.

Now  Before		
✔️	 ✔️	"plot+observe" caused by observe (does not disply on ide)
✔️	 ❌	"pws+warn" caused by disable in verbatim_popup_srv
✔️	 ❌	"pws+tws" caused by table_with_settings
✔️ ❌ "tmg-scatterplot" caused by verbatim_popup_srv and renderDataTable



Sample code to test:
```
library(shiny)
library(ggplot2)
library(magrittr)
library(rtables)
pkgload::load_all("teal.widgets")
pkgload::load_all("teal.modules.general")
pkgload::load_all("teal.code")
library(teal)
library(scda)
ADSL <- synthetic_cdisc_data("latest")$adsl

ui_plot_noqenv <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    div(
      plotOutput(ns("plot_normal")),
      DT::dataTableOutput(ns("tab2"))
    )
  )
}
server_plot_noqenv <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('basic')
        plot(1:10)
        print(p)
      }))
    })

    output$plot_normal <- renderPlot(q2()[["p"]])
    output$tab2 <- DT::renderDataTable({
      q2()
      head(iris)
    })
  })
}

ui_plot <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    fluidRow(
      column(9, plotOutput(ns("plot_normal"))),
      column(3, verbatim_popup_ui(ns("pop2"), "SRC"))
    )
  )
}
server_plot <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('plot')
        plot(1:10)
        print(p)
      }))
    })

    output$plot_normal <- renderPlot(q2()[["p"]])
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}

ui_plot_and_table <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    fluidRow(
      column(9, plotOutput(ns("plot_normal"))),
      column(3, verbatim_popup_ui(ns("pop2"), "SRC"))
    ),
    DT::dataTableOutput(ns("table"))
  )
}
server_plot_and_table <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('plot+table')
        plot(1:10)
        print(p)
      }))
    })


    output$plot_normal <- renderPlot(q2()[["p"]])
    output$table <- DT::renderDataTable(q2()[["iris"]])
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}

ui_plot_and_observe <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    fluidRow(
      column(9, plotOutput(ns("plot_normal"))),
      column(3, verbatim_popup_ui(ns("pop2"), "SRC"))
    )
  )
}
server_plot_and_observe <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('plot+observe')
        plot(1:10)
        print(p)
      }))
    })

    output$plot_normal <- renderPlot(q2()[["p"]])
    observe(q2())
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}

ui_plot_uirender <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    uiOutput(ns("ui"))
  )
}
server_plot_uirender <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    output$ui <- renderUI({
        fluidRow(
        column(9, plotOutput(session$ns("plot_normal"))),
        column(3, verbatim_popup_ui(session$ns("pop2"), "SRC"))
      )
    })
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('plot+uirender')
        plot(1:10)
        print(p)
      }))
    })

    output$plot_normal <- renderPlot(q2()[["p"]])
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}

ui_pws <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    fluidRow(
      column(9, teal.widgets::plot_with_settings_ui(id = ns("p2"))),
      column(3, verbatim_popup_ui(ns("pop2"), "SRC"))
    )
  )
}
server_pws <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('pws')
        plot(1:10)
        print(p)
      }))
    })

    teal.widgets::plot_with_settings_srv(id = "p2", plot_r = reactive(q2()[["p"]]))
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}

ui_pws_and_warn <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    fluidRow(
      column(9, teal.widgets::plot_with_settings_ui(id = ns("p2"))),
      column(3, verbatim_popup_ui(ns("pop2"), "SRC")),
      column(3, verbatim_popup_ui(ns("pop2w"), "Warn"))
    )
  )
}
server_pws_and_warn <- function(id) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('pws+warn')
        plot(1:10)
        print(p)
      }))
    })

    teal.widgets::plot_with_settings_srv(id = "p2", plot_r = reactive(q2()[["p"]]))
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
    verbatim_popup_srv("pop2w", reactive(get_warnings(q2())), "Warn", disabled = reactive(is.null(get_warnings(q2()))))
  })
}

ui_pws_tws <- function(id) {
  ns <- NS(id)
  fluidPage(
    shinyjs::useShinyjs(),
    fluidRow(
      column(9, teal.widgets::plot_with_settings_ui(id = ns("p2"))),
      column(3, verbatim_popup_ui(ns("pop2"), "SRC"))
    ),
    teal.widgets::table_with_settings_ui(id = ns("t2"))
  )
}
server_pws_tws <- function(id) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('pws+tws')
        plot(1:10)
        print(p)
      }))
    })
    table_r <- reactive({
      q2()
      l <- basic_table() %>%
        split_cols_by("ARM") %>%
        analyze(c("SEX", "AGE"))
      build_table(l, DM)
    })

    teal.widgets::plot_with_settings_srv(id = "p2", plot_r = reactive(q2()[["p"]]))
    teal.widgets::table_with_settings_srv(id = "t2", table_r = reactive(table_r()))
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}
server_pws_tws_fix <- function(id) {
  moduleServer(id, function(input, output, session) {
    q2 <- reactive({
      new_qenv() |>
      eval_code(quote({
        iris[1:10, "Sepal.Width"] <- NA
        p <- ggplot(data = iris) + geom_point(aes(x = Sepal.Width, y = Petal.Width)) + ggtitle('pws+tws')
        plot(1:10)
        print(p)
      }))
    })
    table_r <- reactive({
      pdf(NULL)
      q2()
      dev.off()
      l <- basic_table() %>%
        split_cols_by("ARM") %>%
        analyze(c("SEX", "AGE"))
      build_table(l, DM)
    })

    teal.widgets::plot_with_settings_srv(id = "p2", plot_r = reactive(q2()[["p"]]))
    teal.widgets::table_with_settings_srv(id = "t2", table_r = reactive(table_r()))
    verbatim_popup_srv("pop2", reactive(paste(get_code(q2()), collapse = "\n")), "SRC")
  })
}

app <- teal::init(
  data = list(iris = iris, ADSL = ADSL),
  modules = list(
    module("basic", ui = ui_plot_noqenv, server = server_plot_noqenv),
    module("plot", ui = ui_plot, server = server_plot),
    module("plot+table", ui = ui_plot_and_table, server = server_plot_and_table), #
    module("plot+observe", ui = ui_plot_and_observe, server = server_plot_and_observe), # extra print by observe
    module("generic+renderUI", ui = ui_plot_uirender, server = server_plot_uirender),
    module("pws+warn", ui = ui_pws_and_warn, server = server_pws_and_warn), # extra print by verbatim_poppup
    module("pws", ui = ui_pws, server = server_pws),
    module("pws+tws", ui = ui_pws_tws, server = server_pws_tws), # extra print by tws
    module("pws+tws+fix", ui = ui_pws_tws, server = server_pws_tws_fix),
    tm_g_scatterplot( # extra print by multiple things
      label = "Scatterplot Choices",
      x = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          label = "Select variable:",
          choices = variable_choices(ADSL, c("AGE", "BMRKR1", "BMRKR2")),
          selected = "AGE",
          multiple = FALSE,
          fixed = FALSE
        )
      ),
      y = data_extract_spec(
        dataname = "ADSL",
        select = select_spec(
          label = "Select variable:",
          choices = variable_choices(ADSL, c("AGE", "BMRKR1", "BMRKR2")),
          selected = "BMRKR1",
          multiple = FALSE,
          fixed = FALSE
        )
      )
    )
  )
)

runApp(app)
```

---------

Signed-off-by: kartikeya kirar <kirar.kartikeya1@gmail.com>
Co-authored-by: kartikeya <kartikeya.kirar@unicle.life>
Co-authored-by: go_gonzo <dawid.kaledkowski@gmail.com>
Co-authored-by: Dawid Kałędkowski <6959016+gogonzo@users.noreply.github.com>
Co-authored-by: 27856297+dependabot-preview[bot]@users.noreply.github.com <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
6 people authored Jul 31, 2023
1 parent af7ee66 commit 6da5881
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 3 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Depends:
R (>= 4.0)
Imports:
checkmate,
grDevices,
lifecycle,
methods,
rlang,
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

S3method("[[",qenv.error)
export(concat)
export(dev_suppress)
export(eval_code)
export(get_code)
export(get_var)
Expand All @@ -17,6 +18,7 @@ exportMethods(get_warnings)
exportMethods(join)
exportMethods(new_qenv)
exportMethods(show)
import(grDevices)
import(shiny)
importFrom(lifecycle,badge)
importFrom(methods,show)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# teal.code 0.3.0.9008

* `dev_suppress` has been added to suppress rendering of plots on IDE.
* `chunks` have been removed. The new `qenv` object should be used instead. See the new `qenv` vignette in the package for further details.

# teal.code 0.3.0
Expand Down
2 changes: 2 additions & 0 deletions R/qenv-get_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
setGeneric("get_code", function(object, deparse = TRUE) {
# this line forces evaluation of object before passing to the generic
# needed for error handling to work properly
grDevices::pdf(nullfile())
on.exit(grDevices::dev.off())
object

standardGeneric("get_code")
Expand Down
6 changes: 5 additions & 1 deletion R/qenv-get_var.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
#' q2[["b"]]
#'
#' @export
setGeneric("get_var", function(object, var) standardGeneric("get_var"))
setGeneric("get_var", function(object, var) {
grDevices::pdf(nullfile())
on.exit(grDevices::dev.off())
standardGeneric("get_var")
})


#' @rdname get_var
Expand Down
2 changes: 2 additions & 0 deletions R/qenv-get_warnings.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
setGeneric("get_warnings", function(object) {
# this line forces evaluation of object before passing to the generic
# needed for error handling to work properly
grDevices::pdf(nullfile())
on.exit(grDevices::dev.off())
object

standardGeneric("get_warnings")
Expand Down
24 changes: 24 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ remove_enclosing_curly_braces <- function(x) {
}
}

#' Suppresses plot display in the IDE by opening a PDF graphics device
#'
#' This function opens a PDF graphics device using \code{\link[grDevices]{pdf}} to suppress
#' the plot display in the IDE. The purpose of this function is to avoid opening graphic devices
#' directly in the IDE.
#'
#' @param x lazy binding which generates the plot(s)
#'
#' @details The function uses \code{\link[base]{on.exit}} to ensure that the PDF graphics
#' device is closed (using \code{\link[grDevices]{dev.off}}) when the function exits,
#' regardless of whether it exits normally or due to an error. This is necessary to
#' clean up the graphics device properly and avoid any potential issues.
#'
#' @import grDevices
#'
#'
#' @examples
#' dev_suppress(plot(1:10))
#' @export
dev_suppress <- function(x) {
grDevices::pdf(nullfile())
on.exit(grDevices::dev.off())
force(x)
}

# converts vector of expressions to character
format_expression <- function(code) {
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ reference:
desc: "methods to get and modify values of qenv objects"
contents:
- concat
- dev_suppress
- eval_code
- get_code
- get_var
Expand Down
25 changes: 25 additions & 0 deletions man/dev_suppress.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions tests/testthat/test-qenv_eval_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ testthat::test_that("an error when calling eval_code returns a qenv.error object
})

testthat::test_that("a warning when calling eval_code returns a qenv object which has warnings", {
pdf(nullfile())
on.exit(dev.off())
q <- eval_code(new_qenv(), quote("iris_data <- iris"))
q <- eval_code(q, quote("p <- hist(iris_data[, 'Sepal.Length'], ff = '')"))
testthat::expect_s4_class(q, "qenv")
Expand Down
10 changes: 10 additions & 0 deletions tests/testthat/test-utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ testthat::test_that("remove_enclosing_curly_braces removes 4 spaces from lines e
)
})

test_that("dev_suppress function supress printing plot on IDE", {
expect_no_error(dev_suppress(plot(1:10)))

initial_pdf_count <- sum(dev.list())
dev_suppress(plot(1:10))
final_pdf_count <- sum(dev.list())

expect_equal(final_pdf_count, initial_pdf_count, label = "The PDF device should be closed after calling dev_suppress")
})

testthat::test_that("format expression concatenates results of remove_enclosing_curly_braces", {
code_list <- list(
quote("x <- 1"),
Expand Down

0 comments on commit 6da5881

Please sign in to comment.