Create stacked bar sparkline within renderDatatable in R shiny - 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

Related

Cell colouring in Shiny Rendertable when using shiny modules

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.

Value of dynamic UI input lost every time main input change

library(shiny)
ui <- shiny::fluidPage(
shiny::titlePanel("Dynamic UI"),
shiny::sidebarLayout(
shiny::sidebarPanel(width = 2,
shiny::numericInput("num", "Number of input", value=1, min=1, max=7),
htmltools::hr(style = "border-top: 5px dashed skyblue"),
shiny::uiOutput("out")
),
shiny::mainPanel()
)
)
server <- function(input, output, session) {
output$out <- shiny::renderUI({
lapply(1:input$num, function(i) {
htmltools::div(
htmltools::tags$h4(paste0("Input group: ",i)),
shiny::numericInput(paste0("size", i),
label = "size",
value = 3, min = 1, max = 8
),
shiny::numericInput(paste0("inc", i),
label = "incidence",
value = 1, min = 1, max = 8
),
htmltools::hr(style = "border-top: 2px dashed skyblue")
)
})
})
}
shiny::shinyApp(ui, server)
I created an example of app that dynamically create multiple numeric input depending on other input(Number of input here).
Now when Number of input value changes, all the input value that are dynamically created is reset.
I know that because server side creating these input every time Number of input changes.
But, are there any way/trick so dynamic input value will remain unchanged when user change the Number of input?
Use react<-reactiveValues() to have saved values.
Use observeEvent(input$num, {}) to save input values in react values.
library(shiny)
ui <- shiny::fluidPage(
shiny::titlePanel("Dynamic UI"),
shiny::sidebarLayout(
shiny::sidebarPanel(width = 2,
shiny::numericInput("num", "Number of input", value=1, min=1, max=7),
htmltools::hr(style = "border-top: 5px dashed skyblue"),
shiny::uiOutput("out")
),
shiny::mainPanel()
)
)
server <- function(input, output, session) {
react <- reactiveValues(
S=list(),
I=list()
)
output$out <- shiny::renderUI({
lapply(1:input$num, function(i) {
input_size<- isolate(input[[paste0("size", i)]])
input_inc <- isolate(input[[paste0("inc", i)]])
if (is.null(input_size)){
input_size<-3
}
if (is.null(input_inc)){
input_inc<-1
}
htmltools::div(
htmltools::tags$h4(paste0("Input group: ",i)),
shiny::numericInput(paste0("size", i),
label = "size",
value = input_size , min = 1, max = 8
),
shiny::numericInput(paste0("inc", i),
label = "incidence",
value = input_inc, min = 1, max = 8
),
htmltools::hr(style = "border-top: 2px dashed skyblue")
)
})
})
observeEvent(input$num, {
i <- input$num
react$S[i]<- input[[paste0("size", i)]]
react$I[i]<- input[[paste0("inc", i)]]
},ignoreNULL = TRUE, ignoreInit = TRUE
)
}
shiny::shinyApp(ui, server)

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)

Download dataTable is printing HTML along with table

I have this sample app to display and download dataTable. But it is also printing HTML script on top of the downloaded attachment. It is printing logo and title HTML but I also want to preserve them on the app.
library(shiny)
library(DT)
ui <- fluidPage(
titlePanel(title = tags$div(img(src = "test.jpg", width = 170, height = 115, align = "left"))),
titlePanel(title = tags$div(class = "header" , tags$p("Cars", tags$br(), tags$h4("MTCARS", style = "text-align: center; color:navy;"), style = "text-align: center; color:navy;"))),
dataTableOutput("table_output")
)
server <- function(input, output, session){
output$table_output <- renderDataTable(server=FALSE,{
DT::datatable(head(mtcars), extensions = c('Buttons'),
options = list(autoWidth = FALSE, dom = 'lfrtipB',
buttons = list(list(extend = "csv", text = "CSV", filename = "cars",
exportOptions = list(modifier = list(page = "all"))),
list(extend = "excel", text = "EXCEL", filename = "cars",
exportOptions = list(modifier = list(page = "all"))),
list(extend = "pdf", text = "PDF", filename = "cars",
exportOptions = list(modifier = list(page = "all")))
))) })
}
shinyApp(ui, server)
I had to change the UI function to get the proper attachment.
ui <- fluidPage(
img(src = "test.jpg", width = 170, height = 115, align = "left"),
tags$div(class = "header" , tags$h2("Cars", tags$br(), tags$h4("MTCARS", style = "text-align: center; color:navy;"), style = "text-align: center; color:navy;")),
dataTableOutput("table_output")
)

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');"