5

In the following example, the numberInput "Number C" is calculated and updated as "Number A" divided "Number B". When the result is an infinite decimal, this leads to a number with lots of digits to "Number C". I would like to round the infinite decimal to the second or third the digits, making the appearance easier to read for "Number C". In the same time, however, I don't want to apply the round function to num_C because in the real world I want to apply "Number C" for other calculation and I want to use the number as is. In other words, I want to find a way to format the number's appearance but not the acutal value, like format a cell in an Excel spreadsheet to show only limited digits but not change the acutal value. Is this possible to do in Shiny?

library(shiny)

# Define UI
ui <- fluidPage(
  numericInput(inputId = "A", "Number A", value = 2),
  numericInput(inputId = "B", "Number B", value = 3),
  numericInput(inputId = "C", "Number C [A/B]", value = 1)
)

# Server logic
server <- function(input, output, session){

  observe({
    req(input$A, input$B)
    num_C <- input$A/input$B
    updateNumericInput(
      inputId = "C",
      session = session,
      value = num_C
    )
  })
}

# Complete app with UI and server components
shinyApp(ui, server)
www
  • 38,575
  • 12
  • 48
  • 84
  • I would suggest filing a GH issue to Rstudio's shiny repo to ask for a format argument to `numericInput()` and `updateNumericInput()`. – Phil Apr 12 '19 at 17:51
  • @Phil Thanks for your comment. This is something we can do, but I would like to wait for a while making sure I did not miss any solutions. – www Apr 12 '19 at 17:58
  • 1
    @Phil : with my (limited) understanding of the underlying *input type=„number“*, I don’t think this is really possible because input$C corresponds to the *value* and that’s all this widget really has, ie there is no *format* property and if one would use eg *pattern* to restrict to two decimals, then the *value* and *input$C* will be affected likewise and you don’t have the full precision value anymore. I think you have to store it separately, see eg Maurits‘ answer. – RolandASc Apr 17 '19 at 17:01
  • 1
    @RolandASc this is why I believe it should be a feature request to the Shiny team at RStudio – Phil Apr 17 '19 at 17:15

3 Answers3

5

You could use a reactive expression for num_C

library(shiny)

# Define UI
ui <- fluidPage(
  numericInput(inputId = "A", "Number A", value = 2),
  numericInput(inputId = "B", "Number B", value = 3),
  numericInput(inputId = "C", "Number C [A/B]", value = 1)
)

# Server logic
server <- function(input, output, session){

  num_C <- reactive({
      req(input$A, input$B)
      input$A / input$B
  })

  observe(
      updateNumericInput(
          inputId = "C",
          session = session,
          value = format(num_C(), digits = 2))
      )

}

# Complete app with UI and server components
shinyApp(ui, server)

num_C() will then return the "un-rounded" value, whereas we use the rounded value format(num_C(), digits = 2) in updateNumericInput.


A partial update

For what it's worth, here's an incomplete update

library(shiny)

# Define UI
ui <- fluidPage(
  numericInput(inputId = "A", "Number A", value = 2),
  numericInput(inputId = "B", "Number B", value = 3),
  numericInput(inputId = "C", "Number C [A/B]", value = 1),
  textOutput("value"),
  textOutput("rounded_value")
)

# Server logic
server <- function(input, output, session){

  num_C <- reactiveValues(
      value = NULL,
      rounded_value = NULL
  )

  observeEvent(input$A | input$B, {
      num_C$value <- input$A / input$B
      num_C$rounded_value <- round(num_C$value, 1)
  })

  observeEvent(input$C, {
      num_C$value <- input$C
      num_C$rounded_value <- input$C
  })

  output$value <- renderText(
      sprintf("Number C = %f", num_C$value)
  )
  output$rounded_value <- renderText(
      sprintf("Number C (rounded) = %f", num_C$rounded_value)
  )

}

# Complete app with UI and server components
shinyApp(ui, server)

The idea is to use reactiveValues to track the full precision and rounded value of number C. This works in as far as

  1. changing numbers A, B through numericInput will correctly calculate (and display) the full precision and rounded numbers for C in the textOutputs.
  2. changing number C through numericInput will also correctly display the full precision number (which is equal to the rounded) in the textOutputs.

However, I've been unsuccessful with using updateNumericInput to update the value for C with the rounded number when numbers A and B were changed.

To be continued...

