table command fails with shiny input variable - r

I'm creating my first shiny app, everything works fantastic when using ggplot2 but using other base R or vcd plots has me stuck. I'd like the user to be able to select a tabling variable and then view a resulting mosaic or association plot. My server code fails at the table command. Things I've already tried are commented out below.
Thanks for the help.
library(shiny)
library(shinydashboard)
library(vcd)
header = dashboardHeader(title = 'Min Reproducible Example')
sidebar = dashboardSidebar()
body = dashboardBody(
fluidRow(plotOutput('plot'), width=12),
fluidRow(box(selectInput('factor', 'Select Factor:', c('OS', 'Gender'))))
)
ui = dashboardPage(header, sidebar, body)
server = function(input, output){
set.seed(1)
df = data.frame(Condition = rep(c('A','B','C','D'), each = 300),
Conversion = c(sample(c('Convert','Not-Convert'), 300, replace = TRUE, prob = c(0.9, 0.1)),
sample(c('Convert','Not-Convert'), 300, replace = TRUE, prob = c(0.7, 0.3)),
sample(c('Convert','Not-Convert'), 300, replace = TRUE, prob = c(0.5, 0.5)),
sample(c('Convert','Not-Convert'), 300, replace = TRUE, prob = c(0.2, 0.8))),
Gender = sample(c('M','F'), 1200, replace = TRUE),
OS = rep(sample(c('Web','iOS','Android'), 1200, replace = TRUE), times = 2))
#tried this
#table1 = reactive({
# with(df, table(Condition, Conversion, input$factor))
#})
output$plot = renderPlot({
#fails here:
table1 = with(df, table(Condition, Conversion, input$factor))
#also tried these
#table1 = with(df, table(Condition, Conversion, as.character(isolate(reactiveValuesToList(input$factor)))))
#also tried table1 = with(df, table(Condition, Conversion, input$factor))
#also tried table1 = table(df$Condition, df$Conversion, paste0('df$', input$factor))
#then I want some categorical plots
assoc(table1, shade=TRUE)
#or mosaicplot(table1, shade=TRUE)
})
}
shinyApp(ui, server)

An easy fix would be to use 'starts_with' from dplyr in a select() statement on your input variable
library('dplyr')
output$plot = renderPlot({
df <- select(df, Condition, Conversion, tmp_var = starts_with(input$factor))
table1 = with(df, table(Condition, Conversion, tmp_var))
mosaicplot(table1, shade=TRUE)
})
}

Related

How to solve the error in highcharOutput in shiny tool?

