# rtemisseq # ::rtemislive:: # 2024 EDG rtemis.org # shiny::runApp("./") # Setup library(rtemis) library(rtemisbio) library(shiny) library(bslib) library(htmltools) library(plotly) source("globals.R") source("data.R") # Colors primary <- "#72CDF4" info <- helpcol <- "#FEB2E0" success <- "#B4DC55" #' Create protein visualization shinylive app #' #' Visualize protein amino acid sequence and annotations using #' `dplot3_protein()` #' #' Set verbosity to 1 to monitor app progress #' #' @param default_theme Character: "dark" or "light" theme #' @param verbosity Integer: 0 = silent, 1 = verbose #' #' @author EDG #' @export #' @return A shiny app that can be converted to a shinylive app rtemisseq_version <- "0.2.6" seqvizlive <- function( default_theme = "dark", protein_plotly_height = "900px", jsonedit_height = "900px", verbosity = 0) { # Logo logo <- base64enc::dataURI( file = "./www/rtemisbio_gray.png", mime = "image/png" ) # Version platform <- sessionInfo()[["platform"]] svl <- paste0( "rtemisseq v.", rtemisseq_version, " | ", "rtemis v.0.97.3", utils::packageVersion("rtemis"), # doesn't work in shinylive " | ", "rtemisbio v.", utils::packageVersion("rtemisbio"), " | R v.", version$major, ".", version$minor, " | running on ", platform ) # Shinylive info shinylive_info <- if (substr(platform, 1, 4) == "wasm") { paste0( "

This application has been compiled to ", as.character(a("WebAssembly", href = "https://webassembly.org/", target = "_blank")), " using ", as.character(a("shinylive", href = "https://posit-dev.github.io/r-shinylive/", target = "_blank")), "
and is best viewed with the latest version of Chrome." ) } else { NULL } # UI page_navbar ---- ui <- function(request) { bslib::page_navbar( # Title ---- title = list( logo = a( img( src = logo, width = "140px", height = "auto", alt = "rtemisbio" ), href = "https://rtemis.org/rtemisbio", ) ), id = "rtemisbio", selected = "Welcome", footer = span( svl, " © 2024 EDG", style = "display: block; text-align: center; margin-top: 1em; margin-bottom: 1em;" ), # Theme ---- theme = bslib::bs_theme( bg = "#fff", fg = "#000", primary = primary, secondary = "#704071", success = success, info = info, `tooltip-bg` = "#303030", `tooltip-color` = helpcol, `tooltip-opacity` = 1, `tooltip-border-radius` = "10px", `tooltip-padding-x` = "1rem", `tooltip-padding-y` = "1rem", `tooltip-font-size` = "1rem" # base_font = bslib::font_google("Inter"), # doesn't work in shinylive # code_font = bslib::font_google("Fira Code") ) |> bs_add_rules(sass::sass_file("www/rtemislive.scss")), # Window title ---- window_title = "rtemisSeq", # Language ---- lang = "en", # tags$script(src = "globals.js"), # Nav Panels ---- ## [] Welcome ---- bslib::nav_panel( title = "Welcome", icon = bsicons::bs_icon("stars"), bslib::card( h4("Welcome to rtemisSeq", style = "text-align: center;"), card_body( class = "d-inline text-center", HTML(paste0( "rtemisSeq is a web interface for ", as.character(a("rtemisbio", href = "https://rtemis.org/rtemisbio", target = "_blank")), ",
providing interactive visualization of sequence data.", "
Created for the ", as.character(a("FTD CWOW", href = "https://cwow.ucsf.edu/", target = "_blank")), ".

