1 2#' Create a page with fluid layout 3#' 4#' Functions for creating fluid page layouts. A fluid page layout consists of 5#' rows which in turn include columns. Rows exist for the purpose of making sure 6#' their elements appear on the same line (if the browser has adequate width). 7#' Columns exist for the purpose of defining how much horizontal space within a 8#' 12-unit wide grid it's elements should occupy. Fluid pages scale their 9#' components in realtime to fill all available browser width. 10#' 11#' @param ... Elements to include within the page 12#' @param title The browser window title (defaults to the host URL of the page). 13#' Can also be set as a side effect of the [titlePanel()] function. 14#' @inheritParams bootstrapPage 15#' 16#' @return A UI defintion that can be passed to the [shinyUI] function. 17#' 18#' @details To create a fluid page use the `fluidPage` function and include 19#' instances of `fluidRow` and [column()] within it. As an 20#' alternative to low-level row and column functions you can also use 21#' higher-level layout functions like [sidebarLayout()]. 22#' 23#' @note See the [ 24#' Shiny-Application-Layout-Guide](https://shiny.rstudio.com/articles/layout-guide.html) for additional details on laying out fluid 25#' pages. 26#' 27#' @family layout functions 28#' @seealso [column()] 29#' 30#' @examples 31#' ## Only run examples in interactive R sessions 32#' if (interactive()) { 33#' 34#' # Example of UI with fluidPage 35#' ui <- fluidPage( 36#' 37#' # Application title 38#' titlePanel("Hello Shiny!"), 39#' 40#' sidebarLayout( 41#' 42#' # Sidebar with a slider input 43#' sidebarPanel( 44#' sliderInput("obs", 45#' "Number of observations:", 46#' min = 0, 47#' max = 1000, 48#' value = 500) 49#' ), 50#' 51#' # Show a plot of the generated distribution 52#' mainPanel( 53#' plotOutput("distPlot") 54#' ) 55#' ) 56#' ) 57#' 58#' # Server logic 59#' server <- function(input, output) { 60#' output$distPlot <- renderPlot({ 61#' hist(rnorm(input$obs)) 62#' }) 63#' } 64#' 65#' # Complete app with UI and server components 66#' shinyApp(ui, server) 67#' 68#' 69#' # UI demonstrating column layouts 70#' ui <- fluidPage( 71#' title = "Hello Shiny!", 72#' fluidRow( 73#' column(width = 4, 74#' "4" 75#' ), 76#' column(width = 3, offset = 2, 77#' "3 offset 2" 78#' ) 79#' ) 80#' ) 81#' 82#' shinyApp(ui, server = function(input, output) { }) 83#' } 84#' @rdname fluidPage 85#' @export 86fluidPage <- function(..., title = NULL, theme = NULL, lang = NULL) { 87 bootstrapPage(div(class = "container-fluid", ...), 88 title = title, 89 theme = theme, 90 lang = lang) 91} 92 93 94#' @rdname fluidPage 95#' @export 96fluidRow <- function(...) { 97 div(class = "row", ...) 98} 99 100#' Create a page with a fixed layout 101#' 102#' Functions for creating fixed page layouts. A fixed page layout consists of 103#' rows which in turn include columns. Rows exist for the purpose of making sure 104#' their elements appear on the same line (if the browser has adequate width). 105#' Columns exist for the purpose of defining how much horizontal space within a 106#' 12-unit wide grid it's elements should occupy. Fixed pages limit their width 107#' to 940 pixels on a typical display, and 724px or 1170px on smaller and larger 108#' displays respectively. 109#' 110#' @param ... Elements to include within the container 111#' @param title The browser window title (defaults to the host URL of the page) 112#' @inheritParams bootstrapPage 113#' 114#' @return A UI defintion that can be passed to the [shinyUI] function. 115#' 116#' @details To create a fixed page use the `fixedPage` function and include 117#' instances of `fixedRow` and [column()] within it. Note that 118#' unlike [fluidPage()], fixed pages cannot make use of higher-level 119#' layout functions like `sidebarLayout`, rather, all layout must be done 120#' with `fixedRow` and `column`. 121#' 122#' @note See the [ 123#' Shiny Application Layout Guide](https://shiny.rstudio.com/articles/layout-guide.html) for additional details on laying out fixed 124#' pages. 125#' 126#' @family layout functions 127#' 128#' @seealso [column()] 129#' 130#' @examples 131#' ## Only run examples in interactive R sessions 132#' if (interactive()) { 133#' 134#' ui <- fixedPage( 135#' title = "Hello, Shiny!", 136#' fixedRow( 137#' column(width = 4, 138#' "4" 139#' ), 140#' column(width = 3, offset = 2, 141#' "3 offset 2" 142#' ) 143#' ) 144#' ) 145#' 146#' shinyApp(ui, server = function(input, output) { }) 147#' } 148#' 149#' @rdname fixedPage 150#' @export 151fixedPage <- function(..., title = NULL, theme = NULL, lang = NULL) { 152 bootstrapPage(div(class = "container", ...), 153 title = title, 154 theme = theme, 155 lang = lang) 156} 157 158#' @rdname fixedPage 159#' @export 160fixedRow <- function(...) { 161 div(class = "row", ...) 162} 163 164 165#' Create a column within a UI definition 166#' 167#' Create a column for use within a [fluidRow()] or 168#' [fixedRow()] 169#' 170#' @param width The grid width of the column (must be between 1 and 12) 171#' @param ... Elements to include within the column 172#' @param offset The number of columns to offset this column from the end of the 173#' previous column. 174#' 175#' @return A column that can be included within a 176#' [fluidRow()] or [fixedRow()]. 177#' 178#' 179#' @seealso [fluidRow()], [fixedRow()]. 180#' 181#' @examples 182#' ## Only run examples in interactive R sessions 183#' if (interactive()) { 184#' 185#' ui <- fluidPage( 186#' fluidRow( 187#' column(4, 188#' sliderInput("obs", "Number of observations:", 189#' min = 1, max = 1000, value = 500) 190#' ), 191#' column(8, 192#' plotOutput("distPlot") 193#' ) 194#' ) 195#' ) 196#' 197#' server <- function(input, output) { 198#' output$distPlot <- renderPlot({ 199#' hist(rnorm(input$obs)) 200#' }) 201#' } 202#' 203#' shinyApp(ui, server) 204#' 205#' 206#' 207#' ui <- fluidPage( 208#' fluidRow( 209#' column(width = 4, 210#' "4" 211#' ), 212#' column(width = 3, offset = 2, 213#' "3 offset 2" 214#' ) 215#' ) 216#' ) 217#' shinyApp(ui, server = function(input, output) { }) 218#' } 219#' @export 220column <- function(width, ..., offset = 0) { 221 222 if (!is.numeric(width) || (width < 1) || (width > 12)) 223 stop("column width must be between 1 and 12") 224 225 colClass <- paste0("col-sm-", width) 226 if (offset > 0) { 227 # offset-md-x is for bootstrap 4 forward compat 228 # (every size tier has been bumped up one level) 229 # https://github.com/twbs/bootstrap/blob/74b8fe7/docs/4.3/migration/index.html#L659 230 colClass <- paste0(colClass, " offset-md-", offset, " col-sm-offset-", offset) 231 } 232 div(class = colClass, ...) 233} 234 235 236#' Create a panel containing an application title. 237#' 238#' @param title An application title to display 239#' @param windowTitle The title that should be displayed by the browser window. 240#' 241#' @details Calling this function has the side effect of including a 242#' `title` tag within the head. You can also specify a page title 243#' explicitly using the `title` parameter of the top-level page function. 244#' 245#' @examples 246#' ## Only run examples in interactive R sessions 247#' if (interactive()) { 248#' 249#' ui <- fluidPage( 250#' titlePanel("Hello Shiny!") 251#' ) 252#' shinyApp(ui, server = function(input, output) { }) 253#' } 254#' @export 255titlePanel <- function(title, windowTitle=title) { 256 tagList( 257 tags$head(tags$title(windowTitle)), 258 h2(title) 259 ) 260} 261 262#' Layout a sidebar and main area 263#' 264#' Create a layout (`sidebarLayout()`) with a sidebar (`sidebarPanel()`) and 265#' main area (`mainPanel()`). The sidebar is displayed with a distinct 266#' background color and typically contains input controls. The main 267#' area occupies 2/3 of the horizontal width and typically contains outputs. 268#' 269#' @param sidebarPanel The `sidebarPanel()` containing input controls. 270#' @param mainPanel The `mainPanel()` containing outputs. 271#' @param position The position of the sidebar relative to the main area ("left" 272#' or "right"). 273#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed 274#' layout. 275#' @param width The width of the sidebar and main panel. By default, the 276#' sidebar takes up 1/3 of the width, and the main panel 2/3. The total 277#' width must be 12 or less. 278#' @param ... Output elements to include in the sidebar/main panel. 279#' 280#' @family layout functions 281#' 282#' @examples 283#' ## Only run examples in interactive R sessions 284#' if (interactive()) { 285#' options(device.ask.default = FALSE) 286#' 287#' # Define UI 288#' ui <- fluidPage( 289#' 290#' # Application title 291#' titlePanel("Hello Shiny!"), 292#' 293#' sidebarLayout( 294#' 295#' # Sidebar with a slider input 296#' sidebarPanel( 297#' sliderInput("obs", 298#' "Number of observations:", 299#' min = 0, 300#' max = 1000, 301#' value = 500) 302#' ), 303#' 304#' # Show a plot of the generated distribution 305#' mainPanel( 306#' plotOutput("distPlot") 307#' ) 308#' ) 309#' ) 310#' 311#' # Server logic 312#' server <- function(input, output) { 313#' output$distPlot <- renderPlot({ 314#' hist(rnorm(input$obs)) 315#' }) 316#' } 317#' 318#' # Complete app with UI and server components 319#' shinyApp(ui, server) 320#' } 321#' @export 322sidebarLayout <- function(sidebarPanel, 323 mainPanel, 324 position = c("left", "right"), 325 fluid = TRUE) { 326 327 # determine the order 328 position <- match.arg(position) 329 if (position == "left") { 330 firstPanel <- sidebarPanel 331 secondPanel <- mainPanel 332 } 333 else if (position == "right") { 334 firstPanel <- mainPanel 335 secondPanel <- sidebarPanel 336 } 337 338 # return as as row 339 if (fluid) 340 fluidRow(firstPanel, secondPanel) 341 else 342 fixedRow(firstPanel, secondPanel) 343} 344 345#' @export 346#' @rdname sidebarLayout 347sidebarPanel <- function(..., width = 4) { 348 div(class=paste0("col-sm-", width), 349 tags$form(class="well", 350 # A11y semantic landmark for sidebar 351 role="complementary", 352 ... 353 ) 354 ) 355} 356 357#' @export 358#' @rdname sidebarLayout 359mainPanel <- function(..., width = 8) { 360 div(class=paste0("col-sm-", width), 361 # A11y semantic landmark for main region 362 role="main", 363 ... 364 ) 365} 366 367#' Lay out UI elements vertically 368#' 369#' Create a container that includes one or more rows of content (each element 370#' passed to the container will appear on it's own line in the UI) 371#' 372#' @param ... Elements to include within the container 373#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed 374#' layout. 375#' 376#' @family layout functions 377#' 378#' @examples 379#' ## Only run examples in interactive R sessions 380#' if (interactive()) { 381#' 382#' ui <- fluidPage( 383#' verticalLayout( 384#' a(href="http://example.com/link1", "Link One"), 385#' a(href="http://example.com/link2", "Link Two"), 386#' a(href="http://example.com/link3", "Link Three") 387#' ) 388#' ) 389#' shinyApp(ui, server = function(input, output) { }) 390#' } 391#' @export 392verticalLayout <- function(..., fluid = TRUE) { 393 lapply(list2(...), function(row) { 394 col <- column(12, row) 395 if (fluid) 396 fluidRow(col) 397 else 398 fixedRow(col) 399 }) 400} 401 402#' Flow layout 403#' 404#' Lays out elements in a left-to-right, top-to-bottom arrangement. The elements 405#' on a given row will be top-aligned with each other. This layout will not work 406#' well with elements that have a percentage-based width (e.g. 407#' [plotOutput()] at its default setting of `width = "100%"`). 408#' 409#' @param ... Unnamed arguments will become child elements of the layout. Named 410#' arguments will become HTML attributes on the outermost tag. 411#' @param cellArgs Any additional attributes that should be used for each cell 412#' of the layout. 413#' 414#' @family layout functions 415#' 416#' @examples 417#' ## Only run examples in interactive R sessions 418#' if (interactive()) { 419#' 420#' ui <- flowLayout( 421#' numericInput("rows", "How many rows?", 5), 422#' selectInput("letter", "Which letter?", LETTERS), 423#' sliderInput("value", "What value?", 0, 100, 50) 424#' ) 425#' shinyApp(ui, server = function(input, output) { }) 426#' } 427#' @export 428flowLayout <- function(..., cellArgs = list()) { 429 430 children <- list2(...) 431 childIdx <- !nzchar(names(children) %||% character(length(children))) 432 attribs <- children[!childIdx] 433 children <- children[childIdx] 434 435 do.call(tags$div, c(list(class = "shiny-flow-layout"), 436 attribs, 437 lapply(children, function(x) { 438 do.call(tags$div, c(cellArgs, list(x))) 439 }) 440 )) 441} 442 443#' Input panel 444#' 445#' A [flowLayout()] with a grey border and light grey background, 446#' suitable for wrapping inputs. 447#' 448#' @param ... Input controls or other HTML elements. 449#' @export 450inputPanel <- function(...) { 451 div(class = "shiny-input-panel", 452 flowLayout(...) 453 ) 454} 455 456#' Split layout 457#' 458#' Lays out elements horizontally, dividing the available horizontal space into 459#' equal parts (by default). 460#' 461#' @param ... Unnamed arguments will become child elements of the layout. Named 462#' arguments will become HTML attributes on the outermost tag. 463#' @param cellWidths Character or numeric vector indicating the widths of the 464#' individual cells. Recycling will be used if needed. Character values will 465#' be interpreted as CSS lengths (see [validateCssUnit()]), numeric 466#' values as pixels. 467#' @param cellArgs Any additional attributes that should be used for each cell 468#' of the layout. 469#' 470#' @family layout functions 471#' 472#' @examples 473#' ## Only run examples in interactive R sessions 474#' if (interactive()) { 475#' options(device.ask.default = FALSE) 476#' 477#' # Server code used for all examples 478#' server <- function(input, output) { 479#' output$plot1 <- renderPlot(plot(cars)) 480#' output$plot2 <- renderPlot(plot(pressure)) 481#' output$plot3 <- renderPlot(plot(AirPassengers)) 482#' } 483#' 484#' # Equal sizing 485#' ui <- splitLayout( 486#' plotOutput("plot1"), 487#' plotOutput("plot2") 488#' ) 489#' shinyApp(ui, server) 490#' 491#' # Custom widths 492#' ui <- splitLayout(cellWidths = c("25%", "75%"), 493#' plotOutput("plot1"), 494#' plotOutput("plot2") 495#' ) 496#' shinyApp(ui, server) 497#' 498#' # All cells at 300 pixels wide, with cell padding 499#' # and a border around everything 500#' ui <- splitLayout( 501#' style = "border: 1px solid silver;", 502#' cellWidths = 300, 503#' cellArgs = list(style = "padding: 6px"), 504#' plotOutput("plot1"), 505#' plotOutput("plot2"), 506#' plotOutput("plot3") 507#' ) 508#' shinyApp(ui, server) 509#' } 510#' @export 511splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) { 512 513 children <- list2(...) 514 childIdx <- !nzchar(names(children) %||% character(length(children))) 515 attribs <- children[!childIdx] 516 children <- children[childIdx] 517 count <- length(children) 518 519 if (length(cellWidths) == 0 || is.na(cellWidths)) { 520 cellWidths <- sprintf("%.3f%%", 100 / count) 521 } 522 cellWidths <- rep(cellWidths, length.out = count) 523 cellWidths <- sapply(cellWidths, validateCssUnit) 524 525 do.call(tags$div, c(list(class = "shiny-split-layout"), 526 attribs, 527 mapply(children, cellWidths, FUN = function(x, w) { 528 do.call(tags$div, c( 529 list(style = sprintf("width: %s;", w)), 530 cellArgs, 531 list(x) 532 )) 533 }, SIMPLIFY = FALSE) 534 )) 535} 536 537#' Flex Box-based row/column layouts 538#' 539#' Creates row and column layouts with proportionally-sized cells, using the 540#' Flex Box layout model of CSS3. These can be nested to create arbitrary 541#' proportional-grid layouts. **Warning:** Flex Box is not well supported 542#' by Internet Explorer, so these functions should only be used where modern 543#' browsers can be assumed. 544#' 545#' @details If you try to use `fillRow` and `fillCol` inside of other 546#' Shiny containers, such as [sidebarLayout()], 547#' [navbarPage()], or even `tags$div`, you will probably find 548#' that they will not appear. This is due to `fillRow` and `fillCol` 549#' defaulting to `height="100%"`, which will only work inside of 550#' containers that have determined their own size (rather than shrinking to 551#' the size of their contents, as is usually the case in HTML). 552#' 553#' To avoid this problem, you have two options: 554#' \itemize{ 555#' \item only use `fillRow`/`fillCol` inside of `fillPage`, 556#' `fillRow`, or `fillCol` 557#' \item provide an explicit `height` argument to 558#' `fillRow`/`fillCol` 559#' } 560#' 561#' @param ... UI objects to put in each row/column cell; each argument will 562#' occupy a single cell. (To put multiple items in a single cell, you can use 563#' [tagList()] or [div()] to combine them.) Named 564#' arguments will be used as attributes on the `div` element that 565#' encapsulates the row/column. 566#' @param flex Determines how space should be distributed to the cells. Can be a 567#' single value like `1` or `2` to evenly distribute the available 568#' space; or use a vector of numbers to specify the proportions. For example, 569#' `flex = c(2, 3)` would cause the space to be split 40%/60% between 570#' two cells. NA values will cause the corresponding cell to be sized 571#' according to its contents (without growing or shrinking). 572#' @param width,height The total amount of width and height to use for the 573#' entire row/column. For the default height of `"100%"` to be 574#' effective, the parent must be `fillPage`, another 575#' `fillRow`/`fillCol`, or some other HTML element whose height is 576#' not determined by the height of its contents. 577#' 578#' @examples 579#' # Only run this example in interactive R sessions. 580#' if (interactive()) { 581#' 582#' ui <- fillPage(fillRow( 583#' plotOutput("plotLeft", height = "100%"), 584#' fillCol( 585#' plotOutput("plotTopRight", height = "100%"), 586#' plotOutput("plotBottomRight", height = "100%") 587#' ) 588#' )) 589#' 590#' server <- function(input, output, session) { 591#' output$plotLeft <- renderPlot(plot(cars)) 592#' output$plotTopRight <- renderPlot(plot(pressure)) 593#' output$plotBottomRight <- renderPlot(plot(AirPassengers)) 594#' } 595#' 596#' shinyApp(ui, server) 597#' 598#' } 599#' @export 600fillRow <- function(..., flex = 1, width = "100%", height = "100%") { 601 flexfill(..., direction = "row", flex = flex, width = width, height = height) 602} 603 604#' @rdname fillRow 605#' @export 606fillCol <- function(..., flex = 1, width = "100%", height = "100%") { 607 flexfill(..., direction = "column", flex = flex, width = width, height = height) 608} 609 610flexfill <- function(..., direction, flex, width = width, height = height) { 611 children <- list2(...) 612 attrs <- list() 613 614 if (!is.null(names(children))) { 615 attrs <- children[names(children) != ""] 616 children <- children[names(children) == ""] 617 } 618 619 if (length(flex) > length(children)) { 620 flex <- flex[seq_along(children)] 621 } 622 623 # The dimension along the main axis 624 main <- switch(direction, 625 row = "width", 626 "row-reverse" = "width", 627 column = "height", 628 "column-reverse" = "height", 629 stop("Unexpected direction") 630 ) 631 # The dimension along the cross axis 632 cross <- if (main == "width") "height" else "width" 633 634 divArgs <- list( 635 class = sprintf("flexfill-container flexfill-container-%s", direction), 636 style = css( 637 display = "-webkit-flex", 638 display = "-ms-flexbox", 639 display = "flex", 640 .webkit.flex.direction = direction, 641 .ms.flex.direction = direction, 642 flex.direction = direction, 643 width = validateCssUnit(width), 644 height = validateCssUnit(height) 645 ), 646 mapply(children, flex, FUN = function(el, flexValue) { 647 if (is.na(flexValue)) { 648 # If the flex value is NA, then put the element in a simple flex item 649 # that sizes itself (along the main axis) to its contents 650 tags$div( 651 class = "flexfill-item", 652 style = css( 653 position = "relative", 654 "-webkit-flex" = "none", 655 "-ms-flex" = "none", 656 flex = "none" 657 ), 658 style = paste0(main, ":auto;", cross, ":100%;"), 659 el 660 ) 661 } else if (is.numeric(flexValue)) { 662 # If the flex value is numeric, we need *two* wrapper divs. The outer is 663 # the flex item, and the inner is an absolute-fill div that is needed to 664 # make percentage-based sizing for el work correctly. I don't understand 665 # why this is needed but the truth is probably in this SO page: 666 # http://stackoverflow.com/questions/15381172/css-flexbox-child-height-100 667 tags$div( 668 class = "flexfill-item", 669 style = css( 670 position = "relative", 671 "-webkit-flex" = flexValue, 672 "-ms-flex" = flexValue, 673 flex = flexValue, 674 width = "100%", height = "100%" 675 ), 676 tags$div( 677 class = "flexfill-item-inner", 678 style = css( 679 position = "absolute", 680 top = 0, left = 0, right = 0, bottom = 0 681 ), 682 el 683 ) 684 ) 685 } else { 686 stop("Unexpected flex argument: ", flexValue) 687 } 688 }, SIMPLIFY = FALSE, USE.NAMES = FALSE) 689 ) 690 do.call(tags$div, c(attrs, divArgs)) 691} 692