Search code examples
htmlcssrshinyshinydashboard

Change colour of text output as text value changes when invalidated with reactiveTimer in shiny


I am creating a dashboard for financial stocks. I have a box with the stock price. The stock price changes every minute. What I want is as the stock price changes, the colour should change briefly to reflect the type of change. For instance, if the last price is below the previous last price, I want the colour of the text to flash red as it changes, but return to the default colour, which is black. This is similar to what happens on Google Finance as the price changes (see google search result for jse:npn for example)

Here is the most stripped out version of my code.

library(quantmod)
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)

ui <- dashboardPage(
  dashboardHeader(title = "Example"),

  dashboardSidebar(
    sidebarMenu(
      ID = "tabs",
      menuItem(text = "Naspers", tabName = "tabNaspers", icon = icon("chart-line"))
    )
  ),

  dashboardBody(

    tags$head(tags$style(HTML('.fas { font-size: 36px; }

                          .fas {
                            vertical-align: middle;
                          }
                          #'
              ))),

    tabItems(

      tabItem(tabName = "tabNaspers",
              fluidRow(

                column(
                  width = 7,
                  boxPlus(title = span("ALL SHARE", style = "color: rgb(128,128,128); font-size: 22px"),
                          collapsible = TRUE,
                          closable = FALSE,
                          enable_dropdown = TRUE,
                          dropdown_icon = "NULL",
                          status = 'success',
                          valueBoxOutput('npn_price', 12),
                          valueBoxOutput('npn_day_change', 12),
                          width = 4
                  )

                )

              )        

      )

    )
  )

)


npn_close <- 203059.00

server <- function(input, output, session){

    autoInvalidate <- reactiveTimer(intervalMs = 60000)

    output$npn_price <- renderUI({

      autoInvalidate()

      npn_last <- getQuote("NPN.JO", what=yahooQF("Last Trade (Price Only)"))[, 2]

      npn_change <- round((npn_last - npn_close) / npn_close, 4) * 100

      arrow_color <- ifelse(npn_change > 0, 'rgb(15, 157, 88)' ,'rgb(226, 74, 26)')

      npn_diff <- npn_last - npn_close

      npn_diff <- ifelse(npn_diff < 0, paste0('-', npn_diff), paste0('+', npn_diff))

      tags$div(HTML(paste0('<span style="font-size: 24px"><strong>',
                          npn_last, '</strong></span>', '<span style="color:', arrow_color, '; font-size: 14px">', npn_diff, '</span>')))

    })


    output$npn_day_change <- renderUI({

      autoInvalidate()

      npn_last <- getQuote("NPN.JO", what=yahooQF("Last Trade (Price Only)"))[, 2]

      npn_change <- round((npn_last - npn_close) / npn_close, 4) * 100

      npn_change <- paste0(npn_change, "%")

      arrow_color <- ifelse(npn_change > 0, 'rgb(15, 157, 88)' ,'rgb(226, 74, 26)')

      arrow_icon <- ifelse(npn_change < 0, '"fas fa-caret-down"', '"fas fa-caret-up"')

      tags$div(HTML(paste0('<i class=', arrow_icon, ' style = "color:', arrow_color, 
                          ';"></i><span style="color:', arrow_color,'; font-size: 24px"><strong>',
                           npn_change, '</strong></span>')))

    })



}


shinyApp(ui, server)

