Cell colouring in Shiny Rendertable when using shiny modules - shiny

I have been using the guidance here to colour the cells of my table in based on the number in the cell. However the whole table is currently displaying in the colour selected and not just the one cell.
This is what is currently outputting
I believe the issue with this is that my shiny app is built in modules.
This is the code in my DriversTable module:
# UI ----
topDriversTableUI <- function(id) {
tagList(
div(
style = "text-align: left; font-size: 120%",
h4(strong("Social risk")),
p("This section of the tool looks exclusively at the reasons why a neighbourhood is socially vulnerable.")
),
textOutput(NS(id, "lsoas_clicked_name")),
br(),
# dataTableOutput(NS(id, "drivers_table_domains")),
fluidRow(box(
tableOutput(NS(id, "top_drivers_table_domains")),
title = "Overarching reasons why the neighbourhood is socially vulnerable to flooding",
solidHeader = TRUE,
width = 11,
status = "primary",
collapsible = TRUE
)),
fluidRow(box(
tableOutput(NS(id, "top_drivers_table_variables")),
title = "Underlying reasons why the neighbourhood is socially vulnerable to flooding",
solidHeader = TRUE,
width = 11,
status = "primary",
collapsible = TRUE)
),
tags$head(tags$style("#top_drivers_table_variables td{
position:relative;
};
"))
)
}
# Server ----
topDriversTableServer <- function(id,
vuln_drivers,
lsoas_clicked,
selected_ltlas) {
# Checks to ensure the inputs are reactive (data not reactive)
stopifnot(is.reactive(lsoas_clicked))
moduleServer(id, function(input, output, session) {
observeEvent(
lsoas_clicked(),
{
top_drivers_data <- reactive({
vuln_drivers |>
dplyr::filter(lsoa11_name %in% lsoas_clicked()) |>
# explain the concept of quantiles in plain language
# variable_quantiles = 1 means in top 10% worst scoring neighborhoods nationally
mutate(quantiles_eng = case_when(
quantiles_eng <= 5 ~ '<div style="width: 100%; height: 100%; z-index: 0; background-color: red; position:absolute; top: 0; left: 0; padding:5px;">
<span>1</span></div>',
quantiles_eng > 5 ~ '<div style="width: 100%; height: 100%; z-index: 0; background-color: green; position:absolute; top: 0; left: 0; padding:5px;">
<span>1</span></div>')
) |>
select(
`Rank` = normalised_rank,
`Driver of flooding vulnerability` = domain_variable_name,
`Domain or variable` = domain_variable,
`Comparison of value nationally` = quantiles_eng
# `Values` = values
) |>
arrange(`Domain or variable`, Rank) |>
mutate(Rank = if_else(is.na(Rank), "-", as.character(Rank))) |>
mutate(`Comparison of value nationally` = if_else(is.na(`Comparison of value nationally`), "No data available", `Comparison of value nationally`))
})
output$top_drivers_table_domains <- renderTable({
top_drivers_data() |>
filter(`Domain or variable` == "domain") |>
select(-`Domain or variable`)
}, sanitize.text.function = function(x) x)
output$top_drivers_table_variables <- renderTable({
top_drivers_data() |>
filter(`Domain or variable` == "variable") |>
select(-`Domain or variable`)
}, sanitize.text.function = function(x) x)
output$lsoas_clicked_name <- renderText({
# Message to user if no LSOAs selected ----
# Blank since error message captured in 'top_drivers_table' object
validate(need(
length(lsoas_clicked()) > 0,
""
))
paste("Neighbourhood: ", lsoas_clicked())
})
},
ignoreNULL = FALSE # means event triggered when the input (i.e. lsoa_clicked()) is NULL. Needed to trigger the validate message
)
observeEvent(
selected_ltlas(),
{
lsoas_clicked(NULL)
}
)
})
}
I believe the issue is coming from this part of the code:
tags$head(tags$style("#top_drivers_table_variables td{
position:relative;
};
"))
I believe it is this part of the code because if I run this test code and commented out that part then a similar issue occurs:
test <- data.frame(test1 = c(1:3), test2 = c(4:6))
test[test$test2 == 5, "test2"] <- '<div style="width: 100%; height: 100%; z-index: 0; background-color: green; position:absolute; top: 0; left: 0; padding:5px;">
<span>1</span></div>'
test[test$test2 == 4, "test2"] <- '<div style="width: 100%; height: 100%; z-index: 0; background-color: red; position:absolute; top: 0; left: 0; padding:5px;">
<span>1</span></div>'
library(shiny)
ui <- shinyUI(fluidPage(
box(tableOutput("tt"), title = "title"),
# tags$head(tags$style("#tt td{
# position:relative;
# };
#
# "))
)
)
server <- shinyServer(function(input, output) {
output$tt <- renderTable({
test
}, sanitize.text.function = function(x) x)
})
shinyApp(ui = ui, server = server)

When using module, you must use ns() around your objects IDs in your module. This ns() is adding the module id to the object id.
Which mean your object #top_drivers_table_variables actually is #moduleid-top_drivers_table_variables in your HTML code when it is inside a module.
So to add some CSS to it, you need to add the module id to it.
I think doing something like this should solve the problem
tags$head(tags$style(paste0("#",NS(id, "top_drivers_table_variables"), "td{
position:relative;
};
")))
To make some tests on what is really the id of an object when using modules, and especially nested modules, you can launch your app, and on you browser do a right click on the object and click "Inspect'. Then the HTML and CSS code of the app will appear on a panel on your browser. You can then verify what is really the id of you object.

Related

withSpinner with div in Shiny

So far I successfully made a Shiny dashboard that makes a user select from the first dropdown and a second dropdown dependent on the first. Functionality-wise everything works well except for the UI part marked in star (*) in my script. What I want is the following two:
Make the second dropdown appear right next to the first and not below which can be accomplished by:
div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2"))
Make the second dropdown have a loading spinner once the first dropdown is selected:
uiOutput("var_dropdown2") %>% withSpinner(type = 6)
Both 1 and 2 work but when I try to combine these two it won't:
div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2") %>% withSpinner(type = 6))
How can I achieve the both?
Here is the fully working script:
#### Packages
library(dplyr)
library(shiny)
library(shinyWidgets) # pickerInput()
library(shinycssloaders) # withSpinner()
#### UI
ui <- fluidPage(
mainPanel(
### Dropdown 1
div(
style="display:inline-block; vertical-align:top; margin-left: 20px",
pickerInput(
"var_dropdown1",
"Dropdown 1:",
choices = c("Sepal", "Petal"),
multiple = T
)
),
### Dropdown 2
uiOutput("var_dropdown2") %>% withSpinner(type = 6) # Works without div()
# div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2")) # Works without withSpinner()
# div(style="display:inline-block; vertical-align:top; margin-left: 20px", uiOutput("var_dropdown2") %>% withSpinner(type = 6)) # Doesn't work if using both div() and withSpinner() *
)
)
#### Server
server <- function(input, output, session) {
### Dropdown 2
dropdown2 <- reactive({
if (length(input$var_dropdown1) == 1) {
if (input$var_dropdown1 == "Sepal") {
c("Sepal.Length", "Sepal.Width")
}
else {
c("Petal.Length", "Petal.Width")
}
}
else {
c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
}
})
## renderUI
output$var_dropdown2 <- renderUI({
req(input$var_dropdown1)
pickerInput(
"var_dropdown2",
"Dropdown 2:",
choices = dropdown2(),
multiple = T
)
})
}
####
shinyApp(ui=ui, server=server)
One possible solution is to use cloumn layout:
ui <- fluidPage(
mainPanel(
### Dropdown 1
fluidRow(
column(
6,
pickerInput(
"var_dropdown1",
"Dropdown 1:",
choices = c("Sepal", "Petal"),
multiple = T
)
),
column(
6,
uiOutput("var_dropdown2") %>% withSpinner(type = 6)
)
)
)
)
However, we can see the loader is not positioned in the right location as the dropdown. This is hard to achieve with shinycssloaders package. To do so, we can use some advanced loaders.
#### Packages
library(dplyr)
library(shiny)
library(shinyWidgets) # pickerInput()
library(spsComps)
#### UI
ui <- fluidPage(
mainPanel(
### Dropdown 1
div(
style="display:inline-block; vertical-align:top; margin-left: 20px",
pickerInput(
"var_dropdown1",
"Dropdown 1:",
choices = c("Sepal", "Petal"),
multiple = T
)
),
### Dropdown 2
div(
id = "dropdown2_container",
style="display:inline-block; vertical-align:top; margin-left: 20px; min-height: 50px; min-width: 250px",
uiOutput("var_dropdown2")
)
)
)
#### Server
server <- function(input, output, session) {
### Dropdown 2
dropdown2 <- reactive({
if (length(input$var_dropdown1) == 1) {
if (input$var_dropdown1 == "Sepal") {
c("Sepal.Length", "Sepal.Width")
}
else {
c("Petal.Length", "Petal.Width")
}
}
else {
c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
}
})
## renderUI
dd_loader <- spsComps::addLoader$new(target_selector = "dropdown2_container")
output$var_dropdown2 <- renderUI({
dd_loader$show()
on.exit({dd_loader$hide()})
req(input$var_dropdown1)
Sys.sleep(1)
pickerInput(
"var_dropdown2",
"Dropdown 2:",
choices = dropdown2(),
multiple = T
)
})
}
####
shinyApp(ui=ui, server=server)
Attaching loaders from spsComps takes a few more steps, but it allow you to have complete control of the loader, when to display, when to hide, change loader type, position, size and more dynamically, etc.
In this case, the loader is attached to the outside div instead of the uiOutput directly, because the height and width is subject to change when new UI is rendered inside var_dropdown2. The outside container is not usually modified by renderUI.

How to make shiny outputs render within every slide of a bootstrap carousel?

I was trying to implement a bootstrap5 carousel in a Shiny App. Unfortunately any output (text, table, ..) will not render in any slide expect from the first one. Is there a workaround to make this actually work?
library(shiny)
ui = fluidPage(theme = bs_theme(version = 5),
div(id="carouselExampleIndicators", class="carousel slide", `data-bs-ride`="carousel",
div(class="carousel-indicators",
tags$button(`data-bs-target`="#carouselExampleIndicators", `data-bs-slide-to`="0", class="active", `aria-current`="true", `aria-label`="Slide 1"),
tags$button(`data-bs-target`="#carouselExampleIndicators", `data-bs-slide-to`="1", `aria-label`="Slide 2")),
div(class = "carousel-inner",
div(class = "carousel-item active",
div(textOutput(outputId = "txt1"), style = "display: flex; justify-content: center;")),
div(class = "carousel-item",
div(textOutput(outputId = "txt2"),style = "display: flex; justify-content: center;"))),
tags$button(class="carousel-control-prev", `data-bs-target` = "#carouselExampleIndicators", `data-bs-slide`="prev",
span(class="carousel-control-prev-icon", `aria-hidden` = "true"),
span("prev", class="visually-hidden")),
tags$button(class="carousel-control-next", `data-bs-target` = "#carouselExampleIndicators", `data-bs-slide`="next",
span(class="carousel-control-next-icon", `aria-hidden` = "true"),
span(class="visually-hidden")))
)
server = function(input, output, session){
output$txt1 = renderText({
"displayed"
})
output$txt2 = renderText({
"not displayed"
})
}
shinyApp(ui, server)

Border Style in rowcallback in Datatable

I want to apply style based on value in column. For example first column has text 'A' and then apply style to the whole row. I tried the same with rowcallback, it works but it does not apply borders. I tried with default datatable border style overriding border-collapse : collapse
library(shiny)
library(DT)
css <- "
table.dataTable {
border-collapse: collapse !important;
}
.lastRow {
border-top: 1px solid #000 !important;
font-size: 13px;
font-weight: 600;
color: red;
letter-spacing: .5px;
}
"
Clback <- JS(
"function(row, data, index){",
"if (data[0] == 'C') {",
" $(row).addClass('lastRow');",
"}}"
)
shinyApp(
ui = fluidPage(
tags$head(
tags$style(HTML(css))
),
fluidRow(
column(12,
DTOutput('table')
)
)
),
server = function(input, output) {
mydf <- data.frame(x = LETTERS[1:10], y = sample(1:10))
output$table <- renderDT({
datatable(
mydf,
rownames = F,
options = list(
rowCallback = Clback
)
)
}
)
}
)
That's because the border properties must be applied to the td elements:
" $(row).find('td').addClass('lastRow');"

Is there a way to display a plot or image for a set period of time (seconds)?

The problem i am having is that i have to create an psychological online experiment. I won't get into all the details, however, one aspect involves displaying an image or plot for a set number of seconds. I have started learning Shiny and I am two hours into it, (as i am relatively good with R, and it seemed a good step) however i cannot find a way to do this.
I know showNotification has a "duration" attribute, but I found nothing for showPlot, or Image.
Is there a way to do this, or should I quit Shiny while I haven't lost much time?
Greetings,
George
EDIT: wow. more or less is what I want. Thank you all!
Since details were asked (and thank you for that), the experiment requires only one image. there will be two experimental conditions, and both will have one plot to show (that plot i intend to draw with ggplot2) and contains a number of 30-50 points for about 4 seconds. The subject will have to evaluate the number of points (since he can't count them) and insert it in a field, and the subject will have to then evaluate certain evaluation parameters. data that will be reused to compare him to others. that is about it!
Since it is now obviously possible to do this, i will begin with a serious approach to Shiny. Thank you all!
You will be able to do this in shiny.
You can use a reactiveTimer for example
if (interactive()) {
ui <- fluidPage(
plotOutput("plot")
)
server <- function(input, output) {
# every 2 seconds.
autoHide <- reactiveTimer(2000)
display <- TRUE
observe({
# re-execute this reactive expression every time the
# timer fires.
autoHide()
display <- if_else(display,FALSE,TRUE)
})
output$plot <- renderPlot({
autoHide()
if(display){
hist(rnorm(200))
} else {
hist(rnorm(100))
}
})
}
shinyApp(ui, server)
}
Here is a solution using the slick.js JavaScript library. You have to download the zip file here and extract it in the www subfolder of your app.
library(shiny)
# images to be displayed ####
## these images are in the www subfolder
images <- c("img1.JPG", "img2.JPG", "img3.JPG", "img4.JPG", "img5.JPG")
# ui #####
ui <- fluidPage(
tags$head(
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick-theme.css"),
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick.css"),
tags$script(type="text/javascript",
src="slick-1.8.1/slick/slick.js"),
tags$script(HTML(
"$(document).ready(function(){
$('#images').slick({
arrows: true,
dots: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 500,
infinite: false
}).on('afterChange', function(e, slick, cur){
if(cur === slick.$slides.length-1){
slick.setOption('autoplay', false, true);
}
});
});")),
tags$style(HTML(
"#images .slick-prev {
position:absolute;
top:65px;
left:-50px;
}
#images .slick-next {
position:absolute;
top:95px;
left:-50px;
}
.slick-prev:before, .slick-next:before {
color:red !important;
font-size: 30px;
}
.content {
margin: auto;
padding: 2px;
width: 90%;
}"))
),
sidebarLayout(
sidebarPanel(
# empty sidebar #
),
mainPanel(
tags$div(class="content",
do.call(function(...) tags$div(id="images", ...),
lapply(seq_along(images), function(i){
uiOutput(paste0("img",i))
})
)
)
)
)
)
# server #####
server <- function(input, output) {
lapply(seq_along(images), function(i){
output[[paste0("img",i)]] <- renderUI({
tags$img(src = images[i], width = "400px", height = "400px")
})
})
}
# Run the application ####
shinyApp(ui = ui, server = server)
Is it something like this you want ? We could try to add a "Go" button because here the autoplaying slideshow runs at the startup. Though this is not necessary: to stop the slideshow, it suffices to put the mouse cursor on the image.
EDIT
Here is the version with a "Go" button.
library(shiny)
# images to be displayed ####
## these images are in the www subfolder
images <- c("img1.JPG", "img2.JPG", "img3.JPG", "img4.JPG", "img5.JPG")
# ui #####
ui <- fluidPage(
tags$head(
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick-theme.css"),
tags$link(rel="stylesheet", type="text/css",
href="slick-1.8.1/slick/slick.css"),
tags$script(type="text/javascript",
src="slick-1.8.1/slick/slick.js"),
tags$script(HTML(
"function runSlick(x){
$('#images').slick({
arrows: true,
dots: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 500,
infinite: false
}).on('afterChange', function(e, slick, cur){
if(cur === slick.$slides.length-1){
slick.setOption('autoplay', false, true);
}
});
};
Shiny.addCustomMessageHandler('runSlick', runSlick);")),
tags$style(HTML(
"#images .slick-prev {
position:absolute;
top:65px;
left:-50px;
}
#images .slick-next {
position:absolute;
top:95px;
left:-50px;
}
.slick-prev:before, .slick-next:before {
color:red !important;
font-size: 30px;
}
.content {
margin: auto;
padding: 2px;
width: 90%;
}"))
),
sidebarLayout(
sidebarPanel(
actionButton("go", "Go!")
),
mainPanel(
conditionalPanel(
"input.go > 0",
tags$div(class="content",
do.call(function(...) tags$div(id="images", ...),
lapply(seq_along(images), function(i){
uiOutput(paste0("img",i))
})
)
)
)
)
)
)
# server #####
server <- function(input, output, session){
lapply(seq_along(images), function(i){
output[[paste0("img",i)]] <- renderUI({
tags$img(src = images[i], width = "400px", height = "400px")
})
})
observeEvent(input[["go"]], {
session$sendCustomMessage("runSlick", "")
}, once = TRUE)
}
# Run the application ####
shinyApp(ui = ui, server = server)

Create stacked bar sparkline within renderDatatable in R shiny

I'm trying to replicate this site_details table in R shiny , using stacked bar chart within a column in a table. Here is the code that I'm using currently:
cost_data<- data.frame(site = c('Site 1', 'Site 2','Site 3', 'Site 4'),
cost = c("6,3,6,7", "7,4,7,5","3,2,2,2","6,5,3,2"))
ui <- fluidPage(sparklineOutput("details.1"),
DT::dataTableOutput("cost_table"))
server <- function(input, output) {
output$cost_table <- DT::renderDataTable({
dt <- DT::datatable(as.data.frame(cost_data), rownames = FALSE,
options = list(columnDefs = list(list(targets = 1, render =
JS("function(data, type, full){ return '<span class=sparkSamples>' + data + '</span>' }"))),
fnDrawCallback = JS(paste0("function (oSettings, json) {\n
$('.sparkSamples:not(:has(canvas))').sparkline('html', { ", "type: 'bar',
fillColor: 'black'", " });\n}"), collapse = "")))
})
}
shinyApp(ui = ui, server = server)
The above code gives a simple bar chart. Can a horizontal stacked bar be added instead (as shown in image attached).
Thanks!!
Sparkline may not be able to do what you are looking for i.e., the stacked horizontal bar charts. But you can essentially render a custom JS code within the columnDefs of your renderDataTable.
Below is the sample code for the same-
# Prepare the Sample data
test_data <-
data.table(
Sites = c('Site1', 'Site2', 'Site3', 'Site4'),
A = c(6, 3, 6, 7),
B = c(7, 4, 7, 5),
C = c(3, 2, 2, 2),
D = c(6, 5, 3, 2)
)
test_data$chart <- NA
# Define the Shiny UI and Custom CSS Elements
ui <- fluidPage(tags$head(tags$style(
HTML(
"
.bar-chart-bar {
background-color: #e8e8e8;
display: block;
position:relative;
width: 100%;
height: 20px;
}
.bar {
float: left;
height: 100%;
}
.bar1 {
background-color: green;
}
.bar2 {
background-color: yellow;
}
.bar3 {
background-color: black;
}
.bar4 {
background-color: blue;
}
"
)
)), DT::dataTableOutput("test_table"))
# Rendering the DataTable in Shiny Server
server <- function(input, output) {
output$test_table <- DT::renderDataTable({
dt <- DT::datatable(
as.data.frame(test_data),
rownames = FALSE,
class = "compact display",
options = list(columnDefs = list(list(
targets = -1,
render =
JS(
"function(data, type, row, meta){
return $('<div></div>', {
'class': 'bar-chart-bar'
}).append(function(){
console.log(row)
console.log(row[1])
var bars = [];
for(var i = 1; i < row.length; i++){
bars.push($('<div></div>',{
'class': 'bar ' + 'bar' + i
}).css({
'width': row[i] + '%'
}))
}
return bars;
}).prop('outerHTML')
}"
)
)))
)
})
}
# Run the App
shinyApp(ui, server)
Check below image for the output of above code to show Bar Chart within RenderDataTable -
See Output Image