英文:
Reactively doing calculations in columns, based on the values of user input in another column
问题
我有一个应用程序,在该应用程序中,用户在闪亮的DT中输入文本和数字信息,然后这些值保存在数据框中。其中一个列,Duration
,接受一个数字,然后另外两列,Start
和End
,根据这些值进行计算。我知道计算是进行的,因为如果我将Duration
的值从0更改为任何数字,那么数学计算就会进行。问题是,如果我在应用程序中更改Duration
的值,那么Start
和End
的值不会发生响应性变化。
有人知道为什么会这样吗?
英文:
I have an app in which the user inputs text and numeric information in a shiny DT, and then those values are saved in a dataframe. One of the columns, Duration
, intakes a number and then two other columns, Start
and End
, do calculations based on those values. I know that the calculations are being done because if I change the value for Duration
from 0 to any number, the math is done. The problem is that if I change the value for Duration
in the app, the values for Start
and End
do not reactively change.
Does anyone have any ideas as to why this would be?
library(shiny)
library(tidyverse)
library(DT)
ui <- fluidPage(
#numeric input that determines the number of rows for the phases table
numericInput("num_exps",
label = "Enter the number of experimental phases:",
min = 1,
max = 1000,
value = 1),
DT::dataTableOutput('phasesTable'),
br(),
br(),
verbatimTextOutput("reactive_verbatim"),
br(),
DT::dataTableOutput('habit_table'),
br(),
verbatimTextOutput("habit_verbatim"),
)
server <- function(input, output, session) {
#Number output for number of conditions
output$value <- renderPrint({ input$num_exps })
#Creating a data.frame that is dynamic in its length
phases_df <- reactive({data.frame(
'Phase Number' = sapply(1: as.integer(input$num_exps), function(i) { i }) )
})
#calculating the start and end times for each phase, and adding them to phases_df as timings
timings <- reactive({
req(phases_df()) %>%
mutate('Phase Name' = "") %>%
mutate('Phase Group' = 0) %>%
mutate(Duration = 0) %>%
mutate(End = cumsum(Duration)) %>%
mutate(Start = End - Duration) %>%
relocate(Start, .before = End)
})
#Convert dat1 reactive to data frame and set to a reactiveVal
Dat = reactiveVal()
observe({
d = timings()
d = as.data.frame(d)
Dat(d)
})
#columns to not be editable
checkboxesColumns_list <- c(0, 1, 5:9)
output$phasesTable <- renderDT({
datatable(
timings(),
#escape = FALSE,
editable = list(target = "cell", disable = list(columns = checkboxesColumns_list)),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE,
info = FALSE),
selection = "none")}, server = FALSE)
#edit the shiny datatable and dataframe based on the edits
observeEvent(input[["phasesTable_cell_edit"]], {
info <- input[["phasesTable_cell_edit"]]
Dat(editData(Dat(), info))
})
#print the verbatim of the dataframe
output$reactive_verbatim <- renderPrint({
str(Dat())
})
}
shinyApp(ui, server)
答案1
得分: 1
我尝试了一下。我确信有更简洁的解决方案,但我认为这个方法会让你朝着正确的方向前进。不再使用timings
响应式来生成你的数据表,我决定使用Dat
,因为在这里使用响应式Val比响应式更容易操作。
我创建了两个辅助函数create_timings
和create_dt
。
create_timings
从phases_df()
构建初始数据框以设置timings()
。然后,在此之后,一切都使用了Dat
响应式Val。当进行新更改时,将使用更新的数据运行create_timings
。这会更新Dat
,从而使output$phasesTable
无效,触发它输出新的DT。
server <- function(input, output, session) {
#Number output for number of conditions
output$value <- renderPrint({ input$num_exps })
#Creating a data.frame that is dynamic in its length
phases_df <- reactive({
data.frame(
'Phase Number' = sapply(1:as.integer(input$num_exps), function(i) {i})
)
})
# Initialize timings_df then perform calculations based on user input for "Duration"
create_timings = function(phases) {
# init df
if(length(phases) == 1) {
phases %>%
mutate(
'Phase Name' = "",
'Phase Group' = 0,
Duration = 0,
Start = 0,
End = 0
)
} else {
phases %>%
mutate(End = cumsum(Duration)) %>%
mutate(Start = End - Duration)
}
}
# Helper dt func
create_dt = function(dt, checkboxesColumns_list = c(0, 1, 5:9)) {
datatable(
dt,
#escape = FALSE,
editable = list(target = "cell", disable = list(columns = checkboxesColumns_list)),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE,
info = FALSE),
selection = "none")
}
#calculating the start and end times for each phase, and adding them to phases_df as timings
timings <- reactive({
req(phases_df())
create_timings(phases_df())
})
#Convert dat1 reactive to data frame and set to a reactiveVal
Dat = reactiveVal()
observe({
d = timings()
d = as.data.frame(d)
Dat(d)
})
#columns to not be editable
# checkboxesColumns_list <- c(0, 1, 5:9)
# Uses the reactive Dat() object here
output$phasesTable <- renderDT({
create_dt(dt = Dat())
}, server = FALSE)
#edit the shiny datatable and dataframe based on the edits
observeEvent(input[["phasesTable_cell_edit"]], {
info <- input[["phasesTable_cell_edit"]]
# This updates Dat with new data which is first passed into `create_timings`
Dat(create_timings(editData(Dat(), info)))
})
#print the verbatim of the dataframe
output$reactive_verbatim <- renderPrint({
str(Dat())
})
}
英文:
I took a stab at this. I'm sure there's a cleaner solution but I think this will get you in the right direction. Instead of using the timings
reactive for generating your datatable. I decided to use Dat
since it's easier to work with a reactiveVal here instead of a reactive.
I created two helper functions create_timings
and create_dt
.
create_timings
builds out the initial df from the phases_df()
to set up timings()
. Then after that everything is using the Dat reactiveVal. When new changes are made, create_timings
is run with the updated data. This updates Dat
which invalidates output$phasesTable
triggering it to output the new DT.
server <- function(input, output, session) {
#Number output for number of conditions
output$value <- renderPrint({ input$num_exps })
#Creating a data.frame that is dynamic in its length
phases_df <- reactive({
data.frame(
'Phase Number' = sapply(1:as.integer(input$num_exps), function(i) {i})
)
})
# Initialize timings_df then perform calculations based on user input for "Duration"
create_timings = function(phases) {
# init df
if(length(phases) == 1) {
phases %>%
mutate(
'Phase Name' = "",
'Phase Group' = 0,
Duration = 0,
Start = 0,
End = 0
)
} else {
phases %>%
mutate(End = cumsum(Duration)) %>%
mutate(Start = End - Duration)
}
}
# Helper dt func
create_dt = function(dt, checkboxesColumns_list = c(0, 1, 5:9)) {
datatable(
dt,
#escape = FALSE,
editable = list(target = "cell", disable = list(columns = checkboxesColumns_list)),
options = list(paging = FALSE,
ordering = FALSE,
scrollx = FALSE,
searching = FALSE,
stringsAsFactors = FALSE,
info = FALSE),
selection = "none")
}
#calculating the start and end times for each phase, and adding them to phases_df as timings
timings <- reactive({
req(phases_df())
create_timings(phases_df())
})
#Convert dat1 reactive to data frame and set to a reactiveVal
Dat = reactiveVal()
observe({
d = timings()
d = as.data.frame(d)
Dat(d)
})
#columns to not be editable
# checkboxesColumns_list <- c(0, 1, 5:9)
# Uses the reactive Dat() object here
output$phasesTable <- renderDT({
create_dt(dt = Dat())
}, server = FALSE)
#edit the shiny datatable and dataframe based on the edits
observeEvent(input[["phasesTable_cell_edit"]], {
info <- input[["phasesTable_cell_edit"]]
# This updates Dat with new data which is first passed into `create_timings`
Dat(create_timings(editData(Dat(), info)))
})
#print the verbatim of the dataframe
output$reactive_verbatim <- renderPrint({
str(Dat())
})
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论