Solution

  • Sure. In summary we store the price, get the new price, if the price is down make the text red, then we quickly run again to produce the flash effect.

    For testing I added buttons to simulate the price going up and down. I also made it check for changes more frequently.

    The flash length can be changed at this line: invalidateLater(1200).

    library(quantmod)
    library(shiny)
    library(shinydashboard)
    library(shinydashboardPlus)
    library(shinyWidgets)
    
    ui <- dashboardPage(
        dashboardHeader(title = "Example"),
    
        dashboardSidebar(
            sidebarMenu(
                ID = "tabs",
                menuItem(text = "Naspers", tabName = "tabNaspers", icon = icon("chart-line"))
            )
        ),
    
        dashboardBody(
    
            tags$head(tags$style(HTML('.fas { font-size: 36px; }.fas {vertical-align: middle;} #'))),
    
            tabItems(
    
                tabItem(tabName = "tabNaspers",
                        fluidRow(
    
                            column(
                                width = 7,
                                boxPlus(title = span("ALL SHARE", style = "color: rgb(128,128,128); font-size: 22px"),
                                        collapsible = TRUE,
                                        closable = FALSE,
                                        enable_dropdown = TRUE,
                                        dropdown_icon = "NULL",
                                        status = 'success',
                                        valueBoxOutput('npn_price', 12),
                                        valueBoxOutput('npn_day_change', 12),
                                        width = 4
                                )
    
                            )
    
                        ),
    
                        #Buttons to simulate stock going up, so that we don't have to wait for the stock to actually go up or down
                        actionButton('btn_stockgoesup',   'Simulate Stock Going Up'),
                        actionButton('btn_stockgoesdown', 'Simulate Stock Going Down')
    
                )
    
            )
        )
    
    )
    
    
    npn_close <- 203059.00
    
    server <- function(input, output, session){
    
        autoInvalidate <- reactiveTimer(intervalMs = 6000)
    
        #Buttons to simulate stock going up, so that we don't have to wait for the stock to actually go up or down
        observeEvent(input$btn_stockgoesup,   {npn_last_stored <<- 0  ;  print('At the next update the stock will simulate going up')})
        observeEvent(input$btn_stockgoesdown, {npn_last_stored <<- Inf;  print('At the next update the stock will simulate going down')})
    
        output$npn_price <- renderUI({
    
            autoInvalidate()
    
            npn_last <- getQuote("NPN.JO", what=yahooQF("Last Trade (Price Only)"))[, 2]
    
            #Handle when app first starts and there is no stored value to compare against
            if(exists('npn_last_stored') == FALSE) {npn_last_stored <<- npn_last}
    
            if(npn_last < npn_last_stored) {
    
                #Stock went down
                print('stock went down')
                npn_color <- 'rgb(220, 50, 20)'
                invalidateLater(1200)
    
            } else {
    
                #Stock went up / not changed
                print('stock went up / not changed')
                npn_color <- 'rgb(0, 0, 0)'
    
            }
    
            #Update stored value
            npn_last_stored <<- npn_last
    
            npn_change <- round((npn_last - npn_close) / npn_close, 4) * 100
    
            arrow_color <- ifelse(npn_change > 0, 'rgb(15, 157, 88)' ,'rgb(226, 74, 26)')
    
            npn_diff <- npn_last - npn_close
    
            npn_diff <- ifelse(npn_diff < 0, paste0('-', npn_diff), paste0('+', npn_diff))
    
            tags$div(HTML(paste0('<span style="color:', npn_color, '; font-size: 24px"><strong>', npn_last, '</strong></span>', '<span style="color:', arrow_color, '; font-size: 14px">', npn_diff, '</span>')))
    
        })
    
    
        output$npn_day_change <- renderUI({
    
            autoInvalidate()
    
            npn_last <- getQuote("NPN.JO", what=yahooQF("Last Trade (Price Only)"))[, 2]
    
            npn_change <- round((npn_last - npn_close) / npn_close, 4) * 100
    
            npn_change <- paste0(npn_change, "%")
    
            arrow_color <- ifelse(npn_change > 0, 'rgb(15, 157, 88)' ,'rgb(226, 74, 26)')
    
            arrow_icon <- ifelse(npn_change < 0, '"fas fa-caret-down"', '"fas fa-caret-up"')
    
            tags$div(HTML(paste0('<i class=', arrow_icon, ' style = "color:', arrow_color,';"></i><span style="color:', arrow_color,'; font-size: 24px"><strong>',npn_change, '</strong></span>')))
    
        })
    
    }
    
    shinyApp(ui, server)