In shiny, I use plotOutput to output a table, and I want to highlight some cells of it according to some criteria.
Is there any functions in shiny that could achieve this?
Thank you in advance!
======================
Besides to highlighting, I'd also like to add radio buttons on the left of the table, so I could know which lines user chose. Now I'm using renderDataTable to do this, however it doesn't seem to have the highlighting function.
Could it be possible?
Hello a solution without ggplot2 but with package ReporteRs, see the app below for example, the main function is FlexTable :
EDIT : yes, you can put shiny widgets into the HTML table, here an example with checkboxInput for selecting rows :
library(ReporteRs)
library(shiny)
mtcars = mtcars[1:6, ]
runApp(list(
ui = pageWithSidebar(
headerPanel = headerPanel("FlexTable"),
sidebarPanel = sidebarPanel(
selectInput(inputId = "colCol", label = "Col to color", choices = c("None", colnames(mtcars)), selected = "None"),
selectizeInput(inputId = "rowCol", label = "Row to color", choices = rownames(mtcars), multiple = TRUE,
options = list(placeholder = 'None', onInitialize = I('function() { this.setValue(""); }')))
),
mainPanel = mainPanel(
uiOutput(outputId = "tableau"),
br(),
verbatimTextOutput(outputId = "row_select"),
uiOutput(outputId = "car_selected")
)
),
server = function(input, output, session) {
output$tableau <- renderUI({
# here we add check box into the table: it create 6 new input widgets
mtcars$choice = unlist(lapply(1:nrow(mtcars),
FUN = function(x) { paste(capture.output(checkboxInput(inputId = paste0("row", x),
label = paste("Row", x),
value = TRUE)), collapse = " ") }))
tabl = FlexTable( mtcars,
# tune the header and the cells
header.cell.props = cellProperties( background.color = "#003366", padding = 5 ),
body.cell.props = cellProperties( padding = 5 ),
header.text.props = textBold( color = "white" ),
add.rownames = TRUE )
tabl = setZebraStyle( tabl, odd = "#DDDDDD", even = "#FFFFFF" )
# set a column's color
if (input$colCol != "None") {
tabl = setColumnsColors( tabl, j=which(names(mtcars) %in% input$colCol ), colors = "orange" )
}
# set a row's color
if (!is.null(input$rowCol)) {
tabl = setRowsColors( tabl, i=which(rownames(mtcars) %in% input$rowCol ), colors = "#3ADF00" )
}
return(HTML(as.html(tabl)))
})
output$row_select <- renderPrint({
# you can use the input created into the table like others
c("row1" = input$row1, "row2" = input$row2, "row3" = input$row3, "row4" = input$row4, "row5" = input$row5, "row6" = input$row6)
})
output$car_selected <- renderUI({
# if you have more than 6 rows it could be convenient
selected = eval(parse(text = paste("c(", paste(paste0("input$row", 1:6), collapse =", "), ")")))
HTML(paste0("You have selected the following cars : ", paste(rownames(mtcars)[selected], collapse = ", ")))
})
}
))
Which render like this (with check box) :
Related
I need to update/reverse two inputs from drop down inputs upon a button press. At the moment when I hit the swap button (reverse_xz), it reacts however the updatePickerInput doesn't switch my x and z inputs.
I wanted to have the functionality where, once the swap button is clicked, switch the already selected pickerInputs. Then, all the drop down choices (including the selected) need to get reversed. The reason we have to remove the selected choices from vector is to prevent duplicate selections in both x and z inputs.
I am not sure if I have to render the pickerInput ui on the server side?!
This is my code below:
#global.R
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
library(shinyjs)
#variable labels
my_vars <- c("None"= "NONE",
"All" = "all_all",
"Pro" = "Pro_",
"Locomania" = "locomania_Type",
"Racer" = "race")
#ui.R
ui <- shinydashboardPlus::dashboardPage(
header = shinydashboardPlus::dashboardHeader( ),
body = shinydashboard::dashboardBody( box(textOutput("inputs") ) ),
sidebar = shinydashboardPlus::dashboardSidebar(
shinyWidgets::pickerInput(
inputId = "xvar",
label = "X Axis: ",
choices = my_vars,
options = list(
size = 5),
multiple = FALSE,
selected = "all_all"
),
# Button to reverse the choices
shiny::fluidRow(
shiny::column(12, offset = 4,
shinyWidgets::actionBttn(
inputId = "reverse_xz",
label = "",
style = "simple",
color = "primary",
icon = icon("retweet")
)
)
),
shinyWidgets::pickerInput(
inputId = "zvar",
label = "Z Axis: ",
choices = my_vars,
options = list(
size = 5),
multiple = FALSE,
selected = "race"
)
)
)
#server.R
server <- function(input, output, session) {
#
observe({
if(!is.null(input$reverse_xz))
shinyWidgets::updatePickerInput(session, "zvar",
choices = my_vars[!(my_vars %in% input$xvar)],
selected = isolate(input$zvar) )
shinyWidgets::updatePickerInput(session, "xvar",
choices = my_vars[!(my_vars %in% input$zvar)],
selected = isolate(input$xvar) )
})
# These observers remove the selected choices so both pickers are unique
observe({
if(!is.null(input$zvar))
shinyWidgets::updatePickerInput(session, "xvar",
choices = my_vars[!(my_vars %in% input$zvar)],
selected = isolate(input$xvar) )
})
observe({
if(!is.null(input$xvar))
shinyWidgets::updatePickerInput(session, "zvar",
choices = my_vars[!(my_vars %in% input$xvar)],
selected = isolate(input$zvar) )
})
# output inputs
output$inputs <- renderText({ paste0("x var: ", input$xvar,
"\n\n\n z var:", input$zvar,
"\n\n\nreverse press: ", input$reverse_xz) })
}
shiny::shinyApp(ui= ui, server= server)
Thank you in advance. I have looked at some relavant posts however they couldn't guide me much:
Updatepickerinput with change in pickerinput in Shiny
updatePickerInput not updating values after changing tabs in R shiny
update pickerInput by using updatePickerInput in shiny
Look at this and check if it would be OK for you:
#global.R
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
library(shinyjs)
#variable labels
my_vars <- c("None"= "NONE",
"All" = "all_all",
"Pro" = "Pro_",
"Locomania" = "locomania_Type",
"Racer" = "race")
#ui.R
ui <- shinydashboardPlus::dashboardPage(
header = shinydashboardPlus::dashboardHeader( ),
body = shinydashboard::dashboardBody( box(textOutput("inputs") ) ),
sidebar = shinydashboardPlus::dashboardSidebar(
shinyWidgets::pickerInput(
inputId = "xvar",
label = "X Axis: ",
choices = my_vars,
options = list(
size = 5),
multiple = FALSE,
selected = "all_all"
),
# Button to reverse the choices
shiny::fluidRow(
shiny::column(12, offset = 4,
shinyWidgets::actionBttn(
inputId = "reverse_xz",
label = "",
style = "simple",
color = "primary",
icon = icon("retweet")
)
)
),
shinyWidgets::pickerInput(
inputId = "zvar",
label = "Z Axis: ",
choices = my_vars,
options = list(
size = 5),
multiple = FALSE,
selected = "race"
)
)
)
#server.R
server <- function(input, output, session) {
#
observeEvent(input$reverse_xz, {
shinyWidgets::updatePickerInput(session, "zvar",
choices = my_vars[!(my_vars %in% input$zvar)],
selected = input$xvar)
shinyWidgets::updatePickerInput(session, "xvar",
choices = my_vars[!(my_vars %in% input$xvar)],
selected = input$zvar)
})
observe({
if (input$xvar == input$zvar && (length(input$zvar) > 0 && length(input$xvar) > 0)) {
shinyWidgets::updatePickerInput(session, "zvar",
selected = "")
shinyWidgets::updatePickerInput(session, "xvar",
selected = "")
}
})
# output inputs
output$inputs <- renderText({ paste0("x var: ", input$xvar,
"\n\n\n z var:", input$zvar,
"\n\n\nreverse press: ", input$reverse_xz) })
}
shiny::shinyApp(ui= ui, server= server)
I think that maybe this needs an explanation:
if (input$xvar == input$zvar && (length(input$zvar) > 0 && length(input$xvar) > 0))
So, when user choose two the same inputs, then we are updating pickerInputs, so both will have "Nothing selected" as a sign for user that something goes wrong (or that she/he did something wrong). However, "Nothing selected" is like NULL and we can't use NULL like this NULL == "something" inside if, so I'm checking if some input is NULL using length(input$) > 0, because length of NULL is 0. Instead of length(input$) > 0 you could use !is.null(input$) and maybe you should as it is probably more readable, but I'm leaving this decision for you.
I want to create a tabsetPanel that displays a selection of dataframes based on a selectizeInput, while also allowing for permanent edits of the data. I use editable DataTables to render the dataframes but couldn't find a way to save the edits. This example code illustrates my problem:
library(shiny)
library(shinyWidgets)
library(shinyjs)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "dataframes", label = "select dataframes",
choices = c("iris", "mtcars", "DNase", "ChickWeight"), multiple = TRUE, options = list(create = T))
),
mainPanel(
uiOutput("dataframes_rendered")
)
)
)
server <- function(input, output) {
output$dataframes_rendered = renderUI({
# create one tab per df
tabs = lapply(input$dataframes, function(df){
output[[df]] = DT::renderDT(get(df), editable = T, rownames = F, options = list(dom = "t"))
tabPanel(title = df, value = NULL, dataTableOutput(outputId = df), br())
})
# create tabsetPanel
do.call(tabsetPanel, c(tabs, id = "df_tabset"))
})
}
shinyApp(ui = ui, server = server)
I understand why the edits are not saved in my example (the dataframes are re-rendered with every change in the selectizeInput) but, so far, everything I tried to to save the edits and re-render the editeed tables did not work.
Please try the below:
library(shiny)
library(shinyWidgets)
library(shinyjs)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "dataframes", label = "select dataframes",
choices = c("iris", "mtcars", "DNase", "ChickWeight"), multiple = TRUE, options = list(create = T))
),
mainPanel(
tabsetPanel(id = "df_tabset")
)
)
)
server <- function(input, output, session) {
tables <- reactiveValues(
iris = iris,
mtcars = mtcars,
DNase = DNase,
ChickWeight = ChickWeight,
df_tabset = NULL
)
observeEvent(input$dataframes, {
if (length(input$dataframes) > length(tables$df_tabset)) {
df = input$dataframes[! input$dataframes %in% tables$df_tabset]
output[[df]] = renderDT(tables[[df]], editable = T, rownames = F, options = list(dom = "t"))
appendTab(inputId = "df_tabset", select = TRUE,
tabPanel(title = df, value = df, DTOutput(outputId = df))
)
tables$df_tabset = input$dataframes
} else {
df = tables$df_tabset[! tables$df_tabset %in% input$dataframes]
removeTab(inputId = "df_tabset", target = df)
tables$df_tabset = input$dataframes
}
}, ignoreNULL = FALSE, ignoreInit = TRUE)
observeEvent(input$iris_cell_edit, {
tables$iris[input$iris_cell_edit$row, input$iris_cell_edit$col + 1] = input$iris_cell_edit$value
})
observeEvent(input$mtcars_cell_edit, {
tables$mtcars[input$mtcars_cell_edit$row, input$mtcars_cell_edit$col + 1] = input$mtcars_cell_edit$value
})
observeEvent(input$DNase_cell_edit, {
tables$DNase[input$DNase_cell_edit$row, input$DNase_cell_edit$col + 1] = input$DNase_cell_edit$value
})
observeEvent(input$ChickWeight_cell_edit, {
tables$ChickWeight[input$ChickWeight_cell_edit$row, input$ChickWeight_cell_edit$col + 1] = input$ChickWeight_cell_edit$value
})
}
shinyApp(ui = ui, server = server)
I also made a change to your code by adding and removing tabs rather than rerendering all of them each time.
The select = TRUE takes you to the added tab but this can be changed to the default of FALSE to remain on the current tab.
The main way of saving changes is to use reactives/reactiveValues. See DT Shiny and examples.
Update
Based on the comment below, I now create each observeEvent() as needed.
library(shiny)
library(shinyWidgets)
library(shinyjs)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "dataframes", label = "select dataframes",
choices = c("iris", "mtcars", "DNase", "ChickWeight"), multiple = TRUE, options = list(create = T))
),
mainPanel(
tabsetPanel(id = "df_tabset")
)
)
)
server <- function(input, output, session) {
tables <- reactiveValues(
iris = iris,
mtcars = mtcars,
DNase = DNase,
ChickWeight = ChickWeight,
df_tabset = NULL
)
observeEvent(input$dataframes, {
if (length(input$dataframes) > length(tables$df_tabset)) {
df = input$dataframes[! input$dataframes %in% tables$df_tabset]
output[[df]] = renderDT(tables[[df]], editable = T, rownames = F, options = list(dom = "t"))
appendTab(inputId = "df_tabset", select = TRUE,
tabPanel(title = df, value = df, DTOutput(outputId = df))
)
observeEvent(input[[paste0(df, '_cell_edit')]], {
tables[[df]][input[[paste0(df, '_cell_edit')]]$row, input[[paste0(df, '_cell_edit')]]$col + 1] = input[[paste0(df, '_cell_edit')]]$value
})
tables$df_tabset = input$dataframes
} else {
df = tables$df_tabset[! tables$df_tabset %in% input$dataframes]
removeTab(inputId = "df_tabset", target = df)
tables$df_tabset = input$dataframes
}
}, ignoreNULL = FALSE, ignoreInit = TRUE)
}
shinyApp(ui = ui, server = server)
I'm trying to allow users to edit information in certain cells of a dataTable in shiny. In the case where one needs to change numbers into text, or add new texts, how to prevent the new input getting coerced into NA?
Another issue I have is, how to allow user to edit an reactive table (table1 in the sample code)?
library(shiny)
library(datasets)
library(dplyr)
library(ggplot2)
library(plotly)
library(DT)
library(crosstalk)
library(tibble)
######I generated a random list using the mpg data set
data('mpg')
mpg = data.frame(mpg)
nmpg = c()
for (i in 1:dim(mpg)[2]) {
nmpg = cbind(nmpg, sample(x = mpg[, i], size = 2000, replace = T))
i = i+1
}
nmpg = data.frame(nmpg)
colnames(nmpg) = c('Manufacturer', 'Model', 'Engine.Displacement',
'Manufacture.Year', 'Cylinder', 'Transmission',
'Drive.Model', 'City.MPG', 'Highway.MPG', 'Fuel.Type',
'Class')
nmpg$Milage = sample(50000:300000, dim(nmpg)[1], replace = T)
nmpg$Life.Time = sample(seq(0.2, 20, by=0.1), dim(nmpg[1]), replace = T)
nmpg$For.Commercial = sample(c(0, 1), dim(nmpg)[1], replace = T )
for(i in 1:dim(nmpg)[2]){
nmpg[, i] =type.convert(nmpg[,i])
i = i+1
}
runApp( list(
ui = fluidPage(
# Application title
titlePanel("MPG analysis"),
# Sidebar with dropdown menu seletion input for key measuring component
sidebarLayout(
sidebarPanel(
br(),
br(),
selectInput('inputM', 'Measuring: ',
colnames(nmpg), selected = colnames(nmpg)[9]),
selectInput('inputC1', 'Grouping Category: ',
colnames(nmpg), selected = colnames(nmpg)[1]),
selectInput('inputF1', 'Filtering Column: ',
colnames(nmpg), selected = colnames(nmpg)[2]),
uiOutput('filter'),
p(downloadButton('x0', 'Download Selected Data', class = 'text-center'))
),
# Mainpanel is seprated into several tabs using the tablsetPanel function
mainPanel(
tabsetPanel(
tabPanel('Plots', plotlyOutput('barPlot1')),
tabPanel('Different Plots', plotlyOutput('barPlot2')),
tabPanel('Table1', DTOutput('table1')),
tabPanel('Table2', DTOutput('table2'))
)
)
)
), #right ) for ui
# Define server logic required to analzye the data and generate outputs
server = function(input, output) {
output$filter = renderUI({
selectInput('inputF2', 'Filter Item: ',
c('No Filter', unique(nmpg %>% select(input$inputF1))))
})
nmpg_sub = reactive({
if (req(input$inputF2) != 'No Filter'){
nmpg_sub = nmpg %>% filter_at(vars(input$inputF1),
any_vars(. == input$F2))
}
else{
nmpg_sub = nmpg
}
return(nmpg_sub)
})
nmpg_grouped = reactive({
nmpg_sub() %>%
group_by_at(input$inputC1) %>%
summarize(Total.Cars = n(),
Commercial.Cars = sum(For.Commercial),
Ave = mean(!!rlang::sym(input$inputM)),
Trip.Total = sum(Milage),
Year.Total = sum(Life.Time)
) %>%
mutate(Ave.Annual.Milage = Trip.Total / Year.Total,
) %>%
arrange(desc(Total.Cars))
})
output$table1 = renderDT({
datatable(nmpg_grouped(), editable = 'cell',
class = 'cell-border stripe hover responsive compact',
caption = htmltools::tags$caption(
stype = 'caption-side: top; text-align: left;',
htmltools::strong('Table 1: '),
htmltools::em('this is testing data'))
) %>%
formatStyle('Ave', backgroundColor= styleInterval(15, c('default', 'yellow')),
fontWeight = styleInterval(15, c('normal', 'bold'))
)
})
options(DT.options = list(pageLength = 25))
output$table2 = renderDT({
datatable(nmpg, editable = 'cell',
class = 'cell-border stripe hover responsive compact',
caption = htmltools::tags$caption(
stype = 'caption-side: top; text-align: left;',
htmltools::strong('Table 1: '),
htmltools::em('this is testing data'))
)
})
observeEvent(input$table2_cell_edit, {
nmpg <<- editData(nmpg, input$table2_cell_edit,
'table2')
save(nmpg, file = 'InteractiveTable.RData')
})
} #server right )
)) #right )) for runApp and list
I'm building a R Shiny app with a dynamic datatable, using the DT package. Users are able to select two columns within a data.frame that contains more columns.
When users select a column, the datatable is updated and all filters/sorting are reset to default within the datatable object. How can I let the application remember filters and sorting when the given column is not replaced by the user?
Minimal working example below:
library(shiny)
library(DT)
library(data.table)
server <- function(input, output) {
df <- data.frame(
name = rep('a',20),
dimA = 1:20,
dimB = 21:40,
dimC = 41:60
)
observe({
columns <- c('name', input$dim1ID, input$dim2ID)
dfDt <- df[names(df) %in% columns]
output$dtDataTable = DT::renderDataTable(
server = FALSE,
expr = datatable(
dfDt,
filter = 'top',
rownames = FALSE,
selection = 'none',
options = list(sDom = '<"top">rt<"bottom">ip')
)
)
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
## Dimension 1
selectInput(
inputId = "dim1ID",
label = "Dimensie 1",
choices = c('dimA', 'dimB', 'dimC'),
selected = 'dimA'
),
## Dimension 2
selectInput(
inputId = "dim2ID",
label = "Dimensie 2",
choices = c('dimA', 'dimB', 'dimC'),
selected = 'dimB'
)
),
mainPanel(DT::dataTableOutput('dtDataTable'))
)
)
shinyApp(ui = ui, server = server)
This can be done using the DataTables Information, in particular the "state" information (input$tableId_state) which contains the order information of the current table, and input$tableId_search_columns which contains the filtering information by columns. If the columns are fixed (ie in the example above "Dimensie 1" and "Dimensie 2" would always be at the same place), it is much simpler to "remember" which one was ordered (unlike the original example where they are alphabetically reordered when the table is created). For instance based on the above example, the following will work if you sort the "A" column and change the right column from "B" to "C" and back:
library(shiny)
library(DT)
library(data.table)
server <- function(input, output) {
df <- data.frame(
name = rep('a',20),
dimA = 1:20,
dimB = 21:40,
dimC = 41:60
)
values <- reactiveValues(
prevDim1 = "",
prevDim2 = "",
options = list(sDom = '<"top">rt<"bottom">ip',
stateSave = TRUE,
order = list())
)
observeEvent(input$dtDataTable_state$order, {
values$options$order <- input$dtDataTable_state$order
})
observeEvent({
input$dim1ID
input$dim2ID
},{
columns <- c('name', input$dim1ID, input$dim2ID)
dfDt <- df[names(df) %in% columns]
if(length(values$options$order) != 0 && ((values$prevDim1 != input$dim1ID && values$options$order[[1]][[1]] == 1) | (values$prevDim2 != input$dim2ID && values$options$order[[1]][[1]] == 2)) ){
values$options$order = list()
}
values$prevDim1 <- input$dim1ID
values$prevDim2 <- input$dim2ID
output$dtDataTable = DT::renderDataTable(
server = FALSE,
expr = datatable(
dfDt,
filter = 'top',
rownames = FALSE,
selection = 'none',
options = values$options
)
)
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
## Dimension 1
selectInput(
inputId = "dim1ID",
label = "Dimensie 1",
choices = c('dimA', 'dimB', 'dimC'),
selected = 'dimA'
),
## Dimension 2
selectInput(
inputId = "dim2ID",
label = "Dimensie 2",
choices = c('dimA', 'dimB', 'dimC'),
selected = 'dimB'
)
),
mainPanel(DT::dataTableOutput('dtDataTable'))
)
)
shinyApp(ui = ui, server = server)
I have a named list as follows:
vegshop <- list(
"FRUITS" = c("MANGO", "JACKFRUIT", "BANANA"),
'VEGETABLES' = c("OKRA", "BEANS", "CABBAGE")
)
I am trying to order the list based on the names, and this works fine.
vegshop[order(names(vegshop), decreasing = F)]
However when I try to do using an actionButton(), I am getting the following error:
the condition has `length > 1` and only the first element will be used
or
Warning: Error in order: unimplemented type 'list' in 'orderVector1'
A workable example is as follows:
vegshop <- list(
"FRUITS" = c("MANGO", "JACKFRUIT", "BANANA"),
'VEGETABLES' = c("OKRA", "BEANS", "CABBAGE")
)
grocer <- list(
"GROCERY" = c("CEREALS", "PULSES", "TOILETRIES"),
"CLEANERS" = c("DETERGENTS", "FLOOR CLEANERS", "WIPES")
)
library(shiny)
ui <- shinyUI(
fluidPage(
actionButton(style = "font-size: 10px;",inputId = "a2z", label = "Sort-A-Z", icon = icon("sort-alpha-asc")),
radioButtons(inputId = "shopsel", label = "SELECT SHOP", choices = c("SHOPS","SUPERMARKETS"), selected = "SHOPS", inline = TRUE),
uiOutput("shoplist")))
server <- function(session,input, output) {
output$shoplist <- renderUI({
if(input$shopsel == "SHOPS") {
selectInput(inputId = "vegShopList", label = "SHOPLIST", choices = vegshop, selected = c('MANGO', 'JACKFRUIT', 'BANANA'), multiple = TRUE, selectize = FALSE)
} else if(input$shopsel == "SUPERMARKETS") {
selectInput(inputId = "smList", label = "SUPERMARKET", choices = grocer, selected = c('CEREALS', 'PULSES', 'TOILETRIES'), multiple = TRUE, selectize = FALSE)
}
})
observeEvent(input$a2z, {
if(input$shopsel == "SHOPS") {
updateSelectInput(session, inputId = "vegShopList", choices = vegshop[order(vegshop), decreasing = F], selected = NULL)
} else if(input$shopsel == "SUPERMARKETS") {
updateSelectInput(session, inputId = "smList", choices = grocer[order(grocer), decreasing = F], selected = NULL)
}
})
}
shinyApp(ui = ui, server = server)
How could I get the list sorted by the names using the actionButton().
You have a typo:
Outside shiny you write:
vegshop[order(names(vegshop), decreasing = F)]
Within shiny:
vegshop[order(vegshop), decreasing = F]
The same probably holds for the following shiny code snippet:
grocer[order(grocer), decreasing = F]