5 Layouts
Até então, todos os exemplos que fizemos não possuem qualquer preocupação com o layout. Apenas empilhamos elementos na UI, tanto no código quanto na tela.
Neste capítulo, vamos discutir como customizar o layout de um aplicativo Shiny. Para isso, precisamos falar um pouco de HTML e do framework Boostrap, utilizado por padrão pelo Shiny para lidar com questões importantes no desenvolvimento Web. Além de sairmos capazes de construir nossos próprios layouts, também aprenderemos aqui como utilizar alguns layouts prontos do pacote shiny
e, pela primeira vez, exploraremos recursos em outros pacotes da comunidade Shiny.
5.1 Um pouco sobre HTML
HTML é uma linguagem de marcação para construir páginas web.
Uma linguagem de marcação é apenas um tipo de documento que contem texto simples (como em um bloco de notas) e um conjunto de instruções para formatar (anotar, marcar) partes específicas do conteúdo. Esse texto simples é então transformado em um texto bem formatado por algum mecanismo que renderiza as instruções. Além do HTML, o LaTeX e o (R) Markdown são outros exemplos comuns de linguagem de marcação bastante utilizadas. No caso do HTML, os navegadores são os responsáveis por renderizar documentos HTML em páginas bem formatadas.
A maior parte do esforço em aprender uma linguagem de marcação está em aprender quais são e como utilizar as instruções de formatação. No HTML, as instrução de formatação são chamadas tags. Utilizaremos as tags para formatar o texto da página web que estamos criando. Com elas, podemos, por exemplo, transformar um texto em negrito ou itálico, criar títulos e inserir imagens.
O pacote shiny
traz diversas funções para criarmos essas tags. As principais são:
Podemos utilizar essas funções à vontade na UI para construirmos o layout do nosso app. O código abaixo, por exemplo, gera o código HTML a seguir.
#ui
fluidPage(
h1("Esse é o título do meu app!", align = "center"),
hr(),
h3("Sobre"),
p("Lorem ipsum", tags$em("dolor sit amet", .noWS = "after"), ", consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
p(strong("Lorem ipsum dolor"), "sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
hr(),
img(src = "img/logo.png", width = "50%", style = "display: block; margin: auto;")
)
<div class="container-fluid">
<h1 align="center">Esse é o título do meu app!</h1>
<hr/>
<h3>Sobre</h3>
<p>
Lorem ipsum<em>dolor sit amet</em>, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
<strong>Lorem ipsum dolor</strong>
sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<hr/>
<img src="img/logo.png" width="50%" style="display: block; margin: auto;"/>
</div>
Que, por sua vez, gera a seguinte UI:
Warning: package 'shiny' was built under R version 4.3.3
Esse é o título do meu app!
Sobre
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Repare que algumas tags, como a h1
e a img
possuem parâmetros (ou atributos, como são chamados no HTML). O parâmetro align
na tag h1
faz com que o texto fique alinhado no centro da página. Esse parâmetro é típico das tags de título (h1
, …, h6
). Outras tags de texto não possuem necessariamente esse argumento.
Já o argumento src
da tag img
é utilizado para definirmos o caminho para a imagem que queremos mostrar. O argumento width
especifica o comprimento da imagem com relação ao espaço disponível para ela. No exemplo, o logo da Curso-R ocupa 50% do comprimento da página deste livro. O argumento style
nos permite formatar ainda mais a imagem a partir de atributos CSS, centralizando a imagem horizontalmente na tela nesse caso.
Conforme aprendemos e utilizamos o Shiny, inevitavelmente aprendemos bastante sobre HTML. Isso aumenta bastante a nossa capacidade de personalizar a UI dos nossos aplicativos e nos ajuda a entender como o Shiny funciona. Por enquanto, vamos manter o foco em explorar as principais ferramentas do Shiny, mas no Capítulo 12 faremos uma breve introdução formal tanto de HTML quanto de CSS.
5.2 Bootstrap
Hoje em dia, uma página Web pode ser vista em dispositivos de diferentes tamanhos (celulares, tablets, notebooks, televisões…) e o layout da página deve se adaptar à enorme variedade de tamanho de telas. Isso é um grande desafio para quem desenvolve.
Uma solução seria produzir uma versão para telas pequenas e uma versão para telas grandes, direcionando as visitas para a versão adequada a depender do dispositivo utilizado. Muitos sites utilizam essa alternativa, e você pode verificar isso pela URL. Páginas próprias para dispositivos mobile possuem um m.
no início da URL.
Nem sempre essa alternativa é viável, pois produzir duas versões de uma página ou aplicação Web pode ser muito custoso. Nesses casos, a solução é produzir um layout responsivo, isto é, que se adapte a depender do tamanho da tela. É aí que entra o Bootstrap.
O Bootstrap Framework é uma coleção de códigos CSS que nos ajudam a construir páginas Web responsivas. Boa parte da internet hoje em dia é construída em cima do Bootstrap, e nossos aplicativos Shiny não serão diferentes.
O Shiny importa o Bootstrap por padrão, isto é, todos os códigos CSS desse framework já estão disponíveis em nossos apps sem precisarmos especificar nada. E a melhor parte é que não precisamos saber CSS para utilizar o Boostrap no Shiny. Só precisamos aprender algumas funções de R.
5.2.1 Grid system
Antes de vermos essas funções, precisamos entender como funciona o grid system. O Bootstrap estabelece que:
os elementos em uma página serão dispostos primeiramente em linhas;
cada nova linha será colocada embaixo da anterior;
cada linha pode ser dividida em até 12 colunas; independentemente do tamanho da tela;
cada coluna pode ter até 12 unidades de comprimento, sendo que a soma dos comprimentos das colunas de uma linha deve ser no máximo 12;
quando a tela for pequena o suficiente1 todas as colunas passarão a ter comprimento 12.
Em resumo, o conceito por trás do Boostrap estabelece que o layout dos nossos apps serão formados por linhas com até 12 colunas cada. O comprimento de cada coluna pode variar de 1 a 12 unidades e a soma dos comprimentos dessas colunas pode ser no máximo 12. Se o comprimento da tela for menor que um valor limite, todas as colunas automaticamente passam a ter tamanho 12 e os elementos da página passam a ficar um embaixo do outro.
No Shiny, para criar novas linhas, utilizamos a função fluidRow()
. Para criar colunas dentro de uma linha, utilizamos a função column()
. Essa função tem dois argumentos: width
e offset
. O primeiro determina o comprimento da coluna (de 1 a 12). O segundo indica quanto espaço horizontal gostaríamos de “pular” antes de começar a nossa coluna. A função column()
é sempre utilizada dentro da função fluidRow()
.
Seguindo esse esquema, passamos a colocar o conteúdo da página dentro das colunas, isto é, dentro da função column()
.
Utilizando essas funções, podemos mudar o exemplo da seção anterior e construir o app a seguir:
library(shiny)
fluidPage(
fluidRow(
column(
width = 8,
offset = 2,
h1("Esse é o título do meu app!", align = "center")
),column(
width = 2,
img(src = "img/logo.png", width = "100%")
)
),hr(),
h3("Sobre"),
fluidRow(
column(
width = 6,
p("Lorem ipsum", tags$em("dolor sit amet", .noWS = "after"), ", consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
),column(
width = 6,
p(strong("Lorem ipsum dolor"), "sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
)
),hr(),
fluidRow(
column(
width = 6,
offset = 3,
img(src = "img/logo.png", width = "100%")
)
)
)
<- function(input, output, session) {
server
}
shinyApp(ui, server)
Repare que agora a imagem no fim da página foi centralizada utilizando as funções fluidRow()
e column()
. Não foi mais necessário definir CSS diretamente para realizar essa tarefa. Como exercício, rode esse app e veja o que acontece com a página conforme você diminui o comprimento da tela.
5.2.2 Fluid grid vs Fixed grid
Agora que sabemos um pouco sobre Boostrap, podemos falar por que construímos a nossa UI dentro da função fluidPage()
.
Ao rodar essa função, vemos que ela devolve o seguinte código HTML.
fluidPage()
<div class="container-fluid"></div>
A classe container-fluid
aplicada ao elemento <div>
é a explicação. Classes são utilizadas no HTML para atribuir propriedades a elementos da página. Essa classe em específico contém parte do código CSS necessário para a responsividade do Bootstrap funcionar.
Em resumo, o Boostrap fala que o conteúdo da página precisa estar dentro de um elemento (<div>
) com classe col
, que por sua vez deve estar dentro de um elemento com classe row
e que, por fim, deve estar dentro de um elemento com classe container
.
Veja que é exatamente isso que acontece quando escrevemos o código abaixo:
fluidPage(
fluidRow(
column(
width = 4,
"O conteúdo do app vem aqui."
)
) )
<div class="container-fluid">
<div class="row">
<div class="col-sm-4">O conteúdo do app vem aqui.</div>
</div>
</div>
Repare que a classe col
é acompanhada de mais dois valores. O 4
representa o tamanho da coluna (foi esse o tamanho definido pela função column()
). Já o sm
representa o quão estreita precisa ser a tela do dispositivo para que a responsividade seja ativada. O termo sm
é diminutivo para small, indicando que se a tela for menor que 750px, todas as colunas terão comprimento 12. Esse é o padrão no Shiny e não pode ser alterado por meio da função column()
. Você pode saber mais sobre a classe col
lendo a documentação do Boostrap.
A classe container
também possui um qualificador: fluid
. Isso porque existem dois tipos de grades Boostrap: fluid
e fixed
. Páginas construídas com a grade fluida sempre ocuparão todo o comprimento da tela, redimensionando os seus elementos dinamicamente caso o comprimento da tela mude. Já a grade fixa ocupa sempre um tamanho fixo da tela: 724px, 940px ou 1170px, a depender do quão grande for a tela. O conteúdo sempre ficará centralizado e o espaço restante ficará em branco, como uma margem aos lados.
Podemos criar um app com grade fixa utilizando a função fixedPage()
no lugar da fluidPage()
.
5.3 Layouts prontos
O pacote shiny
fornece alguns layouts prontos para serem usados. Os principais são:
sidebarLayout()
: para criar um aplicativo com uma barra lateral;navbarPage()
: para criar um aplicativo com um menu de navegação no topo da tela;navlistPanel()
: para criar um menu de navegação lateral;
Também falaremos nesta seção de layouts que não estão no pacote shiny
. São eles:
shinydashboard
: possui um layout com menu navegação lateral e diversos elementos visuais extras;bs4Dash
: cria o mesmo layout que oshinydashboard
, mas utiliza uma versão mais recente do Boostrap.
A seguir, vamos falar com mais detalhes de cada um deles.
5.3.4 shinydashboard
O shinydasboard
é um pacote que introduz diversas ferramentas para o Shiny. Antes de mais nada, instale o pacote:
install.packages("shinydasboard")
A principal é um novo layout, dividido em três áreas:
o
header
, uma barra superior onde podemos colocar títulos, botões e links;o
sidebar
, uma barra lateral onde podemos colocar um menu de navegação, logos e textos;o
body
, a área do app onde construímos o conteúdo em si (inputs e outputs).
A figura a seguir mostra o layout básico de um shinydasboard
.
Para construir esse layout, utilizamos o seguinte código:
library(shinydashboard)
<- dashboardPage(
ui dashboardHeader(),
dashboardSidebar(),
dashboardBody()
)
<- function(input, output, session) {
server
}
shinyApp(ui, server)
A função
dashboardPage()
é responsável por criar a página doshinydashboard
. Ela recebe três funções como argumentos:dashboardHeader()
,dashboardSidebar()
edashboardBody()
.A função
dashboardHeader()
é responsável por elementos da barra superior (azul).A função
dashboardSidebar()
é responsável por elementos da barra lateral (preta). Geralmente colocamos um menu para criar várias páginas no nosso dashboard, mas também é possível colocar imagens, logos e, embora seja incomum, até inputs e outputs.A função
dashboardBody()
controla os elementos da área principal do app (cinza). É nela que desenvolveremos o conteúdo do nosso app.
Para construir um menu na barra lateral, utilizamos a função sidebarMenu()
dentro da função dashboardSidebar()
. Cada item do menu é criado pela função menuItem()
. Ao argumento text
, passamos o nome que será apresentado na tela. Já ao tabName
, passamos um código que será utilizado para nos referirmos a esse item de menu dentro da função dashboardBody()
, para podermos construir o conteúdo dele.
dashboardSidebar(
sidebarMenu(
menuItem(text = "Página 1", tabName = "pagina1"),
menuItem(text = "Página 2", tabName = "pagina2")
) )
Dentro do dashboardBody()
usamos a função tabItems()
para listar os itens do menu. O conteúdo das páginas é criado utilizando a função tabItem()
. Precisamos passar para essa função o argumento tabName
, para nos referirmos a qual item do menu pertence o conteúdo de cada tabItem()
. No código abaixo, substituiríamos o ...
pelos inputs e outputs que quisermos construir em cada página do dashboard.
# ui
dashboardBody(
tabItems(
tabItem(tabName = "pagina1", ...),
tabItem(tabName = "pagina2", ...)
) )
Assim, o código da ui
ficaria:
<- dashboardPage(
ui dashboardHeader(),
dashboardSidebar(
sidebarMenu(
menuItem("Página 1", tabName = "pagina1"),
menuItem("Página 2", tabName = "pagina2")
)
),dashboardBody(
tabItems(
tabItem(tabName = "pagina1", ...),
tabItem(tabName = "pagina2", ...)
)
) )
Além do layout de dashboard, o pacote shinydashboard
trás novos elementos para a UI, como o box()
e o tabBox()
.
A função box()
cria caixinhas que permitem separarmos conteúdo do restante da página.
fluidRow(
box(
title = "Histograma",
status = "primary",
solidHeader = TRUE,
collapsible = TRUE,
# conteúdo do box
...
)
)
fluidRow(
box(
title = "Inputs",
status = "warning",
solidHeader = TRUE,
# conteúdo do box
...
) )
Se olharmos o código HTML gerado por um box()
, notamos que essas estruturas são colunas dentro do Bootstrap. Por essa razão, a função box()
deve sempre estar dentro de uma fluidRow()
, como fizemos no código acima.
box()
<div class="col-sm-6">
<div class="box">
<div class="box-body"></div>
</div>
</div>
O tabBox()
permite a criação de caixinhas com abas, possibilitando a divisão de conteúdo em várias camadas que ocupam o mesmo espaço da tela. No código abaixo, substituiríamos o ...
pelo conteúdo de cada aba.
fluidRow(
tabBox(
tabPanel("Aba 1", ...),
tabPanel("Aba 2", ...),
tabPanel("Aba 3", ...)
) )
A estrutura criada pelo tabBox()
também é uma coluna no contexto do Boostrap, então também deve estar sempre dentro de uma fluidRow()
.
tabBox()
<div class="col-sm-6">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs" data-tabsetid="4764"></ul>
<div class="tab-content" data-tabsetid="4764"></div>
</div>
</div>
O pacote shinydashboard também possui novos outputs: o valueBox()
e o infoBox()
. Ambos servem para a criação de caixinhas coloridas para destacar valores. Para utilizá-las no Shiny, usamos a combinação valueBoxOutput()
/renderValueBox()
e infoBoxOutput()
/renderInfoBox()
.
Dentro de cada função render
, utilizamos as funções valueBox()
e infoBox()
para criar as caixinhas.
Como as funções valueBox()
e infoBox()
retornam HTML, podemos utilizá-las diretamente na ui
(sem output
/render
) caso esse conteúdo não dependa de valores reativos.
Para saber mais sobre o shinydashboard
, acesso o site do pacote. Para inspiração de como usar esse layout, confira a galeria de exemplos.
5.3.5 bs4Dash
O pacote bs4Dash
é uma outra opção para construir um layout com menu de navegação lateral. Ele se diferencia do shinydashboard
por utilizar uma versão do Bootstrap mais recente: ele utiliza a versão 4, enquanto o shinydashboard
a 3.
Para instalar o pacote, utilize o código a seguir:
install.packages("bs4Dash")
A construção do layout com o pacote bs4Dash
é equivalente ao que vimos com o shinydashboard
.
library(bs4Dash)
<- bs4DashPage(
ui bs4DashNavbar(),
bs4DashSidebar(
bs4SidebarMenu(
bs4SidebarMenuItem("Página 1", tabName = "pagina1"),
bs4SidebarMenuItem("Página 2", tabName = "pagina2")
)
),bs4DashBody(
tabItems(
tabItem(tabName = "pagina1",...),
tabItem(tabName = "pagina2",...)
)
) )
A maioria das funções do bs4Dash
possuem aliases com nomes iguais aos do shinydashboard
. Isso significa que você pode construir um bs4dash
usando praticamente o mesmo código de um shinydashboard
, mudando apenas o pacote que está sendo carregado no início.
# Esse código gera a ui de um bs4Dash
library(bs4Dash)
<- dashboardPage(
ui dashboardHeader(),
dashboardSidebar(
sidebarMenu(
menuItem("Página 1", tabName = "pagina1"), #<<
menuItem("Página 2", tabName = "pagina2")
)
),dashboardBody(
tabItems(
tabItem(tabName = "pagina1",...), #<<
tabItem(tabName = "pagina2",...)
)
) )
O pacote bs4Dash
também possui diversos outros elementos para complementar a construção dos nossos apps. Você pode ver um exemplo da maioria deles e do bs4Dash
como um todo neste exemplo de demonstração.
Para mais informações sobre o bs4Dash
, acesse a vignette do pacote.
5.4 Adicionando CSS
Você pode customizar o visual do seu aplicativo utilizando CSS. Nesta seção, falaremos apenas como adicionar CSS ao Shiny. Uma introdução formal ao CSS será feita no Capítulo Capítulo 12.
A melhor maneira de adicionar CSS a elementos HTML de um aplicativo Shiny é escrever o código em um arquivo .css
e referenciá-lo na seção <head>
.
Para fazer isso, primeiro crie um arquivo de texto com extensão .css
e salve dentro da pasta /www
. A pasta /www
deve estar na mesma pasta que o .R
que gera o aplicativo e é nela que colocamos os arquivos que o navegador poderá ter acesso enquanto roda o app.
Nesse arquivo, coloque a seguinte regra CSS:
body {background-color: lightblue;
}
No seu app, supondo que o arquivo se chama custom.css
, ele deverá ser referenciado dentro da UI da seguinte forma:
<- fluidPage(
ui $head(
tags$link(rel = "stylesheet", href = "custom.css")
tags
) )
A função tags$head()
indica que o código HTML colocado dentro dela será inserido no final da seção <head>
do HTML. É sempre nessa seção que referenciamos arquivos CSS externos. A função tags$link()
serve para referenciarmos o arquivo CSS ao HTML, sendo que o argumento rel = "stylesheet"
indica que esse arquivo deverá ser encarado pelo HTML como uma folha de estilo.
No arquivo CSS, você pode colocar quantas regras CSS você precisar.
body {background-color: lightblue;
}
h1 {color: purple;
}
p {font-size: 12pt;
}
5.5 Exercícios
1 - O que é uma linguagem de marcação?
2 - Como criar as tags HTML usando o pacote shiny
?
3 - O que é o framework Bootstrap? O que é o sistema de grade (grid system)?
4 - Refaça os apps dos exercícios dos capítulos anteriores utilizando o sidebarLayout
.
5 - Utilizando a base dados::clima
e o layout navbarPage
, faça um shiny app que tenha duas páginas:
a primeira com uma série temporal da média diária da
temperatura
, permitindo a escolha do intervalo de dias em que o gráfico é geradoa segunda com uma caixa de seleção permitindo escolher as opções
umidade
,velocidade_vento
eprecipitacao
e um gráfico de dispersão datemperatura
contra a variável escolhida.
6 - Transforme o aplicativo construído no exercício anterior em um shinydasbhoard
.
7 - Transforme o aplicativo construído no exercício anterior em um bs4Dash
.
Você pode conferir os tamanhos limites na documentação do Bootstrap: https://getbootstrap.com.br/docs/4.1/getting-started/introduction/)↩︎