在 R Shiny 中,如何允许通过下一次单击允许修改的操作按钮来保留反应性用户输入的更改?
In R Shiny, how to allow a change in reactive user inputs to retain through the next click of an action button that permits modification?
在下面的第一个 MWE 代码中,它完美地工作 - 用户点击“修改”操作按钮,在弹出的模式对话框中用户对矩阵用户输入网格进行更改,用户关闭模式对话框,再次单击“修改”,这些用户输入将保留(或“保留”)在矩阵输入网格(和输出 table)中,而不会进行任何类型的重置回默认设置。完美,按预期工作。
在下面的第二个 MWE 代码中,在“负债模块”选项卡内,用户输入不会从一次单击“修改...”操作按钮(呈现在侧边栏面板中)到下一次保留。每次单击“修改...”都会将用户输入网格重置为默认值。
我一直在尝试将反应链从第一个 MWE 复制到第二个 MWE,但没有成功。我对反应性的理解不够深入。有人可以帮我解决这个问题,并解释需要做什么以及可能的原因吗?
用户输入正确保留的第一个 MWE 代码:
library(shiny)
library(shinyMatrix)
library(shinyjs)
# Function assigns row headers to input matrix grid
matrix3Headers <- function(){
c('A','B','C','D')}
# Assigns default values to first column of input matrix grid
matrix3Default <- matrix(c(1,24,0,100),4,1,dimnames=list(matrix3Headers(),NULL))
# Automatically assigns names to column headers
colnames(matrix3Default) <- paste0("Series ",1:ncol(matrix3Default))
# Matrix input function
matrix3Input <- function(x, matrix3Default){
matrixInput(x,
label = 'Input series terms into below grid, formatted version shown at bottom:',
value = matrix3Default,
rows = list(extend=FALSE,names=TRUE),
cols = list(extend=TRUE,names=TRUE,editableNames=FALSE,delete=TRUE),
class = 'numeric'
) # close matrix input
} # close function
ui <- fluidPage(
useShinyjs(),
titlePanel('Liabilities Inputs'),
fluidRow(actionButton('show','Show'),
actionButton('modify','Modify'),
actionButton('hide','Hide'),
actionButton('reset','Reset'),
tableOutput('table2')
) # close fluid row
) # close fluid page
server <- function(input, output, session){
rv <- reactiveValues(
mat3=matrix3Input('matrix3',matrix3Default),
input=matrix3Default,
colHeader = colnames(input)
) # close reactive values
hide('table2')
observeEvent(input$modify,{
showModal(modalDialog(
rv$mat3,
tableOutput('table1')
)) # close shown modal and modal dialog
hide('table2')
}) # close observe event
output$table1 <- renderTable({
rv$mat3 <- matrix3Input('matrix3',input$matrix3)
# Indented section below assigns sequential column headers to output tables:
df <- input$matrix3
n <- dim(df)[2]
# Below indented code adds numeric formats to table output
dfA <- format(df[1,],nsmall=0)
dfB <- format(df[2,],nsmall=0)
dfC <- paste(format(df[3,],nsmall=2),'%')
dfD <- paste(format(df[4,],nsmall=2),'%')
df <- rbind(df[0,],dfA,dfB,dfC,dfD)
rownames(df) <- matrix3Headers()
rv$input <- df
colnames(df) <- paste("Series", 1:n)
df
},rownames=TRUE, colnames=TRUE)
observeEvent(input$show,show('table2'))
observeEvent(input$hide, hide('table2'))
observeEvent(input$reset,{
hide('table2')
rv$input <- matrix3Default
rv$mat3 <- matrix3Input('matrix3', matrix3Default)
}) # close observe event
output$table2 <- renderTable({
df <- rv$input
n <- dim(df)[2]
colnames(df) <- paste("Series", 1:n)
df
},rownames=TRUE)
} # close server
shinyApp(ui, server)
第二个需要用户输入的 MWE 代码“留下来”:
library(shiny)
library(shinyMatrix)
library(shinyjs)
library(shinyWidgets)
button2 <- function(x,y){actionButton(x,y,style="width:90px;margin-bottom:5px;font-size:80%")}
matrix1Input <- function(x){
matrixInput(x,
value = matrix(c(0.2), 4, 1,dimnames=list(c("A","B","C","D"),NULL)),
rows = list(extend=FALSE,names=TRUE),
cols = list(extend=FALSE,names=FALSE,editableNames=FALSE),
class = "numeric")}
pct <- function(x){paste(format(round(x*100,digits=1),nsmall=1),"%",sep="")} # convert to percentage
vectorBase <- function(x,y){
a <- rep(y,x)
b <- seq(1:x)
c <- data.frame(x = b, y = a)
return(c)}
vectorPlot <- function(w,x,y,z){
plot(w[,1],sapply(w[,2], function(x)gsub("%","",x)), # << per YBS solution Aug 27, 2021
main=x,
xlab=y,
ylab=z,
type="b",
col="blue",
pch=19,cex=1.25
) # close plot
} # close function
ui <-
pageWithSidebar(
headerPanel("Model..."),
sidebarPanel(
fluidRow(helpText(h5(strong("Base Input Panel")),align="center",
style="margin-top:-15px;margin-bottom:5px")),
# Panels rendered with uiOuput & renderUI in server to stop flashing at invocation
uiOutput("Panels")
), # close sidebar panel
mainPanel(
tabsetPanel(
tabPanel("By balances", value=2),
tabPanel("By accounts", value=3),
tabPanel("Liabilities module", value=4,
fluidRow(h5(strong(helpText("Select model output to view:")))),
fluidRow(
button2('showRatesValueBtn','Rates values'),
button2('showRatesPlotBtn','Rates plots'),
), # close fluid row
div(style = "margin-top: 5px"),
# Shows outputs on each page of main panel
uiOutput('showResults')),
id = "tabselected"
) # close tabset panel
) # close main panel
) # close page with sidebar
server <- function(input,output,session)({
base_input <- reactive(input$base_input)
showResults <- reactiveValues()
yield <- function(){vectorBase(60,input$base_input[1,1])} # Must remain in server section
# --- Conditional panels rendered here rather than in UI to eliminate invocation flashing ------------>
output$Panels <- renderUI({
tagList(
conditionalPanel(
condition="input.tabselected==4",
actionButton('modRates','Modify Rates and Coupons',
style='width:100%;background-color:LightGrey'
),
setShadow(id='modRates'),
div(style = "margin-bottom: 10px"),
), # close conditional panel
conditionalPanel(condition="input.tabselected==3"),
conditionalPanel(condition="input.tabselected==4")
) # close tagList
}) # close renderUI
# --- Below defines the vectorsAll object before user clicks on actionButton "Input Liabilities" ---->
vectorsAll <- reactive({
if (is.null(input$modRates)){df <- NULL}
else {
if(input$modRates < 1){df <- cbind(Period = 1:60,BaseRate = pct(0.2))} # define what you want to display by default
else {
req(input$base_input)
df <- cbind(Period = 1:60,BaseRate = pct(yield()[,2]))
} # close 2nd else
} # close 1st else
df
}) # close reactive
output$table1 <- renderTable({vectorsAll()})
# --- Below produces vector values after clicking "Vector Values" button; see above for pre-click ---->
observeEvent(input$showRatesValueBtn,
{showResults$showme <-
tagList(tableOutput("table1"))
},ignoreNULL = FALSE)
# --- Below produces vector plots -------------------------------------------------------------------->
output$graph1 <-renderPlot(vectorPlot(vectorsAll(),"A Variable","Period","Rate"))
observeEvent(input$showRatesPlotBtn,{showResults$showme <- plotOutput("graph1")})
# --- Below sends both vector plots and vector values to UI section above ---------------------------->
output$showResults <- renderUI({showResults$showme})
# --- Below for modal dialog inputs ------------------------------------------------------------------>
observeEvent(input$modRates,
{showModal(modalDialog(
matrix1Input("base_input"),
div(style = "margin-top: 0px"),
useShinyjs(),
) # close modalDialog
) # close showModal
} # close showModal function
) # close observeEvent
}) # close server
shinyApp(ui, server)
那是因为您的 modalDialog
每次单击按钮时都会重置 table。 matrix1Input
始终使用默认值。你需要把它作为你的第一个例子。渲染 table 时,您不再使用默认值重新渲染它,而是使用旧值。
library(shiny)
library(shinyMatrix)
library(shinyjs)
library(shinyWidgets)
button2 <- function(x,y){actionButton(x,y,style="width:90px;margin-bottom:5px;font-size:80%")}
mDefaults <- matrix(c(0.2), 4, 1,dimnames=list(c("A","B","C","D"),NULL))
matrix1Input <- function(x, defaults){
matrixInput(x,
value = defaults,
rows = list(extend=FALSE,names=TRUE),
cols = list(extend=FALSE,names=FALSE,editableNames=FALSE),
class = "numeric")}
pct <- function(x){paste(format(round(x*100,digits=1),nsmall=1),"%",sep="")} # convert to percentage
vectorBase <- function(x,y){
a <- rep(y,x)
b <- seq(1:x)
c <- data.frame(x = b, y = a)
return(c)}
vectorPlot <- function(w,x,y,z){
plot(w[,1],sapply(w[,2], function(x)gsub("%","",x)), # << per YBS solution Aug 27, 2021
main=x,
xlab=y,
ylab=z,
type="b",
col="blue",
pch=19,cex=1.25
) # close plot
} # close function
ui <-
pageWithSidebar(
headerPanel("Model..."),
sidebarPanel(
fluidRow(helpText(h5(strong("Base Input Panel")),align="center",
style="margin-top:-15px;margin-bottom:5px")),
# Panels rendered with uiOuput & renderUI in server to stop flashing at invocation
uiOutput("Panels")
), # close sidebar panel
mainPanel(
tabsetPanel(
tabPanel("By balances", value=2),
tabPanel("By accounts", value=3),
tabPanel("Liabilities module", value=4,
fluidRow(h5(strong(helpText("Select model output to view:")))),
fluidRow(
button2('showRatesValueBtn','Rates values'),
button2('showRatesPlotBtn','Rates plots'),
), # close fluid row
div(style = "margin-top: 5px"),
# Shows outputs on each page of main panel
uiOutput('showResults')),
id = "tabselected"
) # close tabset panel
) # close main panel
) # close page with sidebar
server <- function(input,output,session)({
base_input <- reactive(input$base_input)
showResults <- reactiveValues()
yield <- function(){vectorBase(60,input$base_input[1,1])} # Must remain in server section
# --- Conditional panels rendered here rather than in UI to eliminate invocation flashing ------------>
output$Panels <- renderUI({
tagList(
conditionalPanel(
condition="input.tabselected==4",
actionButton('modRates','Modify Rates and Coupons',
style='width:100%;background-color:LightGrey'
),
setShadow(id='modRates'),
div(style = "margin-bottom: 10px"),
), # close conditional panel
conditionalPanel(condition="input.tabselected==3"),
conditionalPanel(condition="input.tabselected==4")
) # close tagList
}) # close renderUI
# --- Below defines the vectorsAll object before user clicks on actionButton "Input Liabilities" ---->
vectorsAll <- reactive({
if (is.null(input$modRates)){df <- NULL}
else {
if(input$modRates < 1){df <- cbind(Period = 1:60,BaseRate = pct(0.2))} # define what you want to display by default
else {
req(input$base_input)
df <- cbind(Period = 1:60,BaseRate = pct(yield()[,2]))
} # close 2nd else
} # close 1st else
df
}) # close reactive
output$table1 <- renderTable({vectorsAll()})
# --- Below produces vector values after clicking "Vector Values" button; see above for pre-click ---->
observeEvent(input$showRatesValueBtn,
{showResults$showme <-
tagList(tableOutput("table1"))
},ignoreNULL = FALSE)
# --- Below produces vector plots -------------------------------------------------------------------->
output$graph1 <-renderPlot(vectorPlot(vectorsAll(),"A Variable","Period","Rate"))
observeEvent(input$showRatesPlotBtn,{showResults$showme <- plotOutput("graph1")})
# --- Below sends both vector plots and vector values to UI section above ---------------------------->
output$showResults <- renderUI({showResults$showme})
# --- Below for modal dialog inputs ------------------------------------------------------------------>
observeEvent(input$modRates,
{showModal(modalDialog(
matrix1Input("base_input", if(is.null(input$base_input)) mDefaults else input$base_input),
div(style = "margin-top: 0px"),
useShinyjs(),
) # close modalDialog
) # close showModal
} # close showModal function
) # close observeEvent
}) # close server
shinyApp(ui, server)
我添加了默认参数。当你的 table 存在时,我使用现有值而不是默认值。
在下面的第一个 MWE 代码中,它完美地工作 - 用户点击“修改”操作按钮,在弹出的模式对话框中用户对矩阵用户输入网格进行更改,用户关闭模式对话框,再次单击“修改”,这些用户输入将保留(或“保留”)在矩阵输入网格(和输出 table)中,而不会进行任何类型的重置回默认设置。完美,按预期工作。
在下面的第二个 MWE 代码中,在“负债模块”选项卡内,用户输入不会从一次单击“修改...”操作按钮(呈现在侧边栏面板中)到下一次保留。每次单击“修改...”都会将用户输入网格重置为默认值。
我一直在尝试将反应链从第一个 MWE 复制到第二个 MWE,但没有成功。我对反应性的理解不够深入。有人可以帮我解决这个问题,并解释需要做什么以及可能的原因吗?
用户输入正确保留的第一个 MWE 代码:
library(shiny)
library(shinyMatrix)
library(shinyjs)
# Function assigns row headers to input matrix grid
matrix3Headers <- function(){
c('A','B','C','D')}
# Assigns default values to first column of input matrix grid
matrix3Default <- matrix(c(1,24,0,100),4,1,dimnames=list(matrix3Headers(),NULL))
# Automatically assigns names to column headers
colnames(matrix3Default) <- paste0("Series ",1:ncol(matrix3Default))
# Matrix input function
matrix3Input <- function(x, matrix3Default){
matrixInput(x,
label = 'Input series terms into below grid, formatted version shown at bottom:',
value = matrix3Default,
rows = list(extend=FALSE,names=TRUE),
cols = list(extend=TRUE,names=TRUE,editableNames=FALSE,delete=TRUE),
class = 'numeric'
) # close matrix input
} # close function
ui <- fluidPage(
useShinyjs(),
titlePanel('Liabilities Inputs'),
fluidRow(actionButton('show','Show'),
actionButton('modify','Modify'),
actionButton('hide','Hide'),
actionButton('reset','Reset'),
tableOutput('table2')
) # close fluid row
) # close fluid page
server <- function(input, output, session){
rv <- reactiveValues(
mat3=matrix3Input('matrix3',matrix3Default),
input=matrix3Default,
colHeader = colnames(input)
) # close reactive values
hide('table2')
observeEvent(input$modify,{
showModal(modalDialog(
rv$mat3,
tableOutput('table1')
)) # close shown modal and modal dialog
hide('table2')
}) # close observe event
output$table1 <- renderTable({
rv$mat3 <- matrix3Input('matrix3',input$matrix3)
# Indented section below assigns sequential column headers to output tables:
df <- input$matrix3
n <- dim(df)[2]
# Below indented code adds numeric formats to table output
dfA <- format(df[1,],nsmall=0)
dfB <- format(df[2,],nsmall=0)
dfC <- paste(format(df[3,],nsmall=2),'%')
dfD <- paste(format(df[4,],nsmall=2),'%')
df <- rbind(df[0,],dfA,dfB,dfC,dfD)
rownames(df) <- matrix3Headers()
rv$input <- df
colnames(df) <- paste("Series", 1:n)
df
},rownames=TRUE, colnames=TRUE)
observeEvent(input$show,show('table2'))
observeEvent(input$hide, hide('table2'))
observeEvent(input$reset,{
hide('table2')
rv$input <- matrix3Default
rv$mat3 <- matrix3Input('matrix3', matrix3Default)
}) # close observe event
output$table2 <- renderTable({
df <- rv$input
n <- dim(df)[2]
colnames(df) <- paste("Series", 1:n)
df
},rownames=TRUE)
} # close server
shinyApp(ui, server)
第二个需要用户输入的 MWE 代码“留下来”:
library(shiny)
library(shinyMatrix)
library(shinyjs)
library(shinyWidgets)
button2 <- function(x,y){actionButton(x,y,style="width:90px;margin-bottom:5px;font-size:80%")}
matrix1Input <- function(x){
matrixInput(x,
value = matrix(c(0.2), 4, 1,dimnames=list(c("A","B","C","D"),NULL)),
rows = list(extend=FALSE,names=TRUE),
cols = list(extend=FALSE,names=FALSE,editableNames=FALSE),
class = "numeric")}
pct <- function(x){paste(format(round(x*100,digits=1),nsmall=1),"%",sep="")} # convert to percentage
vectorBase <- function(x,y){
a <- rep(y,x)
b <- seq(1:x)
c <- data.frame(x = b, y = a)
return(c)}
vectorPlot <- function(w,x,y,z){
plot(w[,1],sapply(w[,2], function(x)gsub("%","",x)), # << per YBS solution Aug 27, 2021
main=x,
xlab=y,
ylab=z,
type="b",
col="blue",
pch=19,cex=1.25
) # close plot
} # close function
ui <-
pageWithSidebar(
headerPanel("Model..."),
sidebarPanel(
fluidRow(helpText(h5(strong("Base Input Panel")),align="center",
style="margin-top:-15px;margin-bottom:5px")),
# Panels rendered with uiOuput & renderUI in server to stop flashing at invocation
uiOutput("Panels")
), # close sidebar panel
mainPanel(
tabsetPanel(
tabPanel("By balances", value=2),
tabPanel("By accounts", value=3),
tabPanel("Liabilities module", value=4,
fluidRow(h5(strong(helpText("Select model output to view:")))),
fluidRow(
button2('showRatesValueBtn','Rates values'),
button2('showRatesPlotBtn','Rates plots'),
), # close fluid row
div(style = "margin-top: 5px"),
# Shows outputs on each page of main panel
uiOutput('showResults')),
id = "tabselected"
) # close tabset panel
) # close main panel
) # close page with sidebar
server <- function(input,output,session)({
base_input <- reactive(input$base_input)
showResults <- reactiveValues()
yield <- function(){vectorBase(60,input$base_input[1,1])} # Must remain in server section
# --- Conditional panels rendered here rather than in UI to eliminate invocation flashing ------------>
output$Panels <- renderUI({
tagList(
conditionalPanel(
condition="input.tabselected==4",
actionButton('modRates','Modify Rates and Coupons',
style='width:100%;background-color:LightGrey'
),
setShadow(id='modRates'),
div(style = "margin-bottom: 10px"),
), # close conditional panel
conditionalPanel(condition="input.tabselected==3"),
conditionalPanel(condition="input.tabselected==4")
) # close tagList
}) # close renderUI
# --- Below defines the vectorsAll object before user clicks on actionButton "Input Liabilities" ---->
vectorsAll <- reactive({
if (is.null(input$modRates)){df <- NULL}
else {
if(input$modRates < 1){df <- cbind(Period = 1:60,BaseRate = pct(0.2))} # define what you want to display by default
else {
req(input$base_input)
df <- cbind(Period = 1:60,BaseRate = pct(yield()[,2]))
} # close 2nd else
} # close 1st else
df
}) # close reactive
output$table1 <- renderTable({vectorsAll()})
# --- Below produces vector values after clicking "Vector Values" button; see above for pre-click ---->
observeEvent(input$showRatesValueBtn,
{showResults$showme <-
tagList(tableOutput("table1"))
},ignoreNULL = FALSE)
# --- Below produces vector plots -------------------------------------------------------------------->
output$graph1 <-renderPlot(vectorPlot(vectorsAll(),"A Variable","Period","Rate"))
observeEvent(input$showRatesPlotBtn,{showResults$showme <- plotOutput("graph1")})
# --- Below sends both vector plots and vector values to UI section above ---------------------------->
output$showResults <- renderUI({showResults$showme})
# --- Below for modal dialog inputs ------------------------------------------------------------------>
observeEvent(input$modRates,
{showModal(modalDialog(
matrix1Input("base_input"),
div(style = "margin-top: 0px"),
useShinyjs(),
) # close modalDialog
) # close showModal
} # close showModal function
) # close observeEvent
}) # close server
shinyApp(ui, server)
那是因为您的 modalDialog
每次单击按钮时都会重置 table。 matrix1Input
始终使用默认值。你需要把它作为你的第一个例子。渲染 table 时,您不再使用默认值重新渲染它,而是使用旧值。
library(shiny)
library(shinyMatrix)
library(shinyjs)
library(shinyWidgets)
button2 <- function(x,y){actionButton(x,y,style="width:90px;margin-bottom:5px;font-size:80%")}
mDefaults <- matrix(c(0.2), 4, 1,dimnames=list(c("A","B","C","D"),NULL))
matrix1Input <- function(x, defaults){
matrixInput(x,
value = defaults,
rows = list(extend=FALSE,names=TRUE),
cols = list(extend=FALSE,names=FALSE,editableNames=FALSE),
class = "numeric")}
pct <- function(x){paste(format(round(x*100,digits=1),nsmall=1),"%",sep="")} # convert to percentage
vectorBase <- function(x,y){
a <- rep(y,x)
b <- seq(1:x)
c <- data.frame(x = b, y = a)
return(c)}
vectorPlot <- function(w,x,y,z){
plot(w[,1],sapply(w[,2], function(x)gsub("%","",x)), # << per YBS solution Aug 27, 2021
main=x,
xlab=y,
ylab=z,
type="b",
col="blue",
pch=19,cex=1.25
) # close plot
} # close function
ui <-
pageWithSidebar(
headerPanel("Model..."),
sidebarPanel(
fluidRow(helpText(h5(strong("Base Input Panel")),align="center",
style="margin-top:-15px;margin-bottom:5px")),
# Panels rendered with uiOuput & renderUI in server to stop flashing at invocation
uiOutput("Panels")
), # close sidebar panel
mainPanel(
tabsetPanel(
tabPanel("By balances", value=2),
tabPanel("By accounts", value=3),
tabPanel("Liabilities module", value=4,
fluidRow(h5(strong(helpText("Select model output to view:")))),
fluidRow(
button2('showRatesValueBtn','Rates values'),
button2('showRatesPlotBtn','Rates plots'),
), # close fluid row
div(style = "margin-top: 5px"),
# Shows outputs on each page of main panel
uiOutput('showResults')),
id = "tabselected"
) # close tabset panel
) # close main panel
) # close page with sidebar
server <- function(input,output,session)({
base_input <- reactive(input$base_input)
showResults <- reactiveValues()
yield <- function(){vectorBase(60,input$base_input[1,1])} # Must remain in server section
# --- Conditional panels rendered here rather than in UI to eliminate invocation flashing ------------>
output$Panels <- renderUI({
tagList(
conditionalPanel(
condition="input.tabselected==4",
actionButton('modRates','Modify Rates and Coupons',
style='width:100%;background-color:LightGrey'
),
setShadow(id='modRates'),
div(style = "margin-bottom: 10px"),
), # close conditional panel
conditionalPanel(condition="input.tabselected==3"),
conditionalPanel(condition="input.tabselected==4")
) # close tagList
}) # close renderUI
# --- Below defines the vectorsAll object before user clicks on actionButton "Input Liabilities" ---->
vectorsAll <- reactive({
if (is.null(input$modRates)){df <- NULL}
else {
if(input$modRates < 1){df <- cbind(Period = 1:60,BaseRate = pct(0.2))} # define what you want to display by default
else {
req(input$base_input)
df <- cbind(Period = 1:60,BaseRate = pct(yield()[,2]))
} # close 2nd else
} # close 1st else
df
}) # close reactive
output$table1 <- renderTable({vectorsAll()})
# --- Below produces vector values after clicking "Vector Values" button; see above for pre-click ---->
observeEvent(input$showRatesValueBtn,
{showResults$showme <-
tagList(tableOutput("table1"))
},ignoreNULL = FALSE)
# --- Below produces vector plots -------------------------------------------------------------------->
output$graph1 <-renderPlot(vectorPlot(vectorsAll(),"A Variable","Period","Rate"))
observeEvent(input$showRatesPlotBtn,{showResults$showme <- plotOutput("graph1")})
# --- Below sends both vector plots and vector values to UI section above ---------------------------->
output$showResults <- renderUI({showResults$showme})
# --- Below for modal dialog inputs ------------------------------------------------------------------>
observeEvent(input$modRates,
{showModal(modalDialog(
matrix1Input("base_input", if(is.null(input$base_input)) mDefaults else input$base_input),
div(style = "margin-top: 0px"),
useShinyjs(),
) # close modalDialog
) # close showModal
} # close showModal function
) # close observeEvent
}) # close server
shinyApp(ui, server)
我添加了默认参数。当你的 table 存在时,我使用现有值而不是默认值。