I'm working on cancer data from TCGA.
Im new to shiny and creating web applications (learning it!!)
I'm working on a shiny tool to plot the volcanoplot using highcharter package.
sometimes I'm successfully able to plot the volcanoplot in the UI. but sometimes it fails to plot it and throws an error saying,
"An error has occurred!
could not find function "highchartOutput"
and one warning message is given for the error;
Listening on http://127.0.0.1:5335
Warning: Error in highchartOutput: could not find function "highchartOutput"
83: dots_list
82: div
81: tabPanel
I think there is some problem with the tabset panel.
is this error has anything to do with indentation? (wherever I adjust the brackets it works magically. not sure how it works for sometimes.)
I am attaching the UI and server files with this post.
code is attached for one type of comparison
UI file below:
library(shiny)
# Define UI for application
shinyUI(fluidPage(
# Application title
titlePanel("miR-Gyn-Explorer"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
## select the count matrix
selectInput("file", label = h3("Count Matrix"),
choices = list("Stage I - Normal" = list("TCGA-BRCA" = "Data/TCGA-BRCASI_NT.rda", "TCGA-UCEC" = "Data/TCGA-UCECSI_NT.rda"))),
## select the phenodata of samples
selectInput("phenofile", label = h3("Sample Phenodata"),
choices = list("Stage I - Normal" = list("TCGA-BRCA" = "Data/TCGA-BRCA_phenoSI_NT.rda", "TCGA-UCEC" = "Data/TCGA-UCEC_phenoSI_NT.rda"))),
submitButton("Update View")
),
# Show a plot of the generated distribution
mainPanel(
tabsetPanel(
tabPanel("DEmiRNA", DT::dataTableOutput("DEmiRNA"),
"Volcano-Plot", highchartOutput("volcanoPlot", height = "500px"))
#tabPanel("miRNA-Targets", DT::dataTableOutput('miRTarget'),
#plotOutput("GO"))
)
)
)
)
)
server file:
library(shiny)
library(R.utils)
##function to find the DEmiRNA by edgeR method
library(limma)
library(edgeR)
library(DT)
library(dplyr)
library(multiMiR)
library(miRBaseConverter)
library(ggplot2)
#library(ggrepel)
library(tidyverse)
library(highcharter)
library(org.Hs.eg.db)
library(clusterProfiler)
library(purrr)
gdcDEmiRNA <- function(counts, groups, comparison, filter=TRUE) {
## method = edgeR
dge = DGEList(counts = counts, samples = groups)
group <- factor(groups$group)
design <- model.matrix(~0+group)
colnames(design) <- levels(group)
contrast.matrix <- makeContrasts(contrasts=comparison,
levels=design)
keep = filterByExpr(dge,design)
dge <- dge[keep,,keep.lib.sizes = TRUE]
dge <- calcNormFactors(dge)
dge <- estimateDisp(dge, design)
fit <- glmFit(dge, design)
lrt <- glmLRT(fit, contrast=contrast.matrix)
DEGAll <- lrt$table
DEGAll$FDR <- p.adjust(DEGAll$PValue, method = 'fdr')
o <- order(DEGAll$FDR)
DEGAll <- DEGAll[o,]
return (DEGAll)
}
# Define server logic required to perform the DEmiRNA analysis
server <- function(input, output) {
d <- reactive({
#DEmiRNA calculation
file <- load(input$file)
phenofile <- load(input$phenofile)
if(file == "SI_NT"){
if(phenofile == "phenoSI_NT"){
DEmiRNA <- gdcDEmiRNA(counts = SI_NT, groups = phenoSI_NT,
comparison = 'StageI-Normal')
}
}
})
output$DEmiRNA <- DT::renderDataTable({
mir <- d()
#mir <- mir[mir$FDR < input$FDR,]
})
output$volcanoPlot <- renderHighchart({
x <- d()
x$mirna <- rownames(x)
x$sig <- ifelse(x$PValue < 0.05 & abs(x$logFC) > 0.57, "DEmiRNA", "Not Regulated")
hc <- highchart() %>%
hc_add_series(x, "scatter", hcaes(logFC, -log10(PValue), group = sig, value = mirna),
color = c('rgba(67, 67, 72, 0.6)', 'rgba(124, 181, 236, 0.6)'),
enableMouseTracking = c(TRUE, TRUE),
showInLegend = TRUE, marker = list(radius = 4)) %>%
hc_tooltip(pointFormat = "{point.value}", headerFormat = "") %>%
hc_xAxis(title = list(text = "Log fold change"), gridLineWidth = 1,
tickLength = 0, startOnTick = "true", endOnTick = "true", min = -6, max = 6) %>%
hc_yAxis(title = list(text = "-Log10(p-value)")) %>%
hc_chart(zoomType = "xy", width=700) %>%
hc_exporting(enabled = TRUE, filename = "volcano")
hc
})
}
any comment and help from you guys is appreciated
Thank you in advance!
-Ankita

R error when subsetting dataframe: "Error in ==: comparison of these types is not implemented"

