R Shiny中如何为reactive对象渲染原生绘图解决ylim报错问题
错误原因
vectorsAll返回的Yld_Rate列是经pct()函数处理的带百分号的字符串,R基础绘图无法将字符串识别为数值计算坐标轴范围,因此抛出Error: need finite 'ylim' values错误vectorsAll初始逻辑存在漏洞:用户未点击「Input Liabilities」按钮时,input$showLiabilityGrid为NULL,此时返回空值,表格渲染字符串无影响,但绘图无法识别有效数据
修正后完整代码
library(shiny) library(shinyMatrix) library(shinyjs) 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)} 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('showVectorValueBtn','Vector values'), button2('showVectorPlotBtn','Vector 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('showLiabilityGrid','Input Liabilities',style='width:100%;background-color:LightGrey'), setShadow(id='showLiabilityGrid'), div(style = "margin-bottom: 10px"), ), # close conditional panel conditionalPanel(condition="input.tabselected==3"), conditionalPanel(condition="input.tabselected==4") ) # close tagList }) # close renderUI # --- 调整vectorsAll返回数值型数据,不再提前做百分号格式化 ----> vectorsAll <- reactive({ if (is.null(input$showLiabilityGrid)){ df <- cbind(Period = 1:60,Yld_Rate = 0.2) } else { if(input$showLiabilityGrid < 1){ df <- cbind(Period = 1:60,Yld_Rate = 0.2) } else { req(input$base_input) df <- cbind(Period = 1:60,Yld_Rate = yield()[,2]) } } df }) # close reactive # --- 表格渲染时单独做百分号格式化,保证展示效果不变 ----> output$table1 <- renderTable({ df <- vectorsAll() df$Yld_Rate <- pct(df$Yld_Rate) df }) # --- Below produces vector values after clicking "Vector Values" button; see above for pre-click ----> observeEvent(input$showVectorValueBtn, {showResults$showme <- tagList(tableOutput("table1")) },ignoreNULL = FALSE) # --- 直接用数值型数据绘图,原生plot可正常识别 ----> output$graph1 <-renderPlot( plot(vectorsAll()[,1], vectorsAll()[,2], type = "l", xlab = "周期", ylab = "收益率", main = "收益率走势") ) observeEvent(input$showVectorPlotBtn,{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$showLiabilityGrid, {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)
效果说明
- 未点击侧边栏「Input Liabilities」按钮时,点击「Vector plots」可直接绘制60个周期取值为0.2的默认折线图
- 点击「Input Liabilities」修改A行数值后,数据表和绘图会同步更新
- 全程使用R原生绘图能力,未引入第三方绘图包,可直接在此基础上调整样式优化外观
内容的提问来源于stack exchange,提问作者Curious Jorge - user9788072
相关产品推荐
相关产品推荐