Maurits Evers
  • 49,617
  • 4
  • 47
  • 68
  • Thanks for your answer. I have upvoted your post. However, now `input$C` would be a rounded value, which is what I want in the appearance, but not for the subsequent calculation. For example, later I will do `input$C * 100000`. I want to calculate this value using `0.666666667`, but showing the appearance as `0.67`. – www Apr 12 '19 at 10:58
  • @www The idea was to use `num_C()` instead of `input$C`. The former will have the full precision value. Wouldn’t that work? – Maurits Evers Apr 12 '19 at 11:06
  • This is a good idea. But in this case, I want to give the users the flexibility that when they change the input value to "Number C", for example, `0.25`, the subsequent calculation will use `input$C` (`0.25`), not `num_c()` (`0.67`). – www Apr 12 '19 at 11:13
  • @www Hmm, so just to clarify: You'd like to change the input of "C" either (1) indirectly through changing "A" and/or "B", in which case "C = A/B" should be rounded, or (2) directly through changing "C" itself, in which case there should be no rounding. Is that correct? I think that case is trickier to realise. I'll have to think about this. – Maurits Evers Apr 12 '19 at 12:55
  • Let's say what `C = A/B` or the number directly put into `C` is a decimal numbers, such as `0.6666667`. I want to round it to `0.67` in its appearance, but behind the scene it is still `0.6666667`. What I am asking may not be possible, but in an Excel spreadsheet, it is common to format the cell number with rounding, but not change the actual cell values. I would like to have the same behavior as my numericInput `C`. – www Apr 12 '19 at 13:18
  • @www Ok, I think I understand. But the additional difficulty here is that you want to have two options for changing "C": Either directly or indirectly through changing "A" and/or "B". Having a number rounded only for display is possible for either separate scenario, but I'm not sure (yet) how to link them. – Maurits Evers Apr 12 '19 at 13:43
  • Thanks. I know what I am asking may not be possible. But since it is common in Excel, I want to post my question here to see if anyone has any idea. It is OK if the answer is not possible. – www Apr 12 '19 at 13:49
  • @www I completely agree with you, it sounds like a sensible feature/request. I haven't given up on this;-) I will take another look at this tomorrow morning (it's getting late here in Australia). – Maurits Evers Apr 12 '19 at 14:06
  • Thank you very much for your help. I will also keep looking into this. – www Apr 12 '19 at 14:09
3

Simpliest thing to do would be to count how long input$C is and apply a rounding figure - while saving the true value as a dummy variable:

library(shiny)
#this counts the decimal places of a value
decimalplaces <- function(x) {
  if ((x %% 1) != 0) {
    nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed=TRUE)[[1]][[2]])
  } else {
    return(0)
  }
}

# Define UI
ui <- fluidPage(
  numericInput(inputId = "A", "Number A", value = 2),
  numericInput(inputId = "B", "Number B", value = 3),
  numericInput(inputId = "C", "Number C [A/B]", value = 1)
)

# Server logic
server <- function(input, output, session){
  num_C <- reactiveValues(
    value = NULL
  )

  observeEvent(input$A|input$B,{

    num_C$value <- input$A/input$B
    updateNumericInput(
      inputId = "C",
      session = session,
      value = round(num_C$value,2)
    )
  })

  observeEvent(input$C, {
    #identify if num_C is greater than 2 dc
  if(decimalplaces(input$C)>2){
    num_C$value <- input$C
  }
    updateNumericInput(
      inputId = "C",
      session = session,
      value = round(num_C$value,2)
    )

  })
  #check that everything is working correctly. 
  observeEvent( input$A|input$B|input$C,{
    print(paste0(num_C$value," ", input$C))
  })
}

# Complete app with UI and server components
shinyApp(ui, server)

thanks to @Maurits Evers for the majority of the code.

2

I just changed the code of Maurits a little bit by adding a flag and an observeEvent for the value <- reactiveValue() that holds the actual value. Now, "C" will always show the rounded value, but value will store the actual.

I hope this is useful...

library(shiny)

# Define UI
ui <- fluidPage(
  numericInput(inputId = "A", "Number A", value = 2),
  numericInput(inputId = "B", "Number B", value = 2),
  numericInput(inputId = "C", "Number C [A/B]", value = 1),
  textOutput("value"),
  textOutput("rounded_value")
)

# Server logic
server <- function(input, output, session){

  value <- reactiveVal(1)
  rounded_value <- reactiveVal()
  flag <- reactiveVal(T)

  observeEvent(input$A | input$B, {
    value(input$A / input$B)
    rounded_value(round(value(), 2))
  })


  observeEvent(input$C, {
    if(flag()){
      value(input$C)
      rounded_value(round(input$C, 2))
    }else{
      flag(T)
    }
  })

  observeEvent(value(),{
    flag(F)
    updateNumericInput(session, "C", value = rounded_value())
  })



  output$value <- renderText(
    sprintf("Number C = %f", value())
  )
  output$rounded_value <- renderText(
    sprintf("Number C (rounded) = %f", rounded_value())
  )

}

# Complete app with UI and server components
shinyApp(ui, server)
wati
  • 310
  • 2
  • 11
  • Thanks for your answer. I have given you an upvote first. I am busy today, but I will review your answer and let you know if it works when I have time. – www Apr 17 '19 at 13:37