I have some code which results in an error when I try to subset my dataframe.
The error occurs when I call for the makePopupPlot() function. R apparently doesn't like the data types I'm trying to compare inside the subset() function. I'm very confused, as the code worked perfectly yesterday and I didn't change anything.
The error does not occur when I manually run the makePopupPlot() function line-by-line. That means the error is most likely the result of using df$WK_NAAM[i] as input for the makePopupPlot() function.
The full error message as well as a reproducable example are provided below. Does anyone know how to fix this?
Listening on http://127.0.0.1:6941
Warning in eval(e, x, parent.frame()) :
Incompatible methods ("Ops.data.frame", "Ops.factor") for "=="
Warning: Error in ==: comparison of these types is not implemented
60: eval
59: eval
58: subset.data.frame
55: makePopupPlot [#8]
54: FUN [#29]
53: lapply
52: server [#28]
Error in plotData["WK_NAAM"] == clickedArea :
comparison of these types is not implemented
Reproducable example:
library(sf)
library(dplyr)
library(shiny)
library(shinydashboard)
library(leaflet)
library(leafpop)
library(ggplot2)
library(reshape2)
set.seed(1)
# Let's use this municipality in the example
inputMunicipality = "Landgraaf"
# Download municipality geometry
df <-st_read(URLencode(sprintf("https://geo.leefbaarometer.nl/leefbaarometer/wfs?version=1.0.0&cql_filter=gemeente=%s%s%s&request=GetFeature&typeName=leefbaarometer:wijken_2018&srsName=epsg:4326&outputFormat=json",
"'", inputMunicipality, "'")))[c("WK_NAAM", "WK_CODE")]
# Add some fake scores
df$environmentScore <- sample(10, size = nrow(df), replace = TRUE)
df$facilitiesScore <- sample(10, size = nrow(df), replace = TRUE)
df$housingScore <- sample(10, size = nrow(df), replace = TRUE)
df$safetyScore <- sample(10, size = nrow(df), replace = TRUE)
# Define dashboard UI
ui <- dashboardPage(
dashboardHeader(title = "Testing reactive popup on click event!"),
dashboardSidebar(),
dashboardBody(
fluidRow(leafletOutput("myMap")
)
)
)
# Define server logic
server <- function(input, output) {
# Function for generation a popup based on the area clicked by the user
makePopupPlot <- function (clickedArea, df) {
# prepare the df for ggplot
noGeom <- st_drop_geometry(df)
plotData <- noGeom[c("WK_NAAM", "environmentScore", "facilitiesScore","housingScore", "safetyScore")]
plotDataSubset <- subset(plotData, plotData['WK_NAAM'] == clickedArea)
plotDataMelt = melt(plotDataSubset, id.vars = "WK_NAAM")
popupPlot <- ggplot(data = plotDataMelt, aes(x = variable, y = value, fill=value)) +
geom_bar(position="stack", stat="identity", width = 0.9) +
scale_fill_steps2(
low = "#ff0000",
mid = "#fff2cc",
high = "#70ad47",
midpoint = 5) +
coord_flip() +
ggtitle(paste0("Score overview in ", clickedArea)) +
theme(legend.position = "none") +
theme(plot.margin = unit(c(0,0.5,0,0), "cm"), plot.title = element_text(size = 10))
return (popupPlot)
}
# popup plot list
p <- as.list(NULL)
p <- lapply(1:nrow(df), function(i) {
p[[i]] <- makePopupPlot(df$WK_NAAM[i], df)
})
output$myMap <- renderLeaflet({
leaflet() %>%
addProviderTiles(providers$nlmaps.grijs) %>%
addPolygons(data = df, popup = popupGraph(p, type = "svg"))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Minor issue here. Either wrap your column in double squared brackets or rather, the proper subset() style, just call the variable name unquotet:
library(sf)
library(dplyr)
library(shiny)
library(shinydashboard)
library(leaflet)
library(leafpop)
library(ggplot2)
library(reshape2)
set.seed(1)
# Let's use this municipality in the example
inputMunicipality = "Landgraaf"
# Download municipality geometry
df <-st_read(URLencode(sprintf("https://geo.leefbaarometer.nl/leefbaarometer/wfs?version=1.0.0&cql_filter=gemeente=%s%s%s&request=GetFeature&typeName=leefbaarometer:wijken_2018&srsName=epsg:4326&outputFormat=json",
"'", inputMunicipality, "'")))[c("WK_NAAM", "WK_CODE")]
# Add some fake scores
df$environmentScore <- sample(10, size = nrow(df), replace = TRUE)
df$facilitiesScore <- sample(10, size = nrow(df), replace = TRUE)
df$housingScore <- sample(10, size = nrow(df), replace = TRUE)
df$safetyScore <- sample(10, size = nrow(df), replace = TRUE)
# Define dashboard UI
ui <- dashboardPage(
dashboardHeader(title = "Testing reactive popup on click event!"),
dashboardSidebar(),
dashboardBody(
fluidRow(leafletOutput("myMap")
)
)
)
# Define server logic
server <- function(input, output) {
# Function for generation a popup based on the area clicked by the user
makePopupPlot <- function (clickedArea, df) {
# prepare the df for ggplot
noGeom <- st_drop_geometry(df)
plotData <- noGeom[c("WK_NAAM", "environmentScore", "facilitiesScore","housingScore", "safetyScore")]
plotDataSubset <- subset(plotData, WK_NAAM == clickedArea)
plotDataMelt = melt(plotDataSubset, id.vars = "WK_NAAM")
popupPlot <- ggplot(data = plotDataMelt, aes(x = variable, y = value, fill=value)) +
geom_bar(position="stack", stat="identity", width = 0.9) +
scale_fill_steps2(
low = "#ff0000",
mid = "#fff2cc",
high = "#70ad47",
midpoint = 5) +
coord_flip() +
ggtitle(paste0("Score overview in ", clickedArea)) +
theme(legend.position = "none") +
theme(plot.margin = unit(c(0,0.5,0,0), "cm"), plot.title = element_text(size = 10))
return (popupPlot)
}
# popup plot list
p <- as.list(NULL)
p <- lapply(1:nrow(df), function(i) {
p[[i]] <- makePopupPlot(df$WK_NAAM[i], df)
})
output$myMap <- renderLeaflet({
leaflet() %>%
addProviderTiles(providers$nlmaps.grijs) %>%
addPolygons(data = df, popup = popupGraph(p, type = "svg"))
})
}
# Run the application
shinyApp(ui = ui, server = server)

R - Group by Date then Sum by unique ID

Here is my code - creating a dashboard that will filter by date. One tab will show our wellness survey data, the other will show post-practice loading data. I am pulling in the first 3 columns from "post.csv" which are Date, Name, Daily. Then I am looking to create and add the next 3 columns with the math.
Where I am first stuck is that I need my Daily_Load to aggregate data for a specific athlete on the given Date. Then I need to create a rolling 7-day sum for each athlete using the Daily load data from the last 7 days (including Date selected). A 28-Day Rolling Sum/4 and 7-Day/28-Rolling is the last piece.
Thanks again for all of the help!
library(shiny)
library(dplyr)
library(lubridate)
library(ggplot2)
library(DT)
library(zoo)
library(tidyr)
library(tidyverse)
library(data.table)
library(RcppRoll)
AM_Wellness <- read.csv("amwell.csv", stringsAsFactors = FALSE)
Post_Practice <- read.csv("post.csv", stringsAsFactors = FALSE)
Post_Data <- Post_Practice[, 1:3]
Daily_Load <- aggregate(Daily~ ., Post_Data, sum)
Acute_Load <- rollsum(Post_Data$Daily, 7, fill = NA, align = "right")
Chronic_Load <- rollsum(Post_Data$Daily, 28, fill = NA, align = "right")/4
Post_Data['Day Load'] <- aggregate(Daily~ ., Post_Data, sum)
Post_Data['7-Day Sum'] <- Acute_Load
Post_Data['28-Day Rolling'] <- Chronic_Load
Post_Data['Ratio'] <- Acute_Load/Chronic_Load
ui <- fluidPage(
titlePanel("Dashboard"),
sidebarLayout(
sidebarPanel(
dateInput('date',
label = "Date",
value = Sys.Date()
),
selectInput("athleteInput", "Athlete",
choices = c("All"))
),
mainPanel(tabsetPanel(type = "tabs",
tabPanel("AM Wellness", tableOutput("amwell")),
tabPanel("Post Practice", tableOutput("post"))
)
)
)
)
server <- function(input, output) {
output$amwell <- renderTable({
datefilter <- subset(AM_Wellness, AM_Wellness$Date == input$date)
}, hover = TRUE, bordered = TRUE, spacing = "xs", align = "c")
output$post <- renderTable({
datefilter <- subset(Post_Data, Post_Data$Date == input$date)
}, hover = TRUE, bordered = TRUE, spacing = "xs", align = "c")
}
shinyApp(ui = ui, server = server)

R DT::datatables formatting multiple columns simultaneously

I wish to implement formatCurrency() and formatPercentage() (both from DT package) across multiple columns simultaneously in a shiny dashboard. I am using shinymaterial for the given example.
I am currently doing the following:
# The packages to load.
required_packages <- c("shiny", "shinymaterial", "DT", "tidyverse")
# This function will load in all the packages needed.
lapply(required_packages, require, character.only = TRUE)
# A table example.
ui <- material_page(
title = "Example table",
tags$h1("Table example"),
material_card(
title = "Table",
material_row(
DT::dataTableOutput("data_table_example")
),
depth = 1
)
)
server <- function(input, output) {
data_table_example_data = tibble(
Person = paste0("Person ", c(1:100)),
`Price $` = rnorm(100, 50000, 500),
`Cost $` = rnorm(100, 30000, 300),
`Probability %` = rnorm(100, 0.6, 0.1),
`Win %` = rnorm(100, 0.5, 0.2)
)
# This will create an output summary table
output$data_table_example = renderDataTable({
result = datatable(data_table_example_data, options = list(pageLength = 100, scrollX = TRUE),
class = 'cell-border stripe compact', rownames = FALSE) %>%
formatCurrency("Price $") %>%
formatCurrency("Cost $") %>%
formatPercentage("Probability %", digits = 1) %>%
formatPercentage("Win %", digits = 1)
})
}
shinyApp(ui = ui, server = server)
However, what I wish to do is, within the renderDataTable() function, to simplify the format functions into fewer lines. For example, implement formatCurrency() in any column with a "$" and formatPercentage() in any column with a "%".
I have done a fair bit of searching for an appropriate but could not find a solution, but I assume I am just missing a fairly simple solution.
Something like:
# This will create an output summary table
output$data_table_example = renderDataTable({
result = datatable(data_table_example_data, options = list(pageLength = 100, scrollX = TRUE),
class = 'cell-border stripe compact', rownames = FALSE) %>%
formatCurrency(grepl("$", colnames()) %>%
formatPercentage(grepl("%", colnames()), digits = 1)
})
A few additional points:
The tibble will actually be a reactive
This example is a very trivial version of a rather more complex table and set of reactives
I do not want to implement the formatting in the reactive part since I find this then messes with the DT sorting function, since it assumes the column is a character string
Any help will be greatly appreciated
Try:
# This will create an output summary table
output$data_table_example = renderDataTable({
result = datatable(data_table_example_data, options = list(pageLength = 100, scrollX = TRUE),
class = 'cell-border stripe compact', rownames = FALSE) %>%
formatCurrency(grepl("$", colnames(data_table_example_data)) %>%
formatPercentage(grepl("%", colnames(data_table_example_data)), digits = 1)
})
It seems you need to be explicit with the data so colnames() doesn't work - you need colnames(data_table_example_data).
I noticed during testing if you use grepl with rownames = TRUE that rownames becomes the first column name which means all the formatting is out by one. grep seems to not have this issue.

Shiny - object from reactive expression not found when used in loglm

I created a shiny app, in which I want to display the residual of a log-linear model using a mosaic plot. I need to use the data from a reactive expression and pass it to loglm. It seem pretty strait forward, but when I do that I get the following error : "objet 'mod' introuvable".
I've already figured which line is causing the problem, but I don't know how to fix it. Running the code below as is should work fine.
However, uncomment the line # mod <- loglm( formula = reformulate(f), data = mod ), in server and you should get the same error I get.
Any help would be greatly appreciated.
ui <- fluidPage(
titlePanel("Shiny Viz!"),
fluidRow( class= "R1",
tabsetPanel(type= "pills",
tabPanel("Log-linear model",
fluidRow(
column(3, offset=1,
selectInput("model", label= "Choose model to fit:",
choices= c("(SPT)","(SP,ST,PT)","(ST,PT)","(SP,PT)","(SP,ST)")),
selectInput("type", label= "Visualise the expected or observed values?",
choices = c("observed", "expected")),
sliderInput("n_breaks", label = "Degree Celcius per bin:",
min = .5, max = 5, value = 1, step = .5)),
column(8, plotOutput("loglinear.mosaic", height= "600px") )
))))
)
library(ggplot2)
library(data.table)
library(vcd)
library(vcdExtra)
server <- function(input, output) {
# Create data
DF <- data.table( Temp = runif(5000, 0, 30),
Presence = factor(rbinom(5000, 1, runif(20, 0.1, 0.60))),
Period = factor(as.integer(runif(5000, 1, 9))) )
# Reactive expression
loglinear <- reactive({
DF[ , Temperature.category := cut_interval(Temp, length= input$n_breaks)]
Tab <- xtabs(formula= ~ Period + Temperature.category + Presence,
data = DF)
return(Tab)
})
# mosaic plot
output$loglinear.mosaic <- renderPlot({
mod <- loglinear()
f <- switch(input$model,
"(SPT)"= c("Presence*Period*Temperature.category"),
"(SP,ST,PT)" = c("Presence*Period","Presence*Temperature.category","Period*Temperature.category"),
"(ST,PT)" = c("Presence*Temperature.category","Period*Temperature.category"),
"(SP,PT)" = c("Presence*Period","Period*Temperature.category"),
"(SP,ST)" = c("Presence*Period","Presence*Temperature.category"))
# mod <- loglm( formula = reformulate(f), data = mod )
mosaic(mod,
gp= shading_hcl,
spacing = spacing_highlighting,
type= input$type,
labeling_args= list(offset_varnames = c(right = 1, left=.5),
offset_labels = c(right = .1),
set_varnames = c(Temperature.category="Temperature", Period="Period",
Presence="Status")),
set_labels=list(Presence = c("Ab","Pr")),
margins = c(right = 5, left = 3, bottom = 1, top =3))
})
}
shinyApp(ui = ui, server = server)
I still haven't found what is causing the problem with loglm, but I've figured another way of getting the result I wanted.
I used glm to fit the model instead of loglm, then used mosaic.glm from the vcdExtra package to create the mosaic plot. The code is pretty much the same except that the data as to be a data.frame and the column 'Temperature.category', 'Period' and 'Presence' must be factor to be used with glm.
However, I am still clueless as to why loglm can't find the object 'mod', but glm can? I'd realy want to know the reason. Since my answers doesn't answer that question, I'll accept an other answer if someone has an explanation.
Here's what the code using glm:
ui <- fluidPage(
titlePanel("Shiny Viz!"),
fluidRow( class= "R1",
tabsetPanel(type= "pills",
tabPanel("Log-linear model",
fluidRow(
column(3, offset=1,
selectInput("model", label= "Choose model to fit:",
choices= c("(SPT)","(SP,ST,PT)","(ST,PT)","(SP,PT)","(SP,ST)")),
selectInput("type", label= "Visualise the expected or observed values?",
choices = c("observed", "expected")),
sliderInput("n_breaks", label = "Degree Celcius per bin:",
min = .5, max = 5, value = 1, step = .5)),
column(8, plotOutput("loglinear.mosaic", height= "800px") )
))))
)
library(ggplot2)
library(data.table)
library(vcd)
library(vcdExtra)
server <- function(input, output) {
DF <- data.table( Temp = runif(5000, 0, 30),
Presence = factor(rbinom(5000, 1, runif(20, 0.1, 0.60))),
Period = factor(as.integer(runif(5000, 1, 9)) ) )
# data to data.frame format
loglinear <- reactive({
DF[ , Temperature.category := cut_interval(Temp, length= input$n_breaks)]
# add 'Freq' column
dat <- data.frame(as.table(xtabs(formula= ~ Period + Temperature.category + Presence,
data = DF)), stringsAsFactors = T)
return(dat)
})
# mosaic plot
output$loglinear.mosaic <- renderPlot({
mod <- loglinear()
f <- switch(input$model,
"(SPT)"= c("Presence*Period*Temperature.category"),
"(SP,ST,PT)" = c("Presence*Period","Presence*Temperature.category","Period*Temperature.category"),
"(ST,PT)" = c("Presence*Temperature.category","Period*Temperature.category"),
"(SP,PT)" = c("Presence*Period","Period*Temperature.category"),
"(SP,ST)" = c("Presence*Period","Presence*Temperature.category"))
# fit model using glm
mod.glm <- glm(formula = reformulate(f, response = "Freq"), data= mod, family= poisson)
mosaic.glm(mod.glm,
formula = ~ Temperature.category + Period + Presence,
gp= shading_hcl,
spacing = spacing_highlighting,
type= input$type,
labeling_args= list(rot_labels = c(left = 0, right = 0),
offset_varnames = c(left=1.5, right = 1),
offset_labels = c(left=.5, right = .1),
set_varnames = c(Temperature.category="Temperature", Period="Period",
Presence="Status")),
set_labels=list(Presence = c("Ab","Pr")),
margins = c(right = 5, left = 4, bottom = 1, top =3))
})
}

Resources