How to format table values rendered with rhandsontable as percentages while retaining and showing full decimal precision?

33 views Asked by At

The below R Shiny code, using package rhandsontable, retains full decimal precision via the function row_decimals(). As a next step I am trying to show these values as percentages while retaining full decimal precision. Everything I have tried using format() or custom JavaScript renderer either rounds or truncates the rendered values, which I don't want. Ideally, any values with less than or equal to 2 decimal places are shown with 2 decimals (for example, 0.20, 0.212, 0.2123 shown as 20.00%, 21.20%, and 21.23%, respectively) and any values with greater than 2 decimal places show all decimals (for example, 0.123456789 shown as 12.3456789%). Any recommendations for how to do this? As illustrated in the image below.

enter image description here

Code:

library(shiny)
library(rhandsontable)

# Calculates decimal places for each value in specified column
row_decimals <- function(df, col_name) {
  col <- df[[col_name]]
  n_digits <- sapply(col, function(x) {
    nchar(gsub(".*\\.|^[^.]+$", "", as.character(x)))
  })
  return(n_digits)
}

ui <- fluidPage(
  br(),
  rHandsontableOutput("myTable"),
  br(),
  textOutput("productOutput")
)

server <- function(input, output) {
  values <- reactiveValues(data = data.frame(Values = c(0.5, 0.123456789)))
  
  observeEvent(input$myTable, {values$data <- hot_to_r(input$myTable)})
  
  output$myTable <- renderRHandsontable({
    data <- values$data
    decimals <- row_decimals(data, 'Values')
    data$Values <- mapply(function(x,d){format(x, nsmall = d)},data$Values,decimals)
    rhandsontable(data)
  })
  
  output$productOutput <- renderText({
      product <- as.numeric(values$data$Values[1]) * as.numeric(values$data$Values[2])
      paste("Product of the two rows: ", product)
  })
}

shinyApp(ui = ui, server = server)
2

There are 2 answers

0
Stéphane Laurent On BEST ANSWER

There's a very nice JavaScript library for numbers formatting, namely d3.format. You can use it for a handsontable with the help of the numeric renderer:

library(shiny)
library(rhandsontable)

ui <- fluidPage(
  tags$head(
    # https://github.com/d3/d3-format
    tags$script(src = "https://cdn.jsdelivr.net/npm/d3-format@3")
  ),
  br(),
  rHandsontableOutput("myTable"),
  br(),
  textOutput("productOutput")
)

server <- function(input, output) {
  dat <- data.frame(Values = c(0.5, 0.123456789))
  
  output$myTable <- renderRHandsontable({
    rhandsontable(dat) %>% 
      hot_col(
        1,
        renderer = "function(instance, td, row, col, prop, value, cellProperties) {
          Handsontable.renderers.NumericRenderer.apply(this, arguments);
          const p = Math.max(0, d3.precisionFixed(0.0005) - 2);
          const f = d3.format('.' + p + '%');
          td.innerHTML = f(value);
        }"
      )
  })
  
  output$productOutput <- renderText({
    dd <- hot_to_r(input$myTable)
    product <- dd$Values[1] * dd$Values[2]
    paste("Product of the two rows: ", product)
  })
}

shinyApp(ui = ui, server = server)

enter image description here

I don't understand the line p = Math.max(0, d3.precisionFixed(0.0005) - 2), I copied it from an example.

0
Village.Idyot On

This code addresses the request completely, where a minimum of 2 decimal places are shown and values are shown with fill precision.

library(shiny)
library(rhandsontable)

ui <- fluidPage(
  br(),
  rHandsontableOutput("myTable"),
  br(),
  textOutput("numericOutput")
)

server <- function(input, output) {
  inputs <- reactiveValues(data = data.frame(Values = c(50, 12.3456789)))
  
  observeEvent(input$myTable, { inputs$data <- hot_to_r(input$myTable) })
  
  adjVals <- reactive({ as.numeric(inputs$data$Values) / 100 })
  
  output$myTable <- renderRHandsontable({
    data <- inputs$data
    
    # Simplified formatting logic
    data$Values <- sapply(data$Values, function(x) {
      n_decimals <- nchar(sub("^[^.]*\\.", "", as.character(x)))
      n_decimals <- max(2, n_decimals)  # Ensure at least 2 decimal places
      formatC(x, format = "f", digits = n_decimals)
    })
    
    rhandsontable(data) %>%
      hot_col(1, renderer = "
        function(instance, td, row, col, prop, value, cellProperties) {
          Handsontable.renderers.NumericRenderer.apply(this, arguments);
          td.style.textAlign = 'right';  // Right align text
          if(value) {
            let formattedValue = value.toString();
            let parts = formattedValue.split('.');
            parts[1] = parts[1] || '';  // Ensure there is a decimal part
            td.innerHTML = parts[0] + '.' + parts[1].padEnd(2, '0') + '%';
          }
        }
      ")
  })
  
  output$numericOutput <- renderText({
    product <- adjVals()[1] * adjVals()[2]
    row2 <- adjVals()[2]
    paste("Product of the two rows: ", product, "; Row 2 value is:", row2)
  })
}

shinyApp(ui = ui, server = server)

Output:

enter image description here