15

I would like Shiny to print out some different color text depending on the size of a vector. I was thinking something like:

  output$some_text <- renderText({ 
    if(length(some_vec) < 20){
      paste("This is red text")
      <somehow make it red>
    }else{
    paste("This is blue text")
      <somehow make it blue>

...but then I realized, I'm doing this in the server, not the UI.

And, as far as I know, I can't move this conditional logic into the UI.

For example, something like this won't work in the UI:

    if(length(some_vec)< 20){
         column(6, tags$div(
         HTML(paste("This text is ", tags$span(style="color:red", "red"), sep = ""))
      )}
    else{
         tags$div(HTML(paste("This text is ", tags$span(style="color:blue", "blue"), sep = ""))
)}

Does anyone have any creative ideas?

DeanAttali
  • 25,268
  • 10
  • 92
  • 118
tumultous_rooster
  • 12,150
  • 32
  • 92
  • 149

5 Answers5

9

Inspired by jenesaisquoi's answer I tried the following and it worked for me. It is reactive and requires no additional packages. In particular look at output$text3

library(shiny)

ui <- shinyUI(fluidPage(
  titlePanel("Reactive"),
  sidebarLayout(
    sidebarPanel(
      helpText("Variables!"),
      selectInput("var", 
                  label = "Choose Variable",
                  choices = c("red", "blue",
                              "green", "black"),
                  selected = "Rojo"),
      sliderInput("range", 
                  label = "Range:",
                  min = 0, max = 100, value = c(0, 100))
    ),
    mainPanel(
      textOutput("text1"),
      textOutput("text2"),
      htmlOutput("text3"),
      textOutput("text4")
    )
  )
))

server <- function(input, output) {
  output$text1 <- renderText({ 
    paste("You have selected variable:", input$var)
  })

  output$text2 <- renderText({ 
    paste("You have selected range:", paste(input$range, collapse = "-"))
  })

  output$text3 <- renderText({
    paste('<span style=\"color:', input$var, 
          '\">This is "', input$var, 
          '" written ', input$range[2], 
          ' - ', input$range[1], 
          ' = ', input$range[2] - input$range[1], 
          ' times</span>', sep = "")
  })

  output$text4 <- renderText({ 
    rep(input$var, input$range[2] - input$range[1])
  })
}

# Run the application 
shinyApp(ui = ui, server = server)
Sᴀᴍ Onᴇᴌᴀ
  • 8,218
  • 8
  • 36
  • 58
audiracmichelle
  • 124
  • 1
  • 4
6

Came hunting for an answer to a similar question. Tried a simple approach that worked for my need. It uses inline html style, and htmlOutput.

library(shiny)

ui <- fluidPage(
 mainPanel(
 htmlOutput("some_text")
 )
)

and

server <- function(input, output) {

   output$some_text <- renderText({ 

     if(length(some_vec) < 20){
     return(paste("<span style=\"color:red\">This is red text</span>"))

     }else{
     return(paste("<span style=\"color:blue\">This is blue text</span>"))
     }
   })
 }

Conditionals run server side--it wasn't precisely clear to me from opening question that the author needed the conditional to run in UI. I didn't. Perhaps a simple way to address the issue in common situations.

agenis
  • 8,069
  • 5
  • 53
  • 102
Nathan Brown
  • 156
  • 1
  • 5
  • This helped a lot. I was trying to run a conditional on the data that the user read in, then pass that variable to the `class` of a
    tag. I never quite got that to work, but your method enabled me to just pass the whole tag as text.
    – Brian Jun 15 '17 at 04:28
  • 2
    Going a step further, one can also consider any other style attributes,
    , header classes, ect. that you want, so to compose some more interesting conditional outputs. I've used it quite a bit now in apps now that it's clear its an option.
    – Nathan Brown Jun 16 '17 at 21:46
  • In case anyone is getting ` – Danny Aug 14 '21 at 14:44
3

Well, I have the kernel of an idea, but I'm fairly new to anything HTML/CSS/JavaScript-related, so I'm sure it could be improved quite a bit. That said, this seems to work fairly well, as far as it goes.

The key functions are removeClass() and addClass(), which are well documented in their respective help files in shinyjs:

library(shiny)
library(shinyjs)

shinyApp(
    ui = fluidPage(
        useShinyjs(),  ## Set up shinyjs
        ## Add CSS instructions for three color classes
        inlineCSS(list(.red   = "color: red",
                       .green = "color: green",
                       .blue  = "color: blue")),
        numericInput("nn", "Enter a number",
                     value=1, min=1, max=10, step=1),
        "The number is: ", span(id = "element", textOutput("nn", inline=TRUE))
        ),
    server = function(input, output) {
        output$nn <- renderText(input$nn)
        observeEvent(input$nn, {
            nn <- input$nn
            if(is.numeric(as.numeric(nn)) & !is.na(as.numeric(nn))) {
                ## Clean up any previously added color classes
                removeClass("element", "red")
                removeClass("element", "green")
                removeClass("element", "blue")
                ## Add the appropriate class
                cols <- c("blue", "green", "red")
                col <- cols[cut(nn, breaks=c(-Inf,3.5, 6.5, Inf))]
                addClass("element", col)
            } else  {}
        })
    })
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
2

It sounds like you are trying to keep it all on the client side, so you could just use a couple of conditionalPanels, which accept javascript as conditional code. For example, coloring the text in response to the current value in a numericInput box with id "len",

library(shiny)
ui <- shinyUI(
    fluidPage(
        fluidRow(
            numericInput('len', "Length", value=19),
            conditionalPanel(
                condition = "$('#len').val() > 20",
                div(style="color:red", "This is red!")),
            conditionalPanel(
                condition = "$('#len').val() <= 20",
                div(style="color:blue", "This is blue!"))
        )
    )
)

server <- function(input, output, session) {}
shinyApp(ui = ui, server=server)

You could also add an event listener to update the text with javascript. It's kinda ugly inline (and I don't know much javascript), but you could just move the script to a file in wwww/ and use includeScript. As in the previous example, the server does nothing.

ui <- shinyUI(bootstrapPage(
    numericInput('len', "Length", value=19),
    div(id="divvy", style="color:blue", "This is blue!"),
    tags$script(HTML("
        var target = $('#len')[0];
        target.addEventListener('change', function() {
            var color = target.value > 20 ? 'red' : 'blue';
            var divvy = document.getElementById('divvy');
            divvy.style.color = color;
            divvy.innerHTML = divvy.innerHTML.replace(/red|blue/g, color);
        });
    "))
))
Rorschach
  • 31,301
  • 5
  • 78
  • 129
2

Here's a more flexible answer that uses shinyjs::extendShinyjs() to give R a way to produce some parameterized JavaScript code. Compared to my other answer, the advantage of this one is that the same function can be used to reactively colorize multiple numeric outputs.

library(shiny)
library(shinyjs)

jsCode <-
"shinyjs.setCol = function(params){
     var defaultParams = {
         id: null,
         color : 'red'
     };
     params = shinyjs.getParams(params, defaultParams);
     $('.shiny-text-output#' + params.id).css('color', params.color);
 }"
setColor <- function(id, val) {
    if(is.numeric(as.numeric(val)) & !is.na(as.numeric(val))) {
        cols <- c("green", "orange", "red")
        col <- cols[cut(val, breaks=c(-Inf,3.5, 6.5, Inf))]
        js$setCol(id, col)
    }
}

shinyApp(
    ui = fluidPage(
        useShinyjs(),  ## Set up shinyjs
        extendShinyjs(text = jsCode),
        numericInput("n", "Enter a number", 1, 1, 10, 1),
        "The number is: ", textOutput("n", inline=TRUE),
        br(),
        "Twice the number is: ", textOutput("n2", inline=TRUE)
        ),
    server = function(input, output) {
        output$n  <- renderText(input$n)
        output$n2 <- renderText(2 *  input$n)
        observeEvent(input$n, setColor(id = "n", val = input$n))
        observeEvent(input$n, setColor(id = "n2", val = 2 * input$n))
    })
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • ahhh...this is awesome code. It's sooo close to working for me!! I'm having a small (ok, big) issue where I'm trying to change the color based on the size (number of rows) of a dataframe that lives in the server. When the dataframe is too small, the "sample size" text should go red to warn the user of a small sample size. I'm having to remove the `observeEvent` call...causing all kinds of pain... – tumultous_rooster Oct 28 '15 at 20:15
  • 1
    Are you having problems getting the color to update when you point to a new (and smaller) data.frame? If so, I wonder if, in your computation of an intermediate object (containing the number of rows) from the data.frame read in and assigned as an element to the `input` object, you are breaking the "chain of reactivity" (which can depend on the "reactive conductors/reactive expressions" [described here](http://shiny.rstudio.com/articles/reactivity-overview.html).) Have you looked into `reactive()` and/or (depending on how you're doing this) the `reactiveValues()` function? – Josh O'Brien Oct 28 '15 at 20:35