You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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绑定事件实现格式化逻辑,不影响后台数值计算:

  1. 绑定shiny:bound事件,在矩阵加载完成后初始化显示格式
  2. 绑定失焦事件,在用户输入完成后调整显示格式
  3. 判断矩阵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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 23:45:53