U have a Shiny app and I run some analysis on a dataframe and get results like this:
brand %
nike 50 %
adidas 35 %
umbro 15 %
Instead of just showing this table in Shiny, is it possible to show the corresponding images (no need to have the numbers) so that they change the order those are shown depending on the applied filter, so the resulting element in Shiny should look like this:
I will have the images in the wwww folder.
Is this possible with Shiny?
Thx!
You can create any output you want with a uiOutput. The renderUI section on the server allows you to create HTML. This way you can identify which images are in the top 3 (which I did by ordering the data frame woth order) and then ouput the ccording images using HTML <img/> by using the HTML builder functions from the htmltools package.
Test the code and see how the order of the images changes depending on the values by reloading the shiny app.
Please Note: this sample will not render the images correctly because I do not know the correct file names they have in your 'www' directory. Adapt the paste0() statement to point to the correct path.
library(shiny)
ui <- fluidPage(
titlePanel("Brand Stats"),
uiOutput("uiTopBrands"),
h1("Top 3 Brands as Table"),
tableOutput("tblTopBrands")
)
# Define server logic required to draw the brand stats
server <- function(input, output) {
df <- data.frame(
brands = c("nike", "adidas", "umbro", "puma", "reusch"),
percent = sample(100, 5))
df$percent <- df$percent / sum(df$percent) * 100
df <- df[order(df$percent, decreasing = TRUE),]
output$uiTopBrands <- renderUI({
Output <- tagList(
h1("Top 3 Brands in Images"),
p(df$brands[1], "will appear below"),
img(src = paste0(df$brands[1], ".jpg"), alt = df$brands[1]), # make sure you create the correct url here with "paste0"
p(df$brands[2], "will appear below"),
img(src = paste0(df$brands[2], ".jpg"), alt = df$brands[2]),
p(df$brands[3], "will appear below"),
img(src = paste0(df$brands[3], ".jpg"), alt = df$brands[3])
)
return(Output)
})
output$tblTopBrands <- renderTable({
df
})
}
# Run the application
shinyApp(ui = ui, server = server)
Related
Currently creating a sales report generating site in R using Shiny. I have been struggling to pull the data from a CSV file the user imports into the dashboard itself. I need to use the data from my fileInput to run a calculation and then actively display these results in my shiny window. Ideally this would be initiated by the action of selecting the CSV file for the fileInput by the user and the calculations would occur.
Let's say this is the CSV that the user inputs
ID
DATE
GROSS
000001
5/22/22
75000
000002
5/25/22
100000
Here is an abridged version of the related code
# Load packages
library(shiny)
library(bslib)
library(shinyWidgets)
library(dplyr)
# Define static variables
mayquota <- 135000
# UI
ui <- navbarPage(title = "Example",
tabPanel(title = "Page 1",
fluidPage(inputPanel(textInput("key", "KEY")),
fixedRow(column(12, fileInput("salesdata", "SALES DATA",
width = 100%, buttonLabel = "SELECT"))),
inputPanel(currencyInput("profits", "PROFITS", format = "dollar",
value = 0, align = "right"),
currencyInput("quota", "QUOTA", format = "dollar",
value = 0, align = "right"),
currencyInput("difference", "DIFFERENCE",
format = "dollar", value = 0,
align = "right")))))
# Server
server <- function(input, output, session) {
prof <- reactive({read_csv(input$profits)})
toListen <- reactive({input$key})
observeEvent(toListen(),
{if(input$key == "test123")
{updateCurrencyInput(session, "quota", value = mayquota)
updateCurrencyInput(session, "difference", value = profits() - mayquota}})
}
# Run application
shinyApp(ui = ui, server = server)
I need to pull the sum of the GROSS column in the CSV and use it to updateCurrencyInput in the form of:
updateCurrencyInput(session, "profits", value = profits())
I was hoping that something like this would work:
toListenFile() <- reactive({input$salesdata})
observeEvent(toListenFile(), {profits <- reactive({prof() %>% summarize(sum(`GROSS`))})})
But I was given the error that summarize from dplyr could not be used on reactive data. So that is where I stand. Any help would be appreciated to achieve a similar function to dplyr in a reactive environment where the CSV data is inputted by the user.
It appears as though I have a solution to my issue, just wanted to share since I already opened the question.
server <- function(input, output, session) {
abcInput <- reactive({
req(input$salesdata)
tibble(read_csv(input$salesdata$datapath))
})
sumprof <- reactive({sum(abcInput()$`GROSS`)})
observeEvent(input$rdata, {updateCurrencyInput(session, "profits", value = sumprof())
})
There might be a more elegant way to achieve this, but this appears to work thus far.
I am creating a shiny app to analyze data in a database. I have set up a slider bar to select a range of values and also have two input boxes to adjust the range on the sliders.
In my simplified code below, the slider works fine, but when you try to update the slider by inputting numbers, I get the following error:
Error: Result must have length 10, not 0
The slider itself still works fine and does what it should in tandem with the selectInput, but as soon as you try to input a number into min or max and hit update, it gives back this error.
Looking online it seems this might be a problem with dplyr/filter(), but I couldn't really find any solutions for my problem and I'm not really sure if that's actually the problem here.
Below is some simplified code with some dummy data. For the slider, I am using the code found here to update the values: R shiny - Combine the slider bar with a text input to make the slider bar more user-friendly
library(shiny)
library(ggplot2)
library(readxl)
library(DT)
library(dplyr)
#Fake Data
MSGRAIN <- data.frame("Year" = c(2018,2018,2018,2017,2016,2010,2010,2000,2000,2000),
"SiteNameNew" = c('A','B','B','B','C','C','C','C','D','D'),
"RiverMile" = 550:559)
ui <- fluidPage(
# Selection Bar
fluidRow(
#Select by River Mile (Manual Input)
column(5,
controlledSliderUI('RiverMile')
),
#Select By Site
column(5,
selectInput("SiteNameNew",
"Site Name:",
c("All", unique(as.character(MSGRAIN$SiteNameNew))
)
)
),
column(6,h4(textOutput('test'))
),
#Create a new row for the table
DT::dataTableOutput("table")
)
)
server <- function(input, output, session) {
range <- callModule(controlledSlider, "RiverMile", 550, 559, c(550,559)
)
range$max <- 559
# Current Year
cyear <- as.numeric(format(Sys.Date(), "%Y"))
# Output to show if selected area has been tested in the last 5 years
output$test <- renderText({
data <- MSGRAIN %>%
filter(SiteNameNew == input$SiteNameNew,
RiverMile >= range$min,
RiverMile <= range$max
)
if (max(data$Year) >= cyear-5){
"This site has been tested in the last 5 years."
} else if (max(data$Year) <= cyear-5){
"This site has not been tested in the last 5 years."
} else {
"Cannot Determine"
}
})
output$table <- DT::renderDataTable(DT::datatable({
data <- MSGRAIN
# Sorts data based on Site Name selected
if (input$SiteNameNew !="All"){
data <- data[data$SiteNameNew == input$SiteNameNew,]
}
# Sorts data based on River Mile selected
if (range !="All"){
data <- data[data$RiverMile >= range$min & data$RiverMile <= range$max,]
}
# Show Data Table
data
})
)
}
# Run the application
shinyApp(ui = ui, server = server)
I think it might be a combination of the code from the link + my code causing the issue, but I am new to shiny and not really sure where things are going wrong. I only have a pretty general understanding of how the observeEvent code is working to update my slider.
I would like to know if its possible to create a shiny app which allows you to upload an excel file and which allows you to select a data range based on sheet name and cell range.
I would like to build upon it in order to showcase some regression analysis but haven't been able to find a starting point.
John, it is always a good idea to take a look at the Shiny gallery and take a look at past answers on Stack Overflow for code examples when faced with issues like these.
Here is a example tutorial for data upload. This can be CSV and not just xls.
https://shiny.rstudio.com/gallery/file-upload.html. But code layout may be useful for you to set up your inputs.
Keep it simple? You might be able to save the data range you want out as a csv file so your users do not have specify data range and sheet. I do this so users just simply need to look at what data sets they want in a select box and not go hunt for the data. See example below. (This may save you lots of error trapping code).
Do not forget to transform your data. Note this example where you might need to factor some of your variables.
As outlined above by Parth see https://www.r-bloggers.com/read-excel-files-from-r/ for more detail on packages Xl_Connect and xlsx. You can specify sheets.
WORKING WITH FILES
Some code snippets that may help you. I have the data blocks already available as csv files. Setting up an selectInput with a list of these files
# in ui.R
selectInput(("d1"), "Data:", choices = data.choices)
I fill data.choices in global.R with this code.
# filter on .csv
data.files <- list.files(path = "data", pattern = ".csv")
# dataset choices (later perhaps break by date)
# sort by date most recent so selectInput takes first one
data.choices <- sort(data.files, decreasing = TRUE)
I have a reactive around the selectInput that then loads the data. (I use data.tables package fread so you will need to install this package and use library(data.tables) if you use this code).
dataset1 <- reactive({
validate(
need(input$d1 != "", "Please select a data set")
)
if (!is.null(input$d1)) {
k.filename <- input$d1 # e.g. 'screendata20160405.csv'
isolate({
## part of code this reactive should NOT take dependency on
# LOAD CSV
s.dt <- fread(file.path("data", k.filename),
na.strings = c("NA", "#N/A")) %>%
rename(ticker = Ticker)
# You might choose to rather dot.the.column.names to save DT issues
#setnames(DT, make.names(colnames(DT)))
# SET KEYS IF RELEVANT
k.id.cols <- c("ticker")
if ("date" %in% names(s.dt)) {
k.id.cols <- c(k.id.cols, "date")
}
setkeyv(s.dt, k.id.cols)
# NAME CHANGES rename columns if necessary
setnames(s.dt, "Short Name", "name")
})
} else {
s.dt <- NULL #input$d1 is null
}
s.dt
})
Note the validates as my data is plotted and I want to avoid error messages. Please appreciate the key setting and renaming columns code above is not necessary but specific to my example, but shows you what you can do to get your data "ready" for user.
GET SHEET NAMES OUT
John this is very useful. Take a look at this long thread on google groups https://groups.google.com/forum/#!topic/shiny-discuss/Mj2KFfECBhU
Huidong Tian had this very useful code at 3/17/14 (but also see Stephane Laurent's code about closing XLConnect too to manage memory):
library(XLConnect)
shinyServer(function(input, output) {
Dat <- reactiveValues()
observe({
if (!is.null(input$iFile)) {
inFile <- input$iFile
wb <- loadWorkbook(inFile$datapath)
sheets <- getSheets(wb)
Dat$wb <- wb
Dat$sheets <- sheets
}
})
output$ui <- renderUI({
if (!is.null(Dat$sheets)) {
selectInput(inputId = "sheet", label = "Select a sheet:", choices = Dat$sheets)
}
})
observe({
if (!is.null(Dat$wb)) {
if (!is.null(input$sheet)){
dat <- readWorksheet(Dat$wb, input$sheet)
print(names(dat))
output$columns <- renderUI({
checkboxGroupInput("columns", "Choose columns",
choices = names(dat))
})
}
}
})
})
shinyUI(pageWithSidebar(
# Include css file;
tagList(
tags$head(
tags$title("Upload Data"),
tags$h1("Test")
)
),
# Control panel;
sidebarPanel(
fileInput(inputId = "iFile", label = "Escolha um arquivo:", accept="application/vnd.ms-excel"),
radioButtons("model", "Escolha do Modelo:",
list("CRS" = "crs",
"VRS" = "vrs")),
br(),
tags$hr(),
uiOutput(outputId = "ui"),
uiOutput(outputId = "columns")
),
# Output panel;
mainPanel()
))
You could include inputs for the file path and cell range, and use a shiny action button to send the input variables to read_excel()
https://shiny.rstudio.com/articles/action-buttons.html
http://readxl.tidyverse.org
I have encountered this problem while developing an app, and reproduced it here in a simplified script using Fruits df.
Basically, i have selectInput box to select a Year, which is a column in Fruits. I create unique list of Years, and feed it into selectInput box.
Then, ideally, i wanted my plot to display only the records for the year I selected. However, as you'll see in my code - the second you uncomment a block of 3 lines to accomplish that, - the plot stops displaying even though there doesn't seem to be any errors. Anybody knows why is this? Thanks in advance!!!
Related question - while debugging this i saw that the input$explore_year is at first "Null". I'm trying to handle this in the code but not sure why the selected="2010" doesn't take care of it automatically.
library(shiny)
library(googleVis)
library(DT)
listOfFruits <- sort(unique(Fruits$Year), decreasing = FALSE)
ui <- fluidPage(title = "Fruits Bug Recreated",
fluidRow(
column(3,
wellPanel(
uiOutput("choose_year"),
br()
)),
column(9,
tags$hr(),
htmlOutput("view")
)),
fluidRow(DT::dataTableOutput("tableExplore"))
)
server <- function(input, output) {
output$view <- renderGvis({
#Uncomment these 3 lines to see how the plot stops displaying.
# local_exloreYear <- input$explore_year
# if (is.null(local_exloreYear)) {local_exloreYear <- "2010"}
# FruitsSubset <- subset(Fruits, Year == local_exloreYear)
#------------I wanted to use the commented line below instead of the
#that follows
#gvisBubbleChart(FruitsSubset, idvar="Fruit",
#-------------
gvisBubbleChart(Fruits, idvar="Fruit",
xvar="Sales", yvar="Expenses",
colorvar="Year", sizevar="Profit",
options=list(
hAxis='{minValue:70, maxValue:125, title:"Sales"}',sortBubblesBySize=TRUE,
vAxis='{title: "Expenses",minValue:60, maxValue:95}'
))
})
# Drop-down selection box for dynamic choice of minutes in the plans to compare
output$choose_year <- renderUI({
selectInput("explore_year", "Select Year", as.list(listOfFruits),selected ="2010")
})
output$tableExplore <- DT::renderDataTable(DT::datatable({
FruitsSubset <- subset(Fruits, Fruits$Year == input$explore_year)
myTable <-FruitsSubset[,c(1,2,3,4,5,6)]
data <- myTable
data
},options = list(searching = FALSE,paging = FALSE)
))
}
shinyApp(ui = ui, server = server)
Like i wrote in the comments you can solve it by make the rendering conditional on the input being non-NULL.
output$view <- renderGvis({
if(!is.null(input$explore_year)){
...
}
})
Nevertheless, I donĀ“t think it is really intended that you have to do that, as in other render functions it is not required e.g. in the DT::renderDataTable(), where you also use the same input (being NULL initially).
Therefore, I would suggest reporting it as a bug.
I would like to generate a new html table every time the user changes the parameter "mass" below, and then exhibit it dynamically
server.R
library(hwriter)
shinyServer(function(input, output) {
output$distPlot <- renderText({
mass <- as.numeric(input$mass)
win <- as.numeric(input$sswin)
m1 <- mass-win/2
m2 <- mass+win/2
etr <- paste0("http://rest.kegg.jp/find/compound/", m1, "-", m2, "/exact_mass")
tb <- read.table(etr)
colnames(tb) <- c("id", "mass")
#p <- openPage('test.html')
tblk <- cbind(paste0("http://www.kegg.jp/dbget-bin/www_bget?", tb[,1]), NA)
#hwrite(tb, p, link = tblk)
hwrite(tb, link = tblk)
#close(p)
})
})
ui.R
library(shiny)
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
textInput("mass", "Mass:", "200.05"),
textInput("sswin", "Search window:", "0.5")
),
# Show a plot of the generated distribution
mainPanel(
#plotOutput("distPlot"),
#includeHTML("test.html")
uiOutput("distPlot")
)
)
))
The table is being generated, but I don't know how to update it.
I figured out a way and edited above.
I encountered the very same problem as user1265067 and I found a solution to the problem.
Since this questions lacks an answer and in the case that other users stumble upon this thread, please find my solution (with a working toy example) in this thread: Shiny - populate static HTML table with filtered data based on input
In short, I wrap the static html table in a function, source it in the server and call it in the renderUI() function.