The example below is using ggtree in which I can brush the tips in the phylogeny and add an annotation label ("clade"). Steps to get the app going -
load the tree - called vert.tree
brush over (highlight) tips (test with human and lemur) and press the 'annotate tree' button to add the label in red.
What I want to do is add another annotation onto the tree while maintaining the first annotation (human and lemur). For example, a second label for the pig and cow tips. Essentially, I want to be able to add a line onto a phylogenetic tree based on user input and then repeat that based on second input from the user while maintaining the first line on the image. Currently, the label gets reset every time I brush a different pair so only one annotation is displayed at a time.
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
library(shiny)
library(treeio)
library(ggtree)
library(phytools)
library(ape)
#make phylogenetic tree
text.string <-"(((((((cow, pig),whale),(bat,(lemur,human))),(robin,iguana)),coelacanth),gold_fish),shark);"
#read in the tree
vert.tree<-ape::read.tree(text=text.string)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Test"),
actionButton("add_annotation","Add clade annotation"),
# Show a plot of the generated distribution
mainPanel(plotOutput("treeDisplay", brush ="plot_brush")
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#reactive that holds base tree - this is how I am building the base tree
make_tree <- reactive({
ggtree::ggtree(vert.tree)+
ggtree::geom_tiplab()+
ggplot2::xlim(NA, 10)})
#render base tree
output$treeDisplay <- renderPlot({
make_tree()
})
#reactive that holds the brushed points on a plot
dataWithSelection <- reactive({
brushedPoints(make_tree()$data, input$plot_brush)
})
#add to label to vector if isTip == True
dataWithSelection2 <- reactive({
tipVector <- c()
for (i in 1:length(dataWithSelection()$label)){ if(dataWithSelection()$isTip[i] == TRUE) tipVector <- c(tipVector,dataWithSelection()$label[i])}
return(tipVector)
})
# incorporate the tipVector information for adding layer
layer <- reactive({
ggtree::geom_cladelabel(node=phytools::findMRCA(ape::as.phylo(make_tree()), dataWithSelection2()), label = "Clade", color = "red")
})
#display that layer onto the tree
observeEvent(input$add_annotation, {
output$treeDisplay <- renderPlot({make_tree() + layer()})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Suggestions greatly appreciated!
updated to include a base tree (vert.tree)
Hope you found the solution already, but if not, here is an approach.
First it helps to do the problem in a non-shiny setting. What we need is a list that accumulates vectors of tips. Then we cycle over this list to generate annotations:
tree_plot <-
ggtree::ggtree(vert.tree) +
ggtree::geom_tiplab() +
ggplot2::xlim(NA, 10)
tip_vector <- list(c("human", "lemur"), c("pig", "cow"))
make_layer <- function(tree, tips, label, color) {
ggtree::geom_cladelabel(
node = phytools::findMRCA(ape::as.phylo(tree), tips),
label = label,
color = color
)
}
x + lapply(1:2, function(i)
make_layer(
tree_plot,
tips = tip_vector[[i]],
label = paste("Clade", i),
color = "red"
))
The key bit is in the lapply call, where generate the annotation layer for each member of the tip_vector list.
Now that this is working, we go to shiny. In your app, every time you click add annotation the brushed points data frame is refreshed and your tip vector is just a vector of the newly brushed tips. Any previously selected clades are forgotten.
To remember these, we can introduce two reactive values. One n_annotations is a numeric reactiveVal counting how many times we click add annotation. The other annotations is a reactiveValues list which stores all the brushed clades under the names paste0("ann", n_annotations()).
Then, the actual adding of the layer of annotations proceeds as in the non-reactive example with lapply cycling over the reactiveValues.
App code:
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
library(shiny)
library(treeio)
library(ggtree)
library(phytools)
library(ape)
#make phylogenetic tree
text.string <-"(((((((cow, pig),whale),(bat,(lemur,human))),(robin,iguana)),coelacanth),gold_fish),shark);"
#read in the tree
vert.tree<-ape::read.tree(text=text.string)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Test"),
actionButton("add_annotation","Add clade annotation"),
# Show a plot of the generated distribution
mainPanel(plotOutput("treeDisplay", brush ="plot_brush"),
plotOutput("treeDisplay2")
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#reactive that holds base tree - this is how I am building the base tree
make_tree <- reactive({
ggtree::ggtree(vert.tree) +
ggtree::geom_tiplab() +
ggplot2::xlim(NA, 10)
})
#render base tree
output$treeDisplay <- renderPlot({
make_tree()
})
# Initialize a reactive value and set to zero
n_annotations <- reactiveVal(0)
annotations <- reactiveValues()
#reactive that holds the brushed points on a plot
dataWithSelection <- reactive({
brushedPoints(make_tree()$data, input$plot_brush)
})
#add to label to vector if isTip == True
dataWithSelection2 <- eventReactive(input$plot_brush, {
tipVector <- c()
for (i in 1:length(dataWithSelection()$label)) {
if (dataWithSelection()$isTip[i] == TRUE)
tipVector <- c(tipVector, dataWithSelection()$label[i])
}
tipVector
})
make_layer <- function(tree, tips, label, color) {
ggtree::geom_cladelabel(
node = phytools::findMRCA(ape::as.phylo(tree), tips),
label = label,
color = color
)
}
#display that layer onto the tree
anno_plot <- eventReactive(input$add_annotation, {
# update the reactive value
new <- n_annotations() + 1
n_annotations(new)
annotations[[paste0("ann", n_annotations())]] <- dataWithSelection2()
plt <-
make_tree() +
lapply(1:n_annotations(), function(i)
make_layer(
make_tree(),
tips = annotations[[paste0("ann", i)]],
label = paste("Clade", i),
color = "red"
))
return(plt)
})
output$treeDisplay2 <- renderPlot({
anno_plot()
})
}
# Run the application
shinyApp(ui = ui, server = server)
Edit: how the reactive values work without the phylo stuff
I tried to comment this thoroughly.
ui <- basicPage(
actionButton("add_anno", "Add annotation"),
helpText("n_annotation is counting clicks"),
textOutput("n_anno"),
helpText("clades is accumulating clades"),
verbatimTextOutput("clades")
)
server <- function(input, output) {
# this initializes a reactive value
# and sets the initial state to 0
n_anno <- reactiveVal(0)
# makes an empty reactive list
# this can be populated and index
# like a normal list
# e.g., clades[["first"]] <- c("bird", "lizard")
clades <- reactiveValues()
observeEvent(input$add_anno, {
# increment the number of clicks
new_count <- n_anno() + 1
# update the reactiveValue
# works the same way we initialized it
# except instead of zero we set the incremented value
n_anno(new_count)
# making a name for an element in the clades list
# we use the n_anno number of clicks to increment the clades
# message just prints it on console
message( paste0("clade", n_anno() ))
# populate the list of clades for annotations
clades[[ paste0("clade", n_anno() ) ]] <- sample(LETTERS, 3)
})
output$n_anno <- renderText(n_anno())
output$clades <- renderPrint(
str(reactiveValuesToList(clades))
)
}
shinyApp(ui, server)
hmmm - okay when I tested your suggestion
dataWithSelection2 <- reactive({
tipVector <- c()
for (i in 1:length(dataWithSelection()$label)){
if(!is.null(dataWithSelection()$isTip[i])) {
tipVector <- c(tipVector,dataWithSelection()$label[i])
}
}
return(tipVector)
})
I get the error: missing value where TRUE/FALSE needed....
I have a dataframe that has these columns:
document, user, month, views
I am using a selectInput to filter the data by document.
I want to plot a (Plotly) line chart of views per month, for each user, for the selected document.
E.g. If one filters to a document for which ten users exist, I want to display ten plots, each showing the relevant user's views per month.
At current:
- I filter the data to the selected document (dplyr).
- I pass the filtered data to a function.
- In the function, I loop through the current document's users.
- In each loop, I filter the data to the current user (dplyr), and append a Plotly output to a output list.
- At the end of the function, I return the output list.
- The result of the function is assigne to a UI output.
The app successfully runs, but where the plots should display, I get a Result must have length x, not y error.
How would you go about this? I appreciate any advice you can give me.
For security reasons I cannot share my existing code, sorry - I understand that it's not very useful.
Edit: I've created a minimal reproducible example, based on this.
The process has changed slightly from my original question, mainly that I'm not using a separate function.
library(plotly)
library(tidyverse)
# DATA
data <- data.frame(
document= c("doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2"),
user= c("user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4"),
month= as.Date(c("2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01")),
views= c(19,39,34,3,25,5,1,16,37,21,46,34,23,0,8,10,46,3,47,16,32,4,44,42,12,8,27,39,28,30,26,45,49,38,32,32,1,16,23,34,41,46,37,0,23,44,10,3,43,43,22,38,1,33,11,15,8,21,37,17,7,29,1,33,47,45,37,20,9,41,37,18,30,46,24,45,48,42,49,3,10,17,46,6,12,29,13,6,4,44,37,26,43,5,19,28,44,20,35,40,32,20,41,46,25,47,35,3,25,25,41,5,26,32)
)
# SERVER
server <- shinyServer(function(input, output) {
output$plots <- renderUI({
doc_data <- filter(data, document == input$select_doc) # This is the breaking line
plot_output_list <- lapply(1:length(unique(doc_data$user)), function(i) {
plotname <- paste("plot", i, sep="")
plotlyOutput(plotname)
})
do.call(tagList, plot_output_list)
})
for (i in 1:length(unique(doc_data$user))) {
local({
local_i <- i
doc_users <- unique(doc_data$user)
plotname <- paste("plot", local_i, sep="")
plot_data <- filter(doc_data, user == doc_users[local_i]) %>%
arrange(month)
output[[plotname]] <- renderPlotly({
p <- plot_ly(x= plot_data$month, y= plot_data$views, type = 'scatter', mode = 'lines')
p$elementId <- NULL
p
})
})
}
})
# UI
ui <- shinyUI(pageWithSidebar(
headerPanel("Minimum reproducible example"),
sidebarPanel(
selectInput("select_doc", choices= unique(data$document), label="", selected= 'doc1')#,
),
mainPanel(
uiOutput("plots")
)
))
# RUN
shinyApp(ui, server)
I'm looking to port some older Shiny apps to use Shiny Modules, but running into trouble trying to port over my reactive expressions.
According to the documentation:
The goal is not to prevent modules from interacting with their
containing apps, but rather, to make these interactions explicit. If a
module needs to use a reactive expression, take the reactive
expression as a function parameter.
I have existing reactive expressions that import data from APIs etc. that I would like to pass in, but can't seem to find the syntax. If I modify the given Shiny module example below I can get to the same problem.
Could anyone modify the below so that you can pass in the car_data() reactive data into the module? I've tried just about every combination of isolate and car_data/car_data() I can think of and am stumped :)
I would prefer to not need to call the data within the module itself, as in my case I'm trying to generalise an ETL function applicable to lots of datasets.
library(shiny)
library(ggplot2)
linkedScatterUI <- function(id) {
ns <- NS(id)
fluidRow(
column(6, plotOutput(ns("plot1"), brush = ns("brush"))),
column(6, plotOutput(ns("plot2"), brush = ns("brush")))
)
}
linkedScatter <- function(input, output, session, data, left, right) {
# Yields the data frame with an additional column "selected_"
# that indicates whether that observation is brushed
dataWithSelection <- reactive({
brushedPoints(data(), input$brush, allRows = TRUE)
})
output$plot1 <- renderPlot({
scatterPlot(dataWithSelection(), left())
})
output$plot2 <- renderPlot({
scatterPlot(dataWithSelection(), right())
})
return(dataWithSelection)
}
scatterPlot <- function(data, cols) {
ggplot(data, aes_string(x = cols[1], y = cols[2])) +
geom_point(aes(color = selected_)) +
scale_color_manual(values = c("black", "#66D65C"), guide = FALSE)
}
ui <- fixedPage(
h2("Module example"),
linkedScatterUI("scatters"),
textOutput("summary")
)
server <- function(input, output, session) {
### My modification
### making the reactive outside of module call
car_data <- reactive({
mpg
})
## This doesn't work
## What is the syntax for being able to call car_data()?
df <- callModule(linkedScatter, "scatters", car_data(),
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
)
output$summary <- renderText({
sprintf("%d observation(s) selected", nrow(dplyr::filter(df(), selected_)))
})
}
shinyApp(ui, server)
Drop the parens after car_data:
df <- callModule(linkedScatter, "scatters", car_data,
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
)
The module seems to want "unresolved" reactives. The parentheses "resolves" them.
If you want to pass input which is not part of the module just wrap it around reactive() as stated in a tutorial.
If a module needs to access an input that isn’t part of the module,
the containing app should pass the input value wrapped in a reactive
expression (i.e. reactive(...)):
callModule(myModule, "myModule1", reactive(input$checkbox1))
Update:
As correctly stated in another answer and Joe Cheng correct way to pass reactive expression is without brackets ()
callModule(linkedScatter, "scatters", car_data)
One option is also to modularize your API input function so you don't need to define reactive expression outside modules. Example of modularized input can be found from this answer.
Below your code with right answer.
library(shiny)
library(ggplot2)
linkedScatterUI <- function(id) {
ns <- NS(id)
fluidRow(
column(6, plotOutput(ns("plot1"), brush = ns("brush"))),
column(6, plotOutput(ns("plot2"), brush = ns("brush")))
)
}
linkedScatter <- function(input, output, session, data, left, right) {
# Yields the data frame with an additional column "selected_"
# that indicates whether that observation is brushed
dataWithSelection <- reactive({
brushedPoints(data(), input$brush, allRows = TRUE)
})
output$plot1 <- renderPlot({
scatterPlot(dataWithSelection(), left())
})
output$plot2 <- renderPlot({
scatterPlot(dataWithSelection(), right())
})
return(dataWithSelection)
}
scatterPlot <- function(data, cols) {
ggplot(data, aes_string(x = cols[1], y = cols[2])) +
geom_point(aes(color = selected_)) +
scale_color_manual(values = c("black", "#66D65C"), guide = FALSE)
}
ui <- fixedPage(
h2("Module example"),
linkedScatterUI("scatters"),
textOutput("summary")
)
server <- function(input, output, session) {
data(mpg)
### My modification
### making the reactive outside of module call
car_data <- reactive({
mpg
})
## Fix This doesn't work by reactive (var) no brackets()
## What is the syntax for being able to call car_data()?
df <- callModule(linkedScatter, "scatters", reactive(car_data),
left = reactive(c("cty", "hwy")),
right = reactive(c("drv", "hwy"))
)
output$summary <- renderText({
sprintf("%d observation(s) selected", nrow(dplyr::filter(df(), selected_)))
})
}
shinyApp(ui, server)