I have a datatable in my shiny web app in which I have inserted checkboxes. However, my actual dataset is quite large. Therefore I would like to compress the datatable respectively reduce the padding.
Although I have already found some approaches, this did not work with the checkboxes.
Can anyone help me?
Many thanks in advane!
Here is my reproducible example:
library(shiny)
library(DT)
library(tidyverse)
shinyApp(
ui <- fluidPage(
DT::dataTableOutput('x1')
),
server<-function(input, output, session) {
# create a character vector of shiny inputs
shinyInput<-function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), label=NULL, ...))
}
inputs
}
# a sample data frame
values <- reactiveValues(res=data.frame(
iris %>% slice(10),
v2=shinyInput(checkboxInput, 10, 'v2_', value=FALSE)
)
)
# render the table containing shiny inputs
output$x1 <- DT::renderDataTable(
datatable(values$res, escape = F),
server=FALSE,
escape=FALSE
)
}
)
You need a little CSS. Strictly speaking of the padding, that's straight forward. This rule targets table data cells elements (<td>) contained within the table with the id "x1"
ui <- fluidPage(
DT::dataTableOutput('x1'),
tags$style(HTML("
#x1 td {
padding-top: 0;
padding-bottom: 0;
}
"))
)
This compacts it some. If you want to compact it more, you'll need to find what's driving the row height then target that. For example, the checkboxes have a bottom margin. You could remove that as well. If you're not aware, you can see the applied css by using using your browser's devtools. For Chrome, right click on a row and select "Inspect"
Related
This is how my day column in the datatable appears:
I removed some of the columns to adjust the day column to make it to appear in single row instead of two rows.
The date-column is now like this: '2022-' and in next line '11-24'. I want date column to be like this '2022-11-24'. How can I do this without using width parameter.
I tried all datatable parameters, but nothing seems to work.
Use the CSS white-space: nowrap:
library(DT)
library(shiny)
dat <- mtcars
dat$test <- "2022-12-08"
ui <- fluidPage(
tags$head(
tags$style(HTML(
"
td {
white-space: nowrap;
}
"
))
),
DTOutput("dtable")
)
server <- function(input, output, session) {
output$dtable <- renderDT({
datatable(dat)
})
}
shinyApp(ui, server)
I am implementing some checkboxes in R shiny DT, however for some reasons it seems like the checkbox is contained in a box higher than the cell itself, so I am ending up with a very unpleasant layout as far less rows fit the same page.
Is there a way to control the height in which the checkbox or other components as props are contained, in order to fix the cell? the checkbox itself is much smaller than the actual cells and would fit perfectly in a normal cell.
below some code to reproduce the problem if you want to play with it.
library(DT)
ui <- basicPage(
h2("The mtcars data"),
DT::dataTableOutput("mytable")
)
server <- function(input, output) {
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
mtcars <- data.frame(mtcars, newvar=shinyInput(checkboxInput,nrow(mtcars),"chkbx_",label="",value=TRUE,width=NULL))
output$mytable = DT::renderDataTable({
DT::datatable(mtcars, escape = FALSE, selection = 'none', options = list(searching = FALSE, ordering = FALSE))
})
}
shinyApp(ui, server)
You need to modify CSS of the app in order to achieve this. The quickest solution is to add a style tag to your UI:
ui <- basicPage(
tags$style(HTML("
td > div.form-group > div.checkbox {
margin: 0px;
}
td > div.form-group {
margin: 0px;
}
")),
h2("The mtcars data"),
DT::dataTableOutput("mytable")
)
This will remove margins from all form-groups and checkboxes inside table cells in the app.
I have a data frame of two columns and several rows. I want each row of the data frame to be represented as a choice in a selectInput(). The two elements of each row are supposed to be separated by |. These separators should be exactly one below each other in the selectInput(). Furthermore, when a choice is selected only the first element corresponding to the first column should be shown.
My idea was to use the number of chars to achieve the alignment. However, different chars have different sizes. That's why this appraoch unfortunately doesn't work. See the following example.
library(shiny)
library(stringi)
a <- c("Veronica", "Paul", "Elisabeth", "Mike", "Katy", "Tim")
b <- c(50015, 23010, 86812, 55497, 32309, 67631)
data <- data.frame(a, b)
ui <- fluidPage(
selectInput("selectID",
label = "Test Label:",
choices = as.list(c("", paste(data[, 1], stri_dup(intToUtf8(160), max(nchar(data[, 1])) - nchar(data[, 1])), "|", data[, 2]))))
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
Additionally, when I select the first row for example, only Veronica should be shown. At the moment, I do not have an idea how to achieve this.
By the way, I saw this kind of dropdown menu in an Access database. So maybe shiny or a shiny related package offers this kind of functionality and I just didn't find it. In any case, I appreciate any help.
You're almost there. To display a different value to the selection of a selectInput, you have to name the elements of the choices vector. The padding doesn't show up inside the dropdown because the default behaviour of HTML is to collapse white space. So you need to tweak the CSS that formats the dropdown. That's what the tags$head(...) is for.
I've tidied up your derivation of the choice list and added a textOutput to demonstrate the difference between the items displayed by the selectInput and its return value.
library(shiny)
library(stringr)
a <- c("Veronica", "Paul", "Elisabeth", "Mike", "Katy", "Tim")
b <- c(50015, 23010, 86812, 55497, 32309, 67631)
data <- data.frame(a, b)
maxWidth <- max(str_length(data[, 1]))
choiceList <- data[, 1]
names(choiceList) <- paste0(str_pad(data[, 1], width=maxWidth, side="right"), "|", data[, 2])
ui <- fluidPage(
tags$head(tags$style(".option {font-family: Monospace; white-space: pre; }")),
selectInput("selectID",
label = "Test Label:",
choices = choiceList
),
textOutput("selection")
)
server <- function(input, output, session) {
output$selection <- renderText({ input$selectID })
}
shinyApp(ui, server)
Note that the derivation of the choices for the selectInput is static. If you want that to be dynamic, you need to move the code to populate it inside the server function and wrap it in an observe or observeEvent. You'd probably need to make data reactive as well.
Edit: dealing with proportional fonts
The link in my comment below gives the key to a solution using shinyWidgets. My guess that using tags$span() would help was wrong because all the arguments to tags$span() are taken as the content of the tag rather than as arguments to the tag. So we need to construct the necessary HTML manually.
For convenience, I've added the a variable containing the necessary HTML to the data frame. The rowwise() is necessary to limit the concatenation to the current row rather than the entire data frame. I assume that the "|" separator is no longer required.
Pick the width of the col1 class to be "long enough" or calculate it on the fly.
library(shiny)
library(shinyWidgets)
library(tidyverse)
a <- c("Veronica", "Paul", "Elisabeth", "Mike", "Katy", "Tim")
b <- c(50015, 23010, 86812, 55497, 32309, 67631)
data <- data.frame(a, b) %>%
rowwise() %>%
mutate(dropdownText=HTML(paste0("<span class='col1'>", a, "</span><span class='col2'>", b, "</span>"))) %>%
ungroup()
ui <- fluidPage(
tags$head(
tags$style(".col1 {min-width: 150px; display: inline-block; }"),
tags$style(".col2 {min-width: auto; display: inline-block; }")
),
pickerInput("selectID",
label = "Test Label:",
choices = data$a,
choicesOpt=list(content=data$dropdownText)
),
textOutput("selection")
)
server <- function(input, output, session) {
output$selection <- renderText({ input$selectID })
}
shinyApp(ui, server)
There are undoubtedly better (ie more precise) ways of specifying the CSS classes and selectors, but I don't know enough CSS to know what they are.
Edit 2
To display only the first column in the selectInput but both columns in the dropdown, change the tags$head() to
tags$head(
tags$style(".col1 {min-width: 150px; display: inline-block; }"),
tags$style(".col2 {min-width: auto; display: inline-block; }"),
tags$style(".filter-option-inner-inner .col2 {min-width: auto;
display: inline-block; visibility: hidden; }")
)
The third element overrides the visibility of elements styled with thecol2 style when they are children of elements with the filter-option-inner-inner style. You can see how various elements of the UI are styled by opening the app in a browser, right-clicking anywhere on the page and selecting "Inspect" or similar.
UPDATE
I've gotten to what I think is the root problem. The following R Shiny App produces a UI with 2 text input boxes, as well as event observers that print messages to the console as the text changes in their respective text input boxes. The issue is that only one of these event observers works correctly, and I can't figure out why.
ui.R (shortened)
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
shinyUI(
renderUI({
fluidPage(
column(12, dataTableOutput("Main_table")),
box(textInput("TEST_BOX", label=NULL, value="TEST"))
)
})
)
server.R (shortened)
shinyServer(function(input, output) {
test <- reactiveValues()
test$data <- data.table(ID = 1, Group = 1)
output$Main_table <- renderDataTable({
datatable(data.frame(test$data,
New_Group=as.character(textInput("BOX_ID", label = NULL, value = "TEST2",
width = '100px'))), escape=F
)})
observeEvent(input$TEST_BOX, {
print("Test Box Success")
})
observeEvent(input$BOX_ID, {
print("Box ID Success")
})
})
Original Post:
I'm attempting to create a simple app in R Shiny to allow the user to interactively update the values in a column of a small table, then be able to hit a "Save Changes" button and update the table to include their selections.
I've gotten really close with the code below (I think), but for some reason the inputs cbox_1 to cbox_10 always come back as NULL.
ui.R
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
shinyUI(fluidPage(
dashboardBody(uiOutput("MainBody")
)
))
server.R
# Load libraries
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
# Define server logic
shinyServer(function(input, output) {
# Create sample data
vals <- reactiveValues()
vals$Data <- data.table(ID = 1:10, Group = 1:1)
# Create main UI with Save Changes button and additional text input box for testing.
output$MainBody <- renderUI({
fluidPage(
box(width=12,
h3(strong("Group Testing"),align="center"),
hr(),
box(textInput("test", label=NULL, value="TESTING")),
column(6, offset = 5, actionButton("save_changes","Save changes")),
column(12, dataTableOutput("Main_table"))
)
)
})
# Function to be used to create multiple text input boxes.
shinyInput = function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, value = vals$Data$Group[i], width = '100px', ...))
}
inputs
}
# Renders table to include column with text input boxes. Uses function above.
output$Main_table <- renderDataTable({
datatable(data.frame(vals$Data, New_Group=shinyInput(textInput, nrow(vals$Data),"cbox_")), options = list(dom = 't', pageLength = nrow(vals$Data), paging=FALSE, searching=FALSE), rownames=FALSE,
escape=F)
}
)
# Tests if the test input box works.
observeEvent(input$test, {
print("Success1")
})
# Tests if the first input box in the table works.
observeEvent(input$cbox_1, {
print("Success2")
})
# Tests if the Save Changes button works.
observeEvent(input$save_changes, {
print("Success3")
# Assigns the values in the input boxes (New_Group) to the existing Group column.
for (i in 1:nrow(vals$Data)) {
vals$Data$Group[i] <- eval(paste0("input$cbox_", i))
}
datatable(data.frame(vals$Data, New_Group=shinyInput(textInput, nrow(vals$Data),"cbox_")), options = list(pageLength = nrow(vals$Data), paging=FALSE, searching=FALSE), rownames=FALSE,
escape=F)
})
})
The first two observeEvents at the end of the code are solely for testing purposes. "Success2" is never printed even when the contents of the first box are changed. "Success1" is printed when the test box is changed, but I'm not sure why one works and the other doesn't. I've tried inserting a browser() statement in various places of the code to check the value of cbox_1, but it always comes back NULL. I'd also be open to alternate solutions to this problem if I'm approaching it completely wrong. Thanks.
After further research, an approach utilizing the rhandsontable package seemed like the best solution. I modeled my code after this example:
Data input via shinyTable in R shiny application
I also utilized several of the options described here:
https://jrowen.github.io/rhandsontable/#introduction
I would like to dynamically create a series of input widgets to use in each row of data table. I am successfully able to display such a list of inputs in the table, however I'm having trouble accessing the value of these dynamic inputs.
ui.R
library(shiny)
ui <- fluidPage(
fluidRow(
radioButtons('original','Normal Radio Button',c('1','2','3','4','5')),
DT::dataTableOutput("table")
)
)
server.R
library(DT)
multipleRadio <- function(FUN, id_nums, id_base, label, choices, ...) {
inputs <- 1:length(id_nums)
for (i in 1:length(inputs)) {
inputs[i] <- as.character(FUN(paste0(id_base, id_nums[i]),label, choices, ...))
}
return(inputs)
}
radio_inputs <- multipleRadio(radioButtons,
as.character(1:3),
'input_',
'Radio Button',
c('1','2','3','4','5'),
inline = TRUE)
output_table <- data.frame(id = c(1,2,3),
name=c('Item 1','Item 2','Item 3'),
select = radio_inputs)
server <- function(input, output, session) {
observe({
print(paste('original: ',input$original))
print(paste('input 1: ',input$input_1))
print(paste('input 2: ',input$input_2))
print(paste('input 3: ',input$input_3))
})
output$table <- renderDataTable({
datatable(output_table,rownames= FALSE,escape = FALSE,selection='single',
options = list(paging = FALSE,ordering=FALSE,searching=FALSE))
})
}
I define a function which generates multiple radioButton inputs and converts them into their HTML representation using as.character. This generates a series of inputs whose ids are "input_1", "input_2", and "input_3." I fill a column of the output table with the radio inputs. The display of the radioButtons works as expected. I see one in each row. However, input$input_1,input$input_2, and input$input_3 don't seem to exist and there is no response to clicking on these buttons. Any tips on what's going wrong here would be greatly appreciated!
Edit:
I found a solution here:
http://www.stackoverflow.red/questions/32993257/shiny-datatables-with-interactive-elements
Using the Shiny.bindAll function when rendering the datatable appears to convert the HTML inputs into Shiny input objects.
output$table <- renderDataTable({
datatable(output_table,rownames= FALSE,escape = FALSE,selection='single',
options = list(paging = FALSE,ordering=FALSE,searching=FALSE,
preDrawCallback=JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback=JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
})
A correct shiny input object is a shiny.tag object, which you cannot put into a data.frame. If you do so, you'll get the following error message:
Error in as.data.frame.default(x[[i]], optional = TRUE,
stringsAsFactors = stringsAsFactors) : cannot coerce class
""shiny.tag"" to a data.frame
In your example, the radio_inputs object you get is in fact a list of character, which is pure HTML code. Thus you still get the UI, but they no longer work as shiny inputs.
I guess the only way is to use a pure HTML table if you want radio buttons or any other shiny input objects inside a table.