The following attempts to put Shiny fileInput() and textInput() side by side.
A simple server.R file:
shinyServer(function(input, output) {} )
And the following ui.R:
# Custom function(s) to get file- and text-Input side by side
# Based on: <http://stackoverflow.com/a/21132918/1172302>
# options(shiny.error=browser)
# Globals
display.inline.block <- "display:inline-block"
class.input.small = "input-small"
FileInputId <- "SampleFile"
FileInputLabel <- "Sample"
TextInputId <- "SampleLabel"
TextInputLabel <- "Label"
TextInputLabelDefault <- "Sample Label"
# helper functions
fileInput.custom <- function (inputId, label, ...)
{
tagList(tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "file", ...)
)
}
textInput.custom <- function (inputId, label, value = "",...)
{
tagList(tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "text", value = value,...)
)
}
filetextInput <- function (fileId, fileLabel, textId, textLabel, textValue, divstyle, ...)
{
# sample file
div(style = divstyle,
fileInput.custom(inputId = fileId,
label = fileLabel,
class = class.input.small))
# label for sample, to be used in plot(s)
div(style = divstyle,
textInput.custom(inputId = textId,
label = textLabel,
value = textValue,
class = class.input.small))
}
# Shiny UI
shinyUI(
fluidPage(
# sample input
div(style = display.inline.block,
fileInput.custom(inputId = FileInputId,
label = FileInputLabel)
),
# label for sample
div(style = display.inline.block,
textInput.custom(inputId = TextInputId,
label = TextInputLabel,
value = TextInputLabelDefault)
),
hr(),
filetextInput(
fileId = FileInputId,
fileLabel = FileInputLabel,
textId = TextInputId,
textLabel = TextInputLabel,
textValue = TextInputLabelDefault,
divstyle = display.inline.block)
)
)
The above results in:
As shown in the screenshot, it works using two separate divs. Why doesn't it work in the case of the filetextInput() function?
Functions return the last evaluated value, so in your case the first part is lost. E.g. :
function(){
"a"
"b"
}
returns "b"
so you don't want that. Use a div or a tagList.
filetextInput <- function (fileId, fileLabel, textId, textLabel, textValue, divstyle, ...)
{
div(
# sample file
div(style = divstyle,
fileInput.custom(inputId = fileId,
label = fileLabel,
class = class.input.small)
),
# label for sample, to be used in plot(s)
div(style = divstyle,
textInput.custom(inputId = textId,
label = textLabel,
value = textValue,
class = class.input.small)
)
)
}
Related
In shinywidgets::pickerInput you can pass a named list of data (here, nms) to pickerInput to create headings and choices from the list.
For example:
library(shiny)
library(shinyWidgets)
nms = list('Consumers' = c('a', 'b'),
'Firms' = c('c', 'd'))
ui <- fluidPage(
pickerInput(
inputId = "somevalue",
label = "A label",
choices = nms,
selected = 'a',
multiple=T
),
verbatimTextOutput("value")
)
server <- function(input, output) {
output$value <- renderPrint(input$somevalue)
}
shinyApp(ui, server)
I wish to replicate this heading/choices functionality using shinyWidgets::awesomeCheckboxGroup. Previously I posted this question seeking an answer and was advised that Map could do this. However, Map creates two input objects; I do not require this. The user's choices all need to be fed into a single input object.
Is it possible to create headings in awesomeCheckboxGroup while retaining a single input object?
I looked at the source code and modified awesomeCheckboxGroup and the underlying function generateAwesomeOptions to make it work. Now we can use named lists, which will create sub labels, and unnamed vectors, which will produce the normal checkboxes. We could still optimize the code a bit, and I am also not sure how the labels should look like. But basically you can give them a special class attribute and then use CSS to change the appearance of the labels.
library(shiny)
library(shinyWidgets)
generateAwesomeOptions2 <- function (inputId, choices, selected, inline, status, flag = FALSE) {
# if input is a list, flag will be set to `TRUE` by the calling function
if (flag) {
options <- mapply(choices, names(choices), FUN = function(lchoices, lname) {
lchoices <- shinyWidgets:::choicesWithNames(lchoices)
tags$div(
tags$label(lname, style = "margin-bottom: 10px;"),
mapply(lchoices, names(lchoices), FUN = function(value, name) {
inputTag <- tags$input(type = "checkbox", name = inputId,
value = value, id = paste0(inputId, value))
if (value %in% selected)
inputTag$attribs$checked <- "checked"
if (inline) {
tags$div(class = paste0("awesome-checkbox checkbox-inline checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
# flag is not set `TRUE` this will create the normal checkboxes
else {
tags$div(class = paste0("awesome-checkbox checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)
)
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)} else {
options <- mapply(choices, names(choices), FUN = function(value,
name) {
inputTag <- tags$input(type = "checkbox", name = inputId,
value = value, id = paste0(inputId, value))
if (value %in% selected)
inputTag$attribs$checked <- "checked"
if (inline) {
tags$div(class = paste0("awesome-checkbox checkbox-inline checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
else {
tags$div(class = paste0("awesome-checkbox checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)
}
tags$div(class = "shiny-options-group", options)
}
awesomeCheckboxGroup2 <- function (inputId, label, choices, selected = NULL, inline = FALSE,
status = "primary", width = NULL) {
if(!is.list(choices)) {
choices <- shinyWidgets:::choicesWithNames(choices)
selected <- shiny::restoreInput(id = inputId, default = selected)
if (!is.null(selected))
selected <- shinyWidgets:::validateSelected(selected, choices, inputId)
options <- generateAwesomeOptions2(inputId, choices, selected,
inline, status = status)
} else {
choices2 <- unlist(unname(choices))
choices2 <- shinyWidgets:::choicesWithNames(choices2)
selected <- shiny::restoreInput(id = inputId, default = selected)
if (!is.null(selected))
selected <- shinyWidgets:::validateSelected(selected, choices2, inputId)
options <- generateAwesomeOptions2(inputId, choices, selected,
inline, status = status, flag = TRUE)
}
divClass <- "form-group shiny-input-container shiny-input-checkboxgroup awesome-bootstrap-checkbox"
if (inline)
divClass <- paste(divClass, "shiny-input-container-inline")
awesomeTag <- tags$div(id = inputId, style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), class = divClass,
tags$label(label, `for` = inputId, style = "margin-bottom: 10px;"),
options)
shinyWidgets:::attachShinyWidgetsDep(awesomeTag, "awesome")
}
nms = list('Consumers' = c('a', 'b'),
'Firms' = c('c', 'd'))
nms1 = c("Test", "Test2")
ui <- fluidPage(
awesomeCheckboxGroup2(
inputId = "somevalue",
label = "Make a choice:",
choices = nms
),
verbatimTextOutput("value")
)
server <- function(input, output) {
output$value <- renderPrint(input$somevalue)
}
shinyApp(ui, server)
I have a selectizeInput that can take multiple values (here: names of datasets). The current state of this input is monitored by an observeEvent, which renders the corresponding datatables and dynamically populates a tabsetPanel with the outputs. It all works fine when I choose new values directly in the input field. However, when I supply multiple new values with the updateSelectizeInput function, all tabs contain the same dataframe corresponding to the last value in the selected argument.
The example below illustrates the problem. The UI reacts as expected when using the input field, but when pressing the "Add all at once" button all tabs contain the same dataframe.
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),
actionButton(inputId = "add_all", label = "Add all at once")
),
mainPanel(tabsetPanel(id = "df_tabset"))
)
)
server <- function(input, output, session) {
tables <- reactiveValues(iris = iris, mtcars = mtcars, DNase = DNase, ChickWeight = ChickWeight,
df_tabset = NULL) # keeps track of currently displayed tables
observeEvent(input$dataframes, {
if (length(input$dataframes) > length(tables$df_tabset)) { # new dataframes are selected
new_dfs = setdiff(input$dataframes, tables$df_tabset)
for(df in new_dfs){
output[[df]] = renderDT(tables[[df]], editable = T, rownames = F, options = list(dom = "t")) # DOES NOT WORK AS EXPECTED IF THERE is > 1 NEW DF
appendTab(inputId = "df_tabset", select = TRUE,
tabPanel(title = df, value = df, DTOutput(outputId = df))
)
}
tables$df_tabset = input$dataframes # update
} 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$add_all, {
updateSelectizeInput(session, "dataframes", selected = c("iris", "mtcars", "DNase", "ChickWeight"))
})
}
shinyApp(ui = ui, server = server)
You have to use local (see here).
observeEvent(input$dataframes, {
if (length(input$dataframes) > length(tables$df_tabset)) { # new dataframes are selected
new_dfs = setdiff(input$dataframes, tables$df_tabset)
for(df in new_dfs){
local({
.df <- df
output[[.df]] = renderDT(tables[[.df]], editable = TRUE,
rownames = FALSE, options = list(dom = "t"))
})
appendTab(inputId = "df_tabset", select = TRUE,
tabPanel(title = df, value = df, DTOutput(outputId = df))
)
}
tables$df_tabset = input$dataframes # update
} 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)
I am creating a shiny app that dynamically creates a set of input boxes, the input from those boxes is then used to further create another set of input boxes.
First I get input from the "number of questions" text box and dynamically add that number of "question panels" to the ui (example below).
Issue
The issue is that the generated objects are always attached to the output, and I can't figure out how to grab new user input from them. How could I get this input, and then generate a second round of "answer" input boxes?
ui.R
shinyUI(fluidPage(
titlePanel("RSurvey"),
numericInput("questionCountText", label = h3("Number of Questions"), value = 1),
uiOutput("questionSet")
))
server.R
shinyServer(
function(input, output) {
output[['questionSet']] <- renderUI({
outputHtml = ""
count = input$questionCountText
if(count > 0) {
for(i in 1:input$questionCountText) {
outputHtml = paste0(outputHtml, questionPanel(i))
}
}
HTML(outputHtml)
})
}
)
questionPanel = function(i)
{
return(wellPanel(div(style="display:inline-block", textInput("questionText", label = h4(paste0("Question ", i)), "Enter your question")),
numericInput1("answerCountText", label = h4("Number of Answers"), value = 3, onchange="onTextChanged(this.value)")))
}
numericInput1 = function (inputId, label, value = "", ...)
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "numeric", value = value, ...))
}
Hello try this for example :
#ui
ui <- fluidPage(
titlePanel("RSurvey"),
numericInput("questionCountText", label = h3("Number of Questions"), value = 1),
uiOutput("questionSet"),
verbatimTextOutput(outputId = "answers")
)
#server
server <- function(input, output) {
output[['questionSet']] <- renderUI({
outputHtml = ""
count = input$questionCountText
if(count > 0) {
for(i in 1:input$questionCountText) {
outputHtml = paste0(outputHtml, questionPanel(i))
}
}
HTML(outputHtml)
})
output$answers <- renderPrint({
invisible(
sapply(
X = seq_len(input$questionCountText),
FUN = function(i) {
cat(paste0("Question", i, "\n", input[[paste0("questionText", i)]], "\n", input[[paste0("answerCountText", i)]], "\n"))
}
)
)
})
}
#app
shinyApp(ui = ui, server = server)
#utils
questionPanel = function(i) {
wellPanel(
div(
style="display:inline-block",
textInput2(inputId = paste0("questionText", i), label = h4(paste0("Question ", i)), placeholder = "Enter your question")
),
numericInput1(inputId = paste0("answerCountText", i), label = h4("Number of Answers"), value = 3, onchange="onTextChanged(this.value)")
)
}
numericInput1 = function (inputId, label, value = "", ...) {
div(style="display:inline-block", class = "form-group shiny-input-container",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "number", value = value, ...))
}
`%AND%` <- shiny:::`%AND%`
textInput2 <- function (inputId, label, value = "", placeholder = NULL, width = NULL)
{
if (is.null(placeholder)) {
div(class = "form-group shiny-input-container", style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), label %AND%
tags$label(label, `for` = inputId), tags$input(id = inputId,
type = "text", class = "form-control", value = value))
} else {
div(class = "form-group shiny-input-container", style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), label %AND%
tags$label(label, `for` = inputId), tags$input(id = inputId, placeholder = placeholder,
type = "text", class = "form-control", value = value))
}
Is it possible to make a sliderInput that shows the values in decreasing order (from left to right; eg. 5 4 3 2 1)?
runApp(
list(
ui = fluidPage(
sliderInput("test","", min=5, max=1, value = 3, step=1)
),
server = function(input,output) {}
)
)
EDIT 2017-10-13: This function is now available in package shinyWidgets (with a different name : sliderTextInput()).
Hi you can write your own slider function like this (it's a little dirty...) :
sliderValues <- function (inputId, label, values, from, to = NULL, width = NULL) {
sliderProps <- shiny:::dropNulls(list(class = "js-range-slider",
id = inputId,
`data-type` = if (!is.null(to)) "double",
`data-from` = which(values == from) - 1,
`data-to` = if (!is.null(to)) which(values == to) - 1,
`data-grid` = TRUE,
`data-values` = paste(values, collapse = ", ")
))
sliderProps <- lapply(sliderProps, function(x) {
if (identical(x, TRUE))
"true"
else if (identical(x, FALSE))
"false"
else x
})
sliderTag <- div(class = "form-group shiny-input-container",
style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"),
if (!is.null(label))
shiny:::controlLabel(inputId, label), do.call(tags$input,
sliderProps))
dep <- list(htmltools::htmlDependency("ionrangeslider", "2.0.12", c(href = "shared/ionrangeslider"),
script = "js/ion.rangeSlider.min.js",
stylesheet = c("css/ion.rangeSlider.css",
"css/ion.rangeSlider.skinShiny.css")))
htmltools::attachDependencies(sliderTag, dep)
}
The point to do this is to use the values attribute from ionrangeslider (see section Using custom values array here)
The downside is the value of the input you retrieve server-side isn't the value of the slider but the index of the value (starting from 0).
You can use this function like this :
library("shiny")
runApp(
list(
ui = fluidPage(
# you have to pass the values you want in the slider directly to th function
sliderValues(inputId = "test", label = "", from = 5, values = 5:1),
verbatimTextOutput(outputId = "slidervalue")
),
server = function(input,output) {
output$slidervalue <- renderPrint({
# Careful ! : input$test isn't the expected value !!!
(5:1)[input$test + 1]
})
}
)
)
And bonus : it works with characters vectors too :
runApp(
list(
ui = fluidPage(
sliderValues(inputId = "test", label = "", from = "g", to = "o", values = letters),
verbatimTextOutput(outputId = "slidervalue")
),
server = function(input,output) {
output$slidervalue <- renderPrint({
# Careful ! : input$test isn't the expected value !!!
letters[input$test + 1]
})
}
)
)
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) :