|
|
|
|
|
|
|
|
|
|
|
|
|
rtemisseq_version <- "0.4.0" |
|
library(rtemis) |
|
library(rtemisbio) |
|
library(shiny) |
|
library(bslib) |
|
library(htmltools) |
|
library(plotly) |
|
source("globals.R") |
|
source("data.R") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rtemisseq <- function( |
|
default_theme = "dark", |
|
protein_plotly_height = "900px", |
|
jsonedit_height = "900px", |
|
verbosity = 0L |
|
) { |
|
|
|
logo <- base64enc::dataURI( |
|
file = "./www/rtemisseq_gray.png", |
|
mime = "image/png" |
|
) |
|
|
|
platform <- sessionInfo()[["platform"]] |
|
svl <- paste0( |
|
"rtemisseq v", |
|
rtemisseq_version, |
|
" | ", |
|
"rtemisbio v", |
|
utils::packageVersion("rtemisbio"), |
|
" | ", |
|
"rtemis v", |
|
utils::packageVersion("rtemis"), |
|
" | R v", |
|
version$major, |
|
".", |
|
version$minor, |
|
" | running on ", |
|
platform |
|
) |
|
|
|
|
|
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 <- function(request) { |
|
bslib::page_navbar( |
|
|
|
title = list( |
|
logo = a( |
|
img( |
|
src = logo, |
|
width = "140px", |
|
height = "auto", |
|
alt = "rtemisseq" |
|
), |
|
href = "https://rtemis.org/rtemisseq", |
|
) |
|
), |
|
id = "rtemisseq", |
|
selected = "Welcome", |
|
footer = span( |
|
svl, |
|
" © 2024 EDG", |
|
style = "display: block; text-align: center; margin-top: 1em; margin-bottom: 1em;" |
|
), |
|
|
|
theme = bslib::bs_theme( |
|
`tooltip-opacity` = 1, |
|
`tooltip-border-radius` = "10px", |
|
`tooltip-padding-x` = "1rem", |
|
`tooltip-padding-y` = "1rem", |
|
`tooltip-font-size` = "1rem" |
|
) |> |
|
bs_add_rules(sass::sass_file("www/rtemislive.scss")), |
|
|
|
window_title = "rtemisSeq", |
|
|
|
lang = "en", |
|
|
|
|
|
bslib::nav_panel( |
|
title = "Welcome", |
|
icon = bsicons::bs_icon("stars"), |
|
bslib::card( |
|
card_body( |
|
class = "d-inline text-center", |
|
h4("Welcome to rtemisSeq", style = "text-align: center;"), |
|
br(), |
|
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/rtemisseq-splash.webp", |
|
alt = "rtemisseq", |
|
align = "center", |
|
border_radius = "all", |
|
fill = FALSE, |
|
width = "40%", |
|
class = "mx-auto" |
|
) |
|
) |
|
) |
|
), |
|
|
|
bslib::nav_panel( |
|
title = "Protein Visualization", |
|
icon = bsicons::bs_icon("body-text"), |
|
card( |
|
full_screen = TRUE, |
|
class = "p-0", |
|
|
|
card_header( |
|
class = "d-flex justify-content-end", |
|
|
|
uiOutput("ui_a3_tooltip"), |
|
|
|
uiOutput("ui_a3_popover") |
|
), |
|
layout_sidebar( |
|
fillable = TRUE, |
|
|
|
sidebar = bslib::sidebar( |
|
uiOutput("ui_a3_load_switch"), |
|
uiOutput("ui_a3_data_load"), |
|
uiOutput("ui_a3_data_info"), |
|
uiOutput("ui_a3_plot_button"), |
|
|
|
), |
|
|
|
|
|
|
|
uiOutput("ui_dplot3_protein") |
|
) |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
), |
|
|
|
bslib::nav_panel( |
|
title = "PDB Viewer", |
|
icon = bsicons::bs_icon("box"), |
|
card( |
|
full_screen = TRUE, |
|
class = "p-0", |
|
|
|
card_header( |
|
class = "d-flex justify-content-end", |
|
|
|
|
|
|
|
|
|
), |
|
layout_sidebar( |
|
fillable = TRUE, |
|
|
|
sidebar = bslib::sidebar( |
|
uiOutput("ui_pdb_settings"), |
|
|
|
|
|
|
|
|
|
|
|
), |
|
|
|
NGLVieweR::NGLVieweROutput("pdb", width = "100%", height = "900px") |
|
) |
|
) |
|
), |
|
|
|
bslib::nav_panel( |
|
title = "About", |
|
icon = bsicons::bs_icon("info-square"), |
|
bslib::card( |
|
class = "mx-auto", |
|
bslib::card_body( |
|
div( |
|
class = "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" |
|
)), |
|
")." |
|
)) |
|
), |
|
card_image( |
|
file = "./www/rtemisbio.webp", |
|
href = "https://rtemis.org/rtemisbio", |
|
alt = "rtemisbio", |
|
align = "center", |
|
border_radius = "all", |
|
fill = FALSE, |
|
width = "54%", |
|
class = "mx-auto" |
|
), |
|
div( |
|
class = "text-center", |
|
a( |
|
img( |
|
src = "rtemis_gray.png", |
|
alt = "rtemis", |
|
width = "190px" |
|
), |
|
href = "https://rtemis.org", |
|
target = "_blank" |
|
) |
|
) |
|
) |
|
) |
|
), |
|
bslib::nav_spacer(), |
|
bslib::nav_item(input_dark_mode(id = "dark_mode", mode = default_theme)), |
|
header = list( |
|
|
|
shinybusy::add_busy_spinner( |
|
spin = "orbit", |
|
color = "#FF8F28", |
|
timeout = 200, |
|
position = "bottom-left", |
|
onstart = FALSE |
|
) |
|
) |
|
) |
|
} |
|
|
|
|
|
server <- function(input, output, session) { |
|
|
|
output$ui_a3_load_switch <- shiny::renderUI({ |
|
if (verbosity > 0) { |
|
message("Rendering ui_a3_load_switch") |
|
} |
|
|
|
shiny::radioButtons( |
|
inputId = "a3_load_switch", |
|
label = "Data source", |
|
choices = list( |
|
`Built-in datasets` = "builtin", |
|
`GitHub repository` = "github", |
|
`File upload` = "upload" |
|
), |
|
selected = "builtin" |
|
) |
|
}) |
|
|
|
|
|
|
|
output$ui_github_files_update_button <- shiny::renderUI({ |
|
if (verbosity > 0) { |
|
message("Rendering ui_github_files_update_button") |
|
} |
|
bslib::input_task_button( |
|
"github_files_update_button", |
|
"Update GitHub file list", |
|
icon = bsicons::bs_icon("arrow-clockwise"), |
|
label_busy = "Updating...", |
|
icon_busy = bsicons::bs_icon("clock-history"), |
|
type = "primary", |
|
auto_reset = TRUE |
|
) |
|
}) |
|
|
|
|
|
github_files <- reactive({ |
|
get_github_files( |
|
repo_owner = "rtemis-org", |
|
repo_name = "seq-data", |
|
verbosity = verbosity |
|
) |
|
}) |> |
|
bindEvent(input$github_files_update_button) |
|
|
|
|
|
output$ui_github_files <- shiny::renderUI({ |
|
req(github_files()) |
|
if (verbosity > 0) { |
|
message("Rendering ui_github_files") |
|
} |
|
shiny::selectizeInput( |
|
inputId = "github_file", |
|
label = "Select a file from the GitHub repository", |
|
choices = github_files()$file_name, |
|
selected = github_files()$file_name[1] |
|
) |
|
}) |
|
|
|
|
|
output$ui_a3_data_load <- shiny::renderUI({ |
|
req(input$a3_load_switch) |
|
if (input$a3_load_switch == "upload") { |
|
|
|
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 if (input$a3_load_switch == "github") { |
|
|
|
if (verbosity > 0) { |
|
message("Rendering ui_a3_data_load for GitHub file list") |
|
} |
|
|
|
shiny::tagList( |
|
uiOutput("ui_github_files"), |
|
uiOutput("ui_github_files_update_button") |
|
) |
|
} else { |
|
|
|
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_PTM", |
|
"MAPT_Cathepsin_Cleavage", |
|
"MAPT_APP_Cleavage_3_4", |
|
"MAPT_APP_Cleavage_4_5", |
|
"MAPT_APP_Cleavage_5_5", |
|
"MAPT_APP_Cleavage_7_4", |
|
"MAPT_Citrullination" |
|
), |
|
selected = "MAPT_PTM" |
|
) |
|
} |
|
}) |
|
|
|
|
|
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( |
|
summarize_a3(a3_obj()), |
|
fillable = FALSE |
|
) |
|
) |
|
}) |
|
|
|
|
|
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 |
|
) |
|
}) |
|
|
|
|
|
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 if (input$a3_load_switch == "github") { |
|
|
|
req(input$github_file) |
|
if (verbosity > 0) { |
|
message("Downloading GitHub file '", input$github_file, "'") |
|
} |
|
|
|
jsondat <- httr::GET( |
|
github_files()[ |
|
github_files()$file_name == input$github_file, |
|
"download_url" |
|
] |
|
) |> |
|
httr::content(as = "text", encoding = "UTF-8") |
|
dat <- as.a3(jsonlite::parse_json( |
|
jsondat, |
|
simplifyVector = TRUE, |
|
simplifyMatrix = FALSE |
|
)) |
|
return(dat) |
|
} else { |
|
|
|
req(input$a3_builtin_data) |
|
if (verbosity > 0) { |
|
message("Loading built-in a3 dataset '", input$a3_builtin_data, "'") |
|
} |
|
|
|
|
|
|
|
dat <- get(input$a3_builtin_data) |
|
if (verbosity > 0) { |
|
message("Loaded dataset of class '", class(dat)[1], "'") |
|
} |
|
return(dat) |
|
} |
|
}) |
|
|
|
|
|
dplot3_theme <- shiny::reactive({ |
|
req(input$dark_mode) |
|
if (input$dark_mode == "dark") { |
|
"black" |
|
} else { |
|
"white" |
|
} |
|
}) |
|
|
|
|
|
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, |
|
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) |
|
) |
|
}) |> |
|
bindEvent(input$a3_plot_button, input$a3_plot_update_button) |
|
|
|
|
|
clicked <- shiny::reactiveVal(FALSE) |
|
shiny::observeEvent(input$a3_plot_button, { |
|
clicked(TRUE) |
|
}) |
|
|
|
|
|
output$ui_a3_tooltip <- shiny::renderUI({ |
|
bslib::tooltip( |
|
trigger = span( |
|
"Plot help", |
|
bsicons::bs_icon("info-circle", class = "text-info"), |
|
style = "text-align: right;", |
|
class = "rtanihi" |
|
), |
|
rthelplist( |
|
c( |
|
"Select Data Source (built-in, GitHub, or upload)", |
|
"Click 'Plot dataset' to render the plot - repeat after changing datasets.", |
|
"Click on legend items to toggle visibility of annotations.", |
|
"Double-click on legend items to isolate a single annotation type.", |
|
"Hover over plot to see annotations.", |
|
"Click on top-right gear icon to change plot settings.", |
|
"Use camera icon (first on top right menu within plot) to download plot." |
|
) |
|
), |
|
placement = "bottom" |
|
) |
|
}) |
|
|
|
|
|
output$ui_a3_popover <- shiny::renderUI({ |
|
if (clicked()) { |
|
popover( |
|
trigger = bsicons::bs_icon("gear", class = "ms-auto"), |
|
|
|
|
|
|
|
|
|
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"), |
|
|
|
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" |
|
|
|
) |
|
} else { |
|
popover( |
|
trigger = bsicons::bs_icon("gear", class = "ms-auto"), |
|
HTML( |
|
"Please select dataset from the left sidebar and click 'Plot dataset'.<br>Plot settings will appear here after the plot is rendered." |
|
) |
|
) |
|
} |
|
}) |
|
|
|
|
|
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 |
|
) |
|
} |
|
}) |
|
|
|
|
|
output$jsonedit <- listviewer::renderJsonedit({ |
|
req(a3_obj()) |
|
listviewer::jsonedit(a3_obj()) |
|
}) |
|
|
|
|
|
output$ui_jsonedit <- shiny::renderUI({ |
|
listviewer::jsoneditOutput( |
|
"jsonedit", |
|
height = jsonedit_height |
|
) |
|
}) |
|
|
|
|
|
output$ui_pdb_settings <- renderUI({ |
|
list( |
|
shiny::textInput( |
|
inputId = "pdbcode", |
|
label = "PDB code", |
|
value = "2MZ7" |
|
), |
|
shiny::selectInput( |
|
inputId = "pdbrepresentation", |
|
label = "Representation type", |
|
choices = c( |
|
"cartoon", |
|
"ball+stick", |
|
"line", |
|
"surface", |
|
"ribbon" |
|
), |
|
selected = "cartoon" |
|
), |
|
shiny::selectInput( |
|
inputId = "pdbcolorscheme", |
|
label = "Color Scheme", |
|
choices = c( |
|
"residueindex", |
|
"element", |
|
"hydrophobicity", |
|
"occupancy", |
|
"partialcharge" |
|
), |
|
selected = "residueindex" |
|
), |
|
shiny::radioButtons( |
|
inputId = "pdbanimate", |
|
label = hilite1("Animation"), |
|
choices = c("Off", "Rock", "Spin"), |
|
selected = "Off", |
|
inline = TRUE |
|
) |
|
) |
|
}) |
|
|
|
|
|
output$pdb <- NGLVieweR::renderNGLVieweR({ |
|
req(input$pdbcode, input$pdbrepresentation, input$pdbcolorscheme) |
|
NGLVieweR::NGLVieweR(input$pdbcode) |> |
|
NGLVieweR::addRepresentation( |
|
input$pdbrepresentation, |
|
param = list(color = input$pdbcolorscheme) |
|
) |> |
|
NGLVieweR::stageParameters(backgroundColor = "#161616") |> |
|
NGLVieweR::setQuality("high") |> |
|
NGLVieweR::setFocus(0) |
|
}) |
|
|
|
observeEvent(input$pdbanimate, { |
|
if (input$pdbanimate == "Rock") { |
|
NGLVieweR::NGLVieweR_proxy("pdb") |> NGLVieweR::updateRock(TRUE) |
|
} else if (input$pdbanimate == "Spin") { |
|
NGLVieweR::NGLVieweR_proxy("pdb") |> NGLVieweR::updateSpin(TRUE) |
|
} else { |
|
NGLVieweR::NGLVieweR_proxy("pdb") |> |
|
NGLVieweR::updateRock(FALSE) |> |
|
NGLVieweR::updateSpin(FALSE) |
|
} |
|
}) |
|
} |
|
|
|
|
|
shiny::shinyApp(ui = ui, server = server, enableBookmarking = "url") |
|
} |
|
|
|
rtemisseq() |
|
|