I have this problem and the closest I can find is in reference to the submission here but it doesn't quite address what I'm trying to solve Reactive shiny modules sharing data
Referring to the corrected example in the link above, what if I want to be able to edit table a (cells in column x_2), and this will automatically update table c (the corresponding cells in column x_2).
Thanks
Here is a bit simpler version that doesn't work with proxies (and uses the new module interface), I hope it is ok. You can change any value in the first 2 tables and the 3rd table shows the sum and is updated. The trick is that the modules where you edit data have to return the edited data as reactives, these are saved as variables in the main server function. The module that updates based on this data needs to take these variables as reactive inputs.
Very important is:
the modules that return data need to return a reactive, the easiest way to do this is return(reactive({returnvalue}))
in the server function the reactives passed to the module mustn't be evaluated, e.g. you have to use my_reactive_value instead of my_reactive_value()
### Libraries
library(shiny)
library(dplyr)
library(DT)
### Data----------------------------------------
set.seed(4)
table_a <- data.frame(
id=seq(from=1,to=10),
x_1=rnorm(n=10,mean=0,sd=10),
x_2=rnorm(n=10,mean=0,sd=10),
x_3=rnorm(n=10,mean=0,sd=10)
) %>%
mutate_all(round,3)
table_b <- data.frame(
id=seq(from=1,to=10),
x_1=rnorm(n=10,mean=0,sd=10),
x_2=rnorm(n=10,mean=0,sd=10),
x_3=rnorm(n=10,mean=0,sd=10)
)%>%
mutate_all(round,3)
mod_table_edit <- function(id, data_initialisation) {
moduleServer(
id,
function(input, output, session) {
# initialise the reactive data object for the table
data <- reactiveValues(table = data_initialisation)
# render the table
output$table <- renderDT({
datatable(data$table,
editable = TRUE)
})
# update the underlying data
observeEvent(input$table_cell_edit, {
data$table <- editData(data$table, input$table_cell_edit)
})
# return the data as a reactive
return(reactive(data$table))
}
)
}
mod_table_add <- function(id, data_input_1, data_input_2) {
moduleServer(
id,
function(input, output, session) {
# do the calculations
data_table <- reactive({
data_input_1() + data_input_2()
})
# render the table
output$table <- renderDT({
datatable(data_table())
})
}
)
}
modFunctionUI <- function(id) {
ns <- NS(id)
DTOutput(ns("table"))
}
ui <- fluidPage(
fluidRow(
column(4,
modFunctionUI("table_1")),
column(4,
modFunctionUI("table_2")),
column(4,
modFunctionUI("table_3"))
)
)
server <- function(input, output, session) {
# call the modules for the editable tables and store the results
data_table_1 <- mod_table_edit("table_1", data_initialisation = table_a)
data_table_2 <- mod_table_edit("table_2", data_initialisation = table_b)
# call the module for the table that takes inputs
# the reactives musn't be evaluated
mod_table_add("table_3",
data_input_1 = data_table_1,
data_input_2 = data_table_2)
}
shinyApp(ui, server)
Related
I am writing an App which displays data via datatable. Therefore different forms of text files from users are read in. It is often the case that the order of the columns is mixed up in this files, but the information in it is valid.
After displaying the data I want to do some plausibility routines and therefore I want to say: do XY with the data in "Column A". But if there is the wrong data (because of the wrong order of the columns) the plausibility is useless. Therefore I would like reorder the column names like: Column no. 3 is not 'Column A' but contains the data of 'column B', etc.
What I want to do now is to reorder the column names, not the entire column as done here.
With the following code I am able to move the whole column, but how do I only move the column names?
Edit: Just to make things clear: no, I don't want to sort the columns while reading in the data. I want to read in the file as it is and just work with the datatable object. This is mainly because I don't know what kind of file i get and what kind of information is in there. So I first of all want to display what's in there and then have a closer look.
library(shiny)
library(DT)
ui <- fluidPage(
DTOutput("table")
)
server <- function(input, output, session){
output$table <- renderDT({
datatable(
iris,
rownames = FALSE,
extensions = "ColReorder",
options = list(
colReorder = TRUE
),
colnames = c("S-Length", "S-Width", "P-Length", "P-Width", "Species")
)
})
}
shinyApp(ui, server)
Your solution works but the datatable is re-rendered each time you reorder the column names.
Here is a solution using shinyjqui::jqui_sortable with which the datatable is not re-rendered when one sorts the column names:
library(shiny)
library(shinyjqui)
library(DT)
ui <- fluidPage(
br(),
DTOutput("dtable")
)
server <- function(input, output, session){
output[["dtable"]] <- renderDT({
datatable(head(iris, 5))
})
jqui_sortable("#dtable thead tr")
}
shinyApp(ui, server)
Well, I guess I found a solution myself via the package shinyjqui.
library(shiny)
library(DT)
library(shinyjqui) # of course you need to install the package first, if you've never used it before
ui <- fluidPage(
shinyjqui::orderInput("order",
"some order",
items = c("S-Length", "S-Width", "P-Length", "P-Width", "Species")),
tags$br(),
DTOutput("table")
)
server <- function(input, output, session){
output$table <- renderDT({
names(iris) <- input$order
datatable(
iris,
rownames = FALSE,
extensions = "ColReorder",
options = list(
colReorder = TRUE
)
)
})
}
shinyApp(ui, server)
Note: With names(iris) <- input$order I've directly changed the columns' names of the data set and not just the names of the datatable, because I wanted to further access the columns by its (new) names. One could also "only" change the (displayed) names for the datatable.
Edit: btw, here is Stéphane Laurent's version (see answer above) modularized, in case anyone needs it.
library(shiny)
library(shinyjqui)
library(DT)#
ui_modul <- function(id) {
ns <- NS(id)
tagList(
jqui_sortable( DTOutput(ns("dtable")), options = list(items= "thead th"))
)
}
server_modul <- function(id) {
moduleServer(
id,
function(input, output, session) {
output[["dtable"]] <- renderDT({
datatable(head(iris, 5))
})
}
)
}
ui <- fluidPage(
br(),
ui_modul("test")
)
server <- function(input, output, session){
server_modul("test")
}
shinyApp(ui, server)
I developed a small shiny app:
app
The app plots the rain for stations that are chosen in the selectizeInput.
It goes to an external server for the data each time a station is add or removed.
At the moment, it fetches the data from an external server for all the stations regardless if they remain in the list or not. This adds time and computation that are not needed.
My question is how do I reduce the need to get data that is already present?
because I can't present the real app I created a reproducible app to illustrate my code flow:
#data
id <- as.numeric(1:26)
names(id) <- letters
#dataframe function
get.rain.data <- function(id){
print(id)
vec <- 1:100
id <- as.numeric(id)
print(id)
df <- do.call(rbind,lapply(id,function(i)
tibble(x=vec,y=vec*i+vec^2*i,
id=as.factor(rep(i,length(vec))))))
return(df)
}
#plot function
plot.rain <- function(df){
print(df)
p <- ggplot(df,aes(x=x,y=y,group=id))+
geom_line(aes(color=id),size=0.6)
ggplotly(p,height=700)
}
#### UI
ui <- fluidPage(
titlePanel(h1("Rain Intensities and Cumulative Rain")),
sidebarLayout(
sidebarPanel(
helpText("Check rain with info from
IMS.gov.il"),
selectizeInput("var", h3("Select station"),
choices = id,
multiple = T,
selected = 4)
),
mainPanel(
plotlyOutput("rain")
)
)
)
# Define server logic ----
server <- function(input, output) {
dataInput <- reactive({
get.rain.data(input$var)
})
output$rain <- renderPlotly({
req(input$var)
plot.rain(dataInput())
})
}
# Run the application
shinyApp(ui = ui, server = server)
You have the needed code. Everywhere you want to use results from input$var call DataInput() instead. By creating the reactive dataInput function, it will be called when the input$var is updated
# Define server logic ----
server <- function(input, output) {
dataInput <- reactive({
get.rain.data(input$var)
})
output$rain <- renderPlotly({
plot.rain(dataInput())
})
}
I think what you need is to cache values so that they are only queried once. You may want look at the memoise package the can automatically do this for you.
https://github.com/r-lib/memoise
I have a cumbersome function inside a server output variable.
The function returns a list of 2 data frames.
I want to extract these tables and plot them side by side.
However I do not want to create two different outputs for them in server, as I don't want the heavy function to run twice.
For the sake of giving a reproducible code :
(my getListOfDataFrames function is much heavier than in this example)
I want df1 and df2 displayed side by side with scrollX = TRUE in options
library(shiny)
library(DT)
ui <- fluidPage(
dataTableOutput("output1")
)
server <- function(input, output){
getListOfDataFrames <- function(df){
return(list(df[1:5,], df[6:10,]))
}
output$output1 <- renderDataTable({
myList <- getListOfDataFrames(mtcars)
df1 <- as.data.frame(myList[1])
df2 <- as.data.frame(myList[2])
})
}
shinyApp(ui, server)
There are plenty of examples how to create dynamic content, like example below:
library(shiny)
library(DT)
ui <- fluidPage(
uiOutput("dt")
)
server <- function(input, output){
getListOfDataFrames <- function(df){
return(list(df[1:5,], df[6:10,]))
}
myList <- getListOfDataFrames(mtcars)
output$dt <- renderUI({
ntables <- seq(myList)
# we want to create the width depending how many tables we have
width <- paste0(99/max(ntables),"%;")
lapply(ntables, function(i) {
id <- paste0("dt", i)
div(style=paste0("display:inline-block;width:",width),DT::dataTableOutput(id))
})
})
observe({
# Dynamically creating 2 tables with separate ids
lapply(seq(myList), function(i){
id <- paste0("dt", i)
output[[id]] <- DT::renderDataTable(as.data.frame(myList[i]))
})
})
}
shinyApp(ui, server)
The following code is a simpler version of my original problem.
The code should do the following steps sequentially:
The table will show entire 'mtcars'
On each click on Half button, it should show half of the previous data
On each click on One3 button, it should show one third of the previous data
** Can someone tell me how to use reactiveValues etc. to solve the problem?
library(shiny)
library(DT)
ui <- fluidPage(tabsetPanel(
tabPanel("Table",
actionButton("Half","Click for Half"),
actionButton("One3","Click for One Third"),
DTOutput("tbl")
)
))
server = function(input, output, session){
data <- reactiveValues(
tbl = mtcars
)
tbl <- eventReactive(T,data$tbl)
observeEvent(input$Half,{
data$tbl <- data$tbl[1:(round(nrow(data$tbl)/2)),]
tbl <- eventReactive(input$Half,data$tbl)
})
observeEvent(input$One3,{
data$tbl <- data$tbl[1:(round(nrow(data$tbl)/3)),]
tbl <- eventReactive(input$One3,data$tbl)
})
output$tbl <- renderDT(tbl())
}
shinyApp(ui = ui, server = server)
No need for eventreactive as reactiveValues(tbl = mtcars) default value i.e. mtcars will be there presented at output$tbl until the user hits Half then it will be updated automatically in the reactive chain.
server = function(input, output, session){
data <- reactiveValues(tbl = mtcars)
observeEvent(input$Half,{
data$tbl <- data$tbl[1:(round(nrow(data$tbl)/2)),]
})
output$tbl <- renderDT(data$tbl)
}
I am using the DT package to display a data table in my shiny app. Since I provide different data sets, I have radio buttons to select them and the data table updates automatically.
What I would like to do is to preselect the available rows from df1 in df2 when switching the datasets. At the moment, my selection always get erased. When I try to save the selected rows (uncomment the two rows), my table get reset directly.
library(shiny)
library(DT)
df1 <- data.frame(names=letters,
values=1:26)
df2 <- data.frame(names=letters,
values=(1:26)*2)[seq(1,26,2),]
ui <- shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
radioButtons("dataset", label=h5("Select dataset"),
choices=list("df1"='df1',
"df2"='df2'),
selected='df1', inline=TRUE)
),
mainPanel(
DT::dataTableOutput("name_table")
)
)
)
)
Server side...
server <- function(input, output, session) {
getDataset <- reactive({
result <- list()
result[['dataset']] <- switch(input$dataset,
'df1'=df1,
'df2'=df2)
# result[['selection']] <-
# as.numeric(input$name_table_rows_selected)
return(result)
})
output$name_table <- DT::renderDataTable({
DT::datatable(getDataset()[['dataset']],
options=list(pageLength=5))
})
name_proxy = DT::dataTableProxy('name_table')
}
shinyApp(ui, server)
I used the DT table, since I need the proxy and some interaction with the data table.
You can save selected rows only when going to change df like
server <- function(input, output, session) {
dd=reactiveValues(select=NULL)
observeEvent(input$dataset,{
dd$select=as.numeric(isolate(input$name_table_rows_selected))
})
getDataset <- reactive({
result <- list()
result[['dataset']] <- switch(input$dataset,
'df1'=df1,
'df2'=df2)
return(result)
})
output$name_table <- DT::renderDataTable({
DT::datatable(getDataset()[['dataset']],
options=list(pageLength=5),
selection = list(mode = 'multiple', selected =dd$select )
)
})
name_proxy = DT::dataTableProxy('name_table')
}
shinyApp(ui, server)
Or a bit modification of #drmariod variant: use eventReactive instead of reactive
server <- function(input, output, session) {
getDataset <- eventReactive(input$dataset,{
result <- list()
result[['dataset']] <- switch(input$dataset,
'df1'=df1,
'df2'=df2)
result[['selection']] <- testing()
return(result)
})
testing <- function() {
list(selected=as.numeric(input$name_table_rows_selected))
}
output$name_table <- DT::renderDataTable({
DT::datatable(getDataset()[['dataset']],
options=list(pageLength=5),
selection=getDataset()[['selection']])
})
name_proxy = DT::dataTableProxy('name_table')
}
Hm, it looks like I found a solution, but I wonder if there is a better solution.
server <- function(input, output, session) {
getDataset <- reactive({
result <- list()
result[['dataset']] <- switch(input$dataset,
'df1'=df1,
'df2'=df2)
result[['selection']] <- testing()
return(result)
})
testing <- function() {
list(selected=as.numeric(input$name_table_rows_selected))
}
output$name_table <- DT::renderDataTable({
DT::datatable(getDataset()[['dataset']],
options=list(pageLength=5),
selection=getDataset()[['selection']])
})
name_proxy = DT::dataTableProxy('name_table')
}
I wonder, sometimes comes a processing message. and on each click the table shortly "blinks"... Would be great to get a better answer.