", rthelp_inline( "To get started, use the navigation tabs at the top.", title = "Welcome" ), shinylive_info )), br(), br(), bslib::card_image( file = "./www/svl.webp", alt = "rtemislive", align = "center", border_radius = "all", fill = FALSE, width = "40%", class = "mx-auto" ) ) ) ), # /nav_panel Welcome ## [] Protein Visualization ---- bslib::nav_panel( title = "Protein Visualization", icon = bsicons::bs_icon("body-text"), card( full_screen = TRUE, class = "p-0", # allow items side by side ---- card_header( class = "d-flex justify-content-end", # tooltip UI output ---- uiOutput("ui_a3_tooltip"), # popover UI output ---- uiOutput("ui_a3_popover") ), layout_sidebar( fillable = TRUE, # Data Load Sidebar ---- sidebar = bslib::sidebar( uiOutput("ui_a3_load_switch"), # Switch between file upload and built-in data uiOutput("ui_a3_data_load"), # File upload or built-in data selection depending on switch uiOutput("ui_a3_data_info"), # Shows dataset info uiOutput("ui_a3_plot_button"), # Click to plot # uiOutput("ui_a3_tooltip") ), # Plot Output ---- # for shinylive, do not use plotlyOutput directly in ui !! # fails when no plot is rendered, unlike shiny uiOutput("ui_dplot3_protein") ) ) # bslib::navset_card_tab( # full_screen = TRUE, # # []] Sequence Viewer ---- # bslib::nav_panel( # title = "Sequence Viewer", # layout_sidebar( # # Data Load Sidebar ---- # sidebar = bslib::sidebar( # uiOutput("ui_a3_load_switch"), # Switch between file upload and built-in data # uiOutput("ui_a3_data_load"), # File upload or built-in data selection depending on switch # uiOutput("ui_a3_data_info"), # Shows dataset info # uiOutput("ui_a3_plot_button") # Click to plot # ), # # Plot Output ---- # # for shinylive, do not use plotlyOutput directly in ui !! # # fails when no plot is rendered, unlike shiny # uiOutput("ui_dplot3_protein") # ) # ), # # []] jsonedit ---- # bslib::nav_panel( # title = "Raw Data", # bslib::card( # bslib::card_title("Sequence & Annotation data"), # uiOutput("ui_jsonedit") # ) # ) # /nav_panel jsonedit # ) # /navset_card_tab ), # /nav_panel Protein Visualization ## [] About ---- bslib::nav_panel( title = "About", icon = bsicons::bs_icon("info-square"), bslib::card( card_image( file = "./www/rtemisbio_s.webp", href = "https://rtemis.org/rtemisbio", alt = "rtemislive", align = "center", border_radius = "all", fill = FALSE, width = "54%", class = "mx-auto" ), div( class = "d-inline text-center", HTML(paste0( "Created by the ", as.character(a("FTD CWOW", href = "https://cwow.ucsf.edu/", target = "_blank")), " Genomics & Transcriptomics core.
", "Powered by rtemis & rtemisbio (", as.character(a("rtemis.org", href = "https://rtemis.org", target = "_blank")), ")." )), br(), br(), a( img( src = "rtemis_gray.png", alt = "rtemis", align = "center", width = "190px" ), href = "https://rtemis.org", target = "_blank" ), align = "center" ) ) ), # /nav_panel About bslib::nav_spacer(), bslib::nav_item(input_dark_mode(id = "dark_mode", mode = default_theme)), header = list( # busy indicators ---- shinybusy::add_busy_spinner( spin = "orbit", # color = "#72CDF4", color = "#00ffff", timeout = 200, position = "bottom-left", onstart = FALSE ) ) ) # /ui /bslib::page_navbar } # /ui function # Server ---- server <- function(input, output, session) { # UI a3 load switch ---- output$ui_a3_load_switch <- shiny::renderUI({ if (verbosity > 0) { message("Rendering ui_a3_load_switch") } # Radio buttons: built-in data vs upload file ---- shiny::radioButtons( inputId = "a3_load_switch", label = "Data source", choices = list( `Built-in datasets` = "builtin", `File upload` = "upload" ), selected = "builtin" ) }) # UI a3 Data ---- output$ui_a3_data_load <- shiny::renderUI({ req(input$a3_load_switch) if (input$a3_load_switch == "upload") { # Upload a3 JSON file if (verbosity > 0) { message("Rendering ui_a3_data_load for file upload") } shiny::fileInput( inputId = "a3_file", label = "Upload a3 JSON file", buttonLabel = "Browse local files...", ) } else { # Select built-in data stored in ./data/ directory if (verbosity > 0) { message("Rendering ui_a3_data_load for built-in data selection") } shiny::selectizeInput( inputId = "a3_builtin_data", label = "Select built-in a3 dataset", choices = c("mapt_annot", "mapt_clv"), selected = "mapt_annot" ) } }) # /ui_a3_data_load # UI for a3 data info ---- output$ui_a3_data_info <- shiny::renderUI({ req(a3_obj()) if (verbosity > 0) { message("Rendering ui_a3_data_info") } bslib::card( bslib::card_title("a3 Dataset Info", container = htmltools::h6), bslib::card_body( # HTML("Sequence length:", paste0("", length(a3_obj()$Sequence), "")), # if (length(a3_obj()$UniprotID) > 0) { # HTML("Uniprot ID:", paste0("", a3_obj()$UniprotID, "")) # }, # if (length(a3_obj()$Reference) > 0) { # a("Reference", href = a3_obj()$Reference, target = "_blank") # }, summarize_a3(a3_obj()), fillable = FALSE ) # /card_body ) # /card }) # /ui_a3_data_info # UI for plot action button ---- output$ui_a3_plot_button <- shiny::renderUI({ req(a3_obj()) if (verbosity > 0) { message("Rendering ui_a3_plot_button") } bslib::input_task_button( "a3_plot_button", "Plot dataset", icon = bsicons::bs_icon("magic"), label_busy = "Drawing...", icon_busy = bsicons::bs_icon("clock-history"), type = "primary", auto_reset = TRUE ) }) # /ui_a3_plot_button # Load Dataset ---- a3_obj <- shiny::reactive({ req(input$a3_load_switch) if (input$a3_load_switch == "upload") { req(input$a3_file) if (verbosity > 0) { message("Loading a3 JSON file '", input$a3_file$datapath, "'") } dat <- read.a3json(input$a3_file$datapath) if (verbosity > 0) { message("Loaded dataset of class '", class(dat)[1], "'") } return(dat) } else { # Load built-in data from data.R objects req(input$a3_builtin_data) if (verbosity > 0) { message("Loading built-in a3 dataset '", input$a3_builtin_data, "'") } # read.3.json from data folder works fine in shiny app, not shinylive, # use objects from data.R instead # assign to dat object of name input$a3_builtin_data dat <- get(input$a3_builtin_data) if (verbosity > 0) { message("Loaded dataset of class '", class(dat)[1], "'") } return(dat) } }) # /a3_obj # dplot3 theme is "white" or "black" ---- dplot3_theme <- shiny::reactive({ req(input$dark_mode) if (input$dark_mode == "dark") { "black" } else { "white" } }) # /dplot3_theme # Render plotly ---- output$dplot3_protein <- plotly::renderPlotly({ req(a3_obj()) if (verbosity > 0) { message("Rendering dplot3_protein of object with class '", class(a3_obj())[1], "'") } plot( a3_obj(), theme = dplot3_theme(), marker.size = input$marker.size, font.size = input$font.size, ptm.marker.size = input$ptm.marker.size, clv.marker.size = input$clv.marker.size, bg = input$plot.bg, # legend.bg defaults to transparent, this sets paper bg plot.bg = input$plot.bg, marker.col = input$marker.col, n.per.row = if (input$n.per.row == "auto") NULL else as.integer(input$n.per.row) ) }) |> # /dplot3_protein bindEvent(input$a3_plot_button, input$a3_plot_update_button) # Create variable clicked that is TRUE after input$a3_plot_button is clicked clicked <- shiny::reactiveVal(FALSE) shiny::observeEvent(input$a3_plot_button, { clicked(TRUE) }) # UI a3 tooltip ---- output$ui_a3_tooltip <- shiny::renderUI({ if (clicked()) { bslib::tooltip( trigger = span( "Plot help", bsicons::bs_icon("info-circle", class = "text-info"), style = "text-align: right;", class = "rtanihi" ), div( # HTML( # paste0( # "" # ) # ), rhelplist( c( "Hover over plot to see annotations.", "Click on legend items to toggle visibility of annotations.", "Double-click on legend items to isolate a single annotation type.", "Click on top-right gear icon to change plot settings." ) ), style = "text-align: left;" ), # /div rt-tooltip placement = "bottom" ) # /tooltip } }) # /ui_a3_tooltip # UI a3 popover ---- output$ui_a3_popover <- shiny::renderUI({ if (clicked()) { popover( trigger = bsicons::bs_icon("gear", class = "ms-auto"), # trigger = span( # "Plot settings", bsicons::bs_icon("gear", class = "ms-auto"), # style = "text-align: right;" # shiny::selectizeInput("a3.theme", label = "Theme", choices = c("dark", "light"), selected = default_theme), shiny::sliderInput("marker.size", label = "Marker size", min = 1, max = 100, value = 28), shiny::sliderInput("font.size", label = "Font size", min = 1, max = 72, value = 18), if (length(a3_obj()$Annotations$PTM) > 0) shiny::sliderInput("ptm.marker.size", label = "PTM Marker size", min = .1, max = 36, value = 28 / 4.5), if (length(a3_obj()$Annotations$Cleavage_site) > 0) shiny::sliderInput("clv.marker.size", label = "Cleavage Site Marker size", min = .1, max = 36, value = 28 / 4), shinyWidgets::colorPickr( "plot.bg", label = "Plot background", selected = ifelse(input$dark_mode == "dark", "#191919", "#FFFFFF") ), shinyWidgets::colorPickr( "marker.col", label = "Marker color", selected = ifelse(input$dark_mode == "dark", "#3f3f3f", "#dfdfdf") ), textInput("n.per.row", "Number of AAs per row", value = "auto"), # tags$i("Click on 'Plot dataset' to update render after changing settings."), bslib::input_task_button( "a3_plot_update_button", "Update rendering", icon = bsicons::bs_icon("arrow-clockwise"), label_busy = "Drawing...", icon_busy = bsicons::bs_icon("clock-history"), type = "primary", auto_reset = TRUE ), title = "Plot settings", placement = "auto" # options = list(trigger = "hover focus click") ) # /popover } }) # /ui_a3_popover # UI dplot3_protein ---- output$ui_dplot3_protein <- renderUI({ if (clicked() == FALSE || is.null(a3_obj())) { rthelp( "Select Data Source and Click 'Plot dataset' on the left.", title = "Protein Visualization " ) } else { plotly::plotlyOutput( "dplot3_protein", width = "100%", height = protein_plotly_height ) } }) # /ui_dplot3_protein # Render JSON ---- output$jsonedit <- listviewer::renderJsonedit({ req(a3_obj()) listviewer::jsonedit(a3_obj()) }) # /json # UI jsonedit ---- output$ui_jsonedit <- shiny::renderUI({ listviewer::jsoneditOutput( "jsonedit", height = jsonedit_height ) }) } # /server # Shiny app ---- shiny::shinyApp(ui = ui, server = server, enableBookmarking = "url") } # seqvizlive seqvizlive()