Shiny输入矩阵:设置默认值小数位数且保留输入精度
Shiny矩阵输入的小数显示解决方案
需求说明
现有Shiny应用包含3个输入矩阵:
base_input:静态输入矩阵var_1_input、var_2_input:从base_input获取数值,左列“X”代表不可拆分的整月时间维度,右列为数值列
要求:
- 数值列默认显示2位小数
- 用户输入超过2位小数时,保留完整输入精度
- 禁止使用
sprintf()(会破坏后续数值计算流程)
原始示例代码
library(shiny) library(shinyMatrix) matInputBase <- function(name) { matrixInput( name, value = matrix(rep(20,2),2, 1,dimnames = list(c("A", "B"), NULL)), rows = list(extend = FALSE, names = TRUE), cols = list(extend = FALSE, names = FALSE, editableNames = FALSE), class = "numeric" ) } matInputVary <- function(name, x,y) { matrixInput( name, value = matrix(c(x, y), 1, 2, dimnames = list(NULL,c("X","Y"))), rows = list(extend = TRUE, names = FALSE), cols = list(extend = TRUE, delta = 0, names = TRUE, editableNames = FALSE), class = "numeric" ) } ui <- fluidPage( sliderInput("periods","Time window (W):", min = 1, max =10, value = 5), h5(strong("Variable (Y) over window (W):")), matInputBase("base_input"), uiOutput("Vectors") ) server <- function(input, output, session) { output$Vectors <- renderUI({ input$resetVectorBtn varNames <- c("A", "B") lapply(1:2, function(i) { list( h5(strong(paste("Adjust", varNames[i], "(Y) at time X:"))), matInputVary(paste0("var_", i, "_input"), input$periods, input$base_input[i, 1]) ) }) }) } shinyApp(ui, server)
实现方案
通过JavaScript绑定事件实现格式化逻辑,不影响后台数值计算:
- 绑定
shiny:bound事件,在矩阵加载完成后初始化显示格式 - 绑定失焦事件,在用户输入完成后调整显示格式
- 判断矩阵ID和列索引:
var_x_input的第一列(X列)强制显示0位小数var_x_input的第二列(数值列):若原输入小数位<2则显示2位,否则保留原位数
JavaScript代码
js <- " $(document).on('shiny:bound', function(event) { var $elem = $(event.target); if($elem.hasClass('vue-input')) { $elem.find('td').each(function() { var $td = $(this); var columnIndex = $td.index(); var $table = $td.closest('.vue-input'); var tableId = $table.attr('id'); var isVarInput = tableId.startsWith('var_') && tableId.endsWith('_input'); var text = $td.find('span').text(); var num = parseFloat(text); if (!isNaN(num)) { if (isVarInput && columnIndex == 0) { // 对var_x_input的第一列格式化为0位小数 $td.find('span').html(num.toFixed(0)); } else { // var_x_input的第二列及其他情况:小数位<2则显示2位,否则保留原位数 var decimalPlaces = (text.split('.')[1] || []).length; var fixed = decimalPlaces < 2 ? 2 : decimalPlaces; $td.find('span').html(num.toFixed(fixed)); } } }); } }); $(document).ready(function() { $('body').on('focusout', '.vue-input table td input', function(e) { var $td = $(this).parent(); var columnIndex = $td.index(); var $table = $td.closest('.vue-input'); var tableId = $table.attr('id'); var isVarInput = tableId.startsWith('var_') && tableId.endsWith('_input'); var interval = setInterval(function() { if ($td.children().is('span')) { clearInterval(interval); var $span = $td.find('span'); var text = $span.text(); var num = parseFloat(text); if (!isNaN(num)) { if (isVarInput && columnIndex == 0) { // 对var_x_input的第一列格式化为0位小数 $span.html(num.toFixed(0)); } else { // var_x_input的第二列及其他情况:小数位<2则显示2位,否则保留原位数 var decimalPlaces = (text.split('.')[1] || []).length; var fixed = decimalPlaces < 2 ? 2 : decimalPlaces; $span.html(num.toFixed(fixed)); } } } }, 50); }); }); "
整合后的完整代码
将JavaScript代码加入UI头部,完整代码如下:
library(shiny) library(shinyMatrix) matInputBase <- function(name) { matrixInput( name, value = matrix(rep(20,2),2, 1,dimnames = list(c("A", "B"), NULL)), rows = list(extend = FALSE, names = TRUE), cols = list(extend = FALSE, names = FALSE, editableNames = FALSE), class = "numeric" ) } matInputVary <- function(name, x,y) { matrixInput( name, value = matrix(c(x, y), 1, 2, dimnames = list(NULL,c("X","Y"))), rows = list(extend = TRUE, names = FALSE), cols = list(extend = TRUE, delta = 0, names = TRUE, editableNames = FALSE), class = "numeric" ) } js <- " $(document).on('shiny:bound', function(event) { var $elem = $(event.target); if($elem.hasClass('vue-input')) { $elem.find('td').each(function() { var $td = $(this); var columnIndex = $td.index(); var $table = $td.closest('.vue-input'); var tableId = $table.attr('id'); var isVarInput = tableId.startsWith('var_') && tableId.endsWith('_input'); var text = $td.find('span').text(); var num = parseFloat(text); if (!isNaN(num)) { if (isVarInput && columnIndex == 0) { // 对var_x_input的第一列格式化为0位小数 $td.find('span').html(num.toFixed(0)); } else { // var_x_input的第二列及其他情况:小数位<2则显示2位,否则保留原位数 var decimalPlaces = (text.split('.')[1] || []).length; var fixed = decimalPlaces < 2 ? 2 : decimalPlaces; $td.find('span').html(num.toFixed(fixed)); } } }); } }); $(document).ready(function() { $('body').on('focusout', '.vue-input table td input', function(e) { var $td = $(this).parent(); var columnIndex = $td.index(); var $table = $td.closest('.vue-input'); var tableId = $table.attr('id'); var isVarInput = tableId.startsWith('var_') && tableId.endsWith('_input'); var interval = setInterval(function() { if ($td.children().is('span')) { clearInterval(interval); var $span = $td.find('span'); var text = $span.text(); var num = parseFloat(text); if (!isNaN(num)) { if (isVarInput && columnIndex == 0) { // 对var_x_input的第一列格式化为0位小数 $span.html(num.toFixed(0)); } else { // var_x_input的第二列及其他情况:小数位<2则显示2位,否则保留原位数 var decimalPlaces = (text.split('.')[1] || []).length; var fixed = decimalPlaces < 2 ? 2 : decimalPlaces; $span.html(num.toFixed(fixed)); } } } }, 50); }); }); " ui <- fluidPage( tags$head(tags$script(HTML(js))), sliderInput("periods","时间窗口(W):", min = 1, max =10, value = 5), h5(strong("窗口(W)内的变量(Y):")), matInputBase("base_input"), uiOutput("Vectors") ) server <- function(input, output, session) { output$Vectors <- renderUI({ input$resetVectorBtn varNames <- c("A", "B") lapply(1:2, function(i) { list( h5(strong(paste("调整时间点X的变量", varNames[i], "(Y):"))), matInputVary(paste0("var_", i, "_input"), input$periods, input$base_input[i, 1]) ) }) }) } shinyApp(ui, server)
内容的提问来源于stack exchange,提问作者Village.Idyot
相关产品推荐
相关产品推荐

