rtemisseq / app.R
egenn's picture
init
977cc7d
raw
history blame
17.7 kB
# 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(
"<br><br>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")),
"<br>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")),
", <br>providing interactive visualization of sequence data.",
"<br>Created for the ", as.character(a("FTD CWOW", href = "https://cwow.ucsf.edu/", target = "_blank")),
".<br><br>",
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.<br/>",
"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("<b>", length(a3_obj()$Sequence), "</b>")),
# if (length(a3_obj()$UniprotID) > 0) {
# HTML("Uniprot ID:", paste0("<b>", a3_obj()$UniprotID, "</b>"))
# },
# 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(
# "<ul>",
# "<li>Hover over plot to see annotations.",
# "<li>Click on legend items to toggle visibility of annotations.",
# "<li>Double-click on legend items to isolate a single annotation type.",
# "<li>Click on top-right gear icon to change plot settings.",
# "</ul>"
# )
# ),
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()