Dashboards Interativos com Shiny for Python
“A diferença entre uma análise que permanece na gaveta e uma que transforma decisões está na interatividade. O Shiny for Python democratiza a criação de dashboards profissionais, transformando dados estáticos em experiências envolventes.”
O Shiny for Python é um framework revolucionário que permite criar aplicações web interativas diretamente em Python, sem necessidade de conhecer HTML, CSS ou JavaScript. Seguindo os princípios da gramática dos gráficos que você já domina, agora aprenderá a construir dashboards que trazem seus dados à vida.
Neste módulo, você desenvolverá:
- Fluência na arquitetura reativa que conecta inputs e outputs automaticamente
- Metodologia sistemática para construir layouts profissionais e intuitivos
- Domínio de componentes para criar interfaces ricas e responsivas
- Capacidade de integração com Altair para visualizações interativas
- Competência prática através de um caso real com dados do SUS
Vamos construir um dashboard completo para explorar as Unidades Básicas de Saúde (UBS) do Brasil, combinando mapas interativos, filtros dinâmicos e visualizações que respondem em tempo real às interações do usuário.
O que é o Shiny for Python?
A Revolução da Interatividade
Imagine transformar suas análises estáticas em experiências dinâmicas onde cada clique revela novos insights. O Shiny for Python torna isso realidade através do paradigma reativo - mudanças em inputs automaticamente disparam atualizações nos outputs relacionados.
from shiny.express import input, render, ui
from shinywidgets import render_widget
import altair as alt
import pandas as pd
import numpy as np
np.random.seed(42)
data = pd.DataFrame({
'estado': np.repeat(['CE', 'SP', 'RJ', 'MG'], 12),
'mes': list(range(1, 13)) * 4,
'atendimentos': np.random.normal(120000, 15000, 48).astype(int)
})
ui.input_radio_buttons("estado", "Estado:", ['CE', 'SP', 'RJ', 'MG'], inline=True)
@render_widget
def grafico():
dados = data[data["estado"] == input.estado()]
return alt.Chart(dados).mark_line(point=True, color='#005baa').encode(
x=alt.X('mes:O', title='Mês', axis=alt.Axis(labelAngle=0)),
y=alt.Y('atendimentos:Q', title='Atendimentos')
).properties(width=500, height=250)
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 400px
from shiny.express import input, render, ui
from shinywidgets import render_widget
import altair as alt
import pandas as pd
import numpy as np
ui.tags.style("""
.forward-fill-potential > * {
display: flex;
flex-direction: column;
flex: 1 1 0 !important;
min-height: 0;
min-width: 0;
width: 100%;
}
@media (min-width: 576px) {
.container-sm, .container {
max-width: none;
}
}
""")
np.random.seed(42)
data = pd.DataFrame({
'estado': np.repeat(['CE', 'SP', 'RJ', 'MG'], 12),
'mes': list(range(1, 13)) * 4,
'atendimentos': np.random.normal(120000, 15000, 48).astype(int)
})
ui.input_radio_buttons("estado", "Estado:", ['CE', 'SP', 'RJ', 'MG'], inline=True)
@render_widget
def grafico():
dados = data[data["estado"] == input.estado()]
return alt.Chart(dados).mark_line(point=True, color='#005baa').encode(
x=alt.X('mes:O', title='Mês', axis=alt.Axis(labelAngle=0)),
y=alt.Y('atendimentos:Q', title='Atendimentos')
).properties(width=500, height=250)
Anatomia de uma Aplicação Shiny
Toda aplicação Shiny é construída sobre três pilares fundamentais:
| Componente | Função | Analogia |
|---|---|---|
| UI (User Interface) | Define a aparência e layout | O “corpo” da aplicação - o que o usuário vê |
| Server | Contém a lógica e processamento | O “cérebro” da aplicação - onde a mágica acontece |
| Reatividade | Conecta UI e Server automaticamente | O “sistema nervoso” - como tudo se comunica |
Express vs Core: Duas Abordagens
O Shiny oferece duas sintaxes para criar aplicações:
- Shiny Express: Sintaxe simplificada onde UI e server coexistem no mesmo arquivo
- Shiny Core: Separação explícita entre UI e server para maior controle
Shiny Express (Recomendado para Iniciantes)
from shiny.express import input, render, ui
import numpy as np
import matplotlib.pyplot as plt
ui.h1("Explorando Dados com Shiny Express")
ui.input_slider("n", "Número de observações:", min=10, max=1000, value=500)
@render.plot
def histograma():
data = np.random.normal(size=input.n())
fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(data, bins=30, alpha=0.7, color='#2E86C1')
ax.set_title(f"Distribuição Normal - {input.n()} observações")
ax.set_xlabel("Valor")
ax.set_ylabel("Frequência")
return fig#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 500px
from shiny.express import input, render, ui
import numpy as np
import matplotlib.pyplot as plt
ui.input_slider("n", "Número de observações:", min=10, max=1000, value=500)
@render.plot
def histograma():
data = np.random.normal(size=input.n())
fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(data, bins=30, alpha=0.7, color='#2E86C1')
ax.set_title(f"Distribuição Normal - {input.n()} observações")
ax.set_xlabel("Valor")
ax.set_ylabel("Frequência")
return fig
Shiny Core (Separação Explícita)
from shiny import App, render, ui
import numpy as np
import matplotlib.pyplot as plt
# UI: Interface definida separadamente
app_ui = ui.page_fluid(
ui.input_slider("n", "Número de observações:", min=10, max=1000, value=500),
ui.output_plot("histograma")
)
# Server: Lógica separada
def server(input, output, session):
@render.plot
def histograma():
data = np.random.normal(size=input.n())
fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(data, bins=30, alpha=0.7, color='#E74C3C')
ax.set_title(f"Distribuição Normal - {input.n()} observações")
ax.set_xlabel("Valor")
ax.set_ylabel("Frequência")
return fig
# App: Conecta UI e Server
app = App(app_ui, server)#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 500px
from shiny import App, render, ui
import numpy as np
import matplotlib.pyplot as plt
# UI: Interface definida separadamente
app_ui = ui.page_fluid(
ui.input_slider("n", "Número de observações:", min=10, max=1000, value=500),
ui.output_plot("histograma")
)
# Server: Lógica separada
def server(input, output, session):
@render.plot
def histograma():
data = np.random.normal(size=input.n())
fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(data, bins=30, alpha=0.7, color='#E74C3C')
ax.set_title(f"Distribuição Normal - {input.n()} observações")
ax.set_xlabel("Valor")
ax.set_ylabel("Frequência")
return fig
# App: Conecta UI e Server
app = App(app_ui, server)
Em ambos os casos, quando você move o slider, o gráfico atualiza automaticamente. O Shiny detecta dependências e gerencia as atualizações!
Fundamentos da Reatividade
Como Funciona a Magia Reativa
A reatividade no Shiny funciona através de um sistema de dependências automáticas. Quando um output usa um input, o Shiny automaticamente:
- Detecta a dependência entre output e input
- Re-executa o output quando o input muda
- Minimiza atualizações executando apenas o necessário
from shiny.express import input, render, ui
import time
ui.input_text("nome", "Digite seu nome:", value="")
ui.input_slider("idade", "Sua idade:", min=0, max=100, value=25)
@render.text
def saudacao():
# Simula processamento
time.sleep(0.1)
if input.nome():
return f"Olá, {input.nome()}! Você tem {input.idade()} anos."
else:
return "Digite seu nome para começar..."
@render.text
def apenas_idade():
return f"Idade atual: {input.idade()}"#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 250px
from shiny.express import input, render, ui
import time
ui.input_text("nome", "Digite seu nome:", value="")
ui.input_slider("idade", "Sua idade:", min=0, max=100, value=25)
@render.text
def saudacao():
# Simula processamento
time.sleep(0.1)
if input.nome():
return f"Olá, {input.nome()}! Você tem {input.idade()} anos."
else:
return "Digite seu nome para começar..."
@render.text
def apenas_idade():
return f"Idade atual: {input.idade()}"
Cálculos Reativos: Evitando Repetições
Para cálculos que são usados em múltiplos outputs, use @reactive.calc para evitar reprocessamento desnecessário:
from shiny import reactive
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
ui.input_slider("n_amostras", "Número de amostras:", min=100, max=5000, value=1000)
ui.input_slider("seed", "Semente aleatória:", min=1, max=100, value=42)
@reactive.calc
def dados_processados():
# Simula processamento pesado
np.random.seed(input.seed())
return pd.DataFrame({
'x': np.random.normal(0, 1, input.n_amostras()),
'y': np.random.normal(0, 1, input.n_amostras())
})
@render.ui
def estatisticas():
dados = dados_processados() # Usa o cálculo reativo
return ui.HTML(f"""
Estatísticas dos dados:<br>
- Média X: {dados['x'].mean():.3f}<br>
- Média Y: {dados['y'].mean():.3f}<br>
- Correlação: {dados['x'].corr(dados['y']):.3f}<br>
""")
@render.text
def contagem():
dados = dados_processados() # Reutiliza sem recalcular!
return f"Total de {len(dados)} pontos gerados"#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 350px
from shiny import reactive
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
ui.input_slider("n_amostras", "Número de amostras:", min=100, max=5000, value=1000)
ui.input_slider("seed", "Semente aleatória:", min=1, max=100, value=42)
@reactive.calc
def dados_processados():
# Simula processamento pesado
np.random.seed(input.seed())
return pd.DataFrame({
'x': np.random.normal(0, 1, input.n_amostras()),
'y': np.random.normal(0, 1, input.n_amostras())
})
@render.ui
def estatisticas():
dados = dados_processados() # Usa o cálculo reativo
return ui.HTML(f"""
Estatísticas dos dados:<br>
- Média X: {dados['x'].mean():.3f}<br>
- Média Y: {dados['y'].mean():.3f}<br>
- Correlação: {dados['x'].corr(dados['y']):.3f}
""")
@render.text
def contagem():
dados = dados_processados() # Reutiliza sem recalcular!
return f"Total de {len(dados)} pontos gerados"
Construindo Interfaces Profissionais
Componentes Essenciais para Dashboards
O Shiny oferece uma rica coleção de componentes para criar interfaces intuitivas e responsivas. Vamos explorar os mais importantes para dashboards:
Inputs: Coletando Informações do Usuário
Os inputs são os componentes que permitem ao usuário interagir com sua aplicação. Todos seguem o padrão ui.input_*() e requerem um id único:
from shiny.express import input, render, ui
import datetime
# Layout em colunas para organizar os inputs
with ui.layout_columns(col_widths=[6, 6]):
# Coluna 1: Inputs básicos
with ui.card():
ui.card_header("Inputs Básicos")
ui.input_text("texto", "Campo de texto:", placeholder="Digite aqui...")
ui.input_numeric("numero", "Entrada numérica:", value=100, min=0, max=1000)
ui.input_slider("slider", "Controle deslizante:", min=0, max=100, value=50)
ui.input_date("data", "Seletor de data:", value=datetime.date.today())
# Coluna 2: Seleções e botões
with ui.card():
ui.card_header("Seleções e Controles")
ui.input_selectize("opcao", "Lista de seleção:",
choices=["Opção A", "Opção B", "Opção C"],
selected="Opção A")
ui.input_checkbox_group("grupo", "Múltipla escolha:",
choices=["Item 1", "Item 2", "Item 3"],
selected=["Item 1"])
ui.input_radio_buttons("radio", "Botões de rádio:",
choices=["Sim", "Não"], inline=True)
ui.input_action_button("botao", "Executar Ação", class_="btn-primary")
# Área de resultado
with ui.card():
ui.card_header("Valores Selecionados")
@render.ui
def valores_atuais():
return ui.div(
ui.p(f"Texto: {input.texto()}"),
ui.p(f"Número: {input.numero()}"),
ui.p(f"Slider: {input.slider()}"),
ui.p(f"Data: {input.data()}"),
ui.p(f"Seleção: {input.opcao()}"),
ui.p(f"Grupo: {', '.join(input.grupo())}"),
ui.p(f"Rádio: {input.radio()}"),
ui.p(f"Botão clicado: {input.botao()} vezes")
)#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 860px
from shiny.express import input, render, ui
import datetime
# Layout em colunas para organizar os inputs
with ui.layout_columns(col_widths=[6, 6]):
# Coluna 1: Inputs básicos
with ui.card():
ui.card_header("Inputs Básicos")
ui.input_text("texto", "Campo de texto:", placeholder="Digite aqui...")
ui.input_numeric("numero", "Entrada numérica:", value=100, min=0, max=1000)
ui.input_slider("slider", "Controle deslizante:", min=0, max=100, value=50)
ui.input_date("data", "Seletor de data:", value=datetime.date.today())
# Coluna 2: Seleções e botões
with ui.card():
ui.card_header("Seleções e Controles")
ui.input_selectize("opcao", "Lista de seleção:",
choices=["Opção A", "Opção B", "Opção C"],
selected="Opção A")
ui.input_checkbox_group("grupo", "Múltipla escolha:",
choices=["Item 1", "Item 2", "Item 3"],
selected=["Item 1"])
ui.input_radio_buttons("radio", "Botões de rádio:",
choices=["Sim", "Não"], inline=True)
ui.input_action_button("botao", "Executar Ação", class_="btn-primary")
# Área de resultado
with ui.card():
ui.card_header("Valores Selecionados")
@render.ui
def valores_atuais():
return ui.div(
ui.p(f"Texto: {input.texto()}"),
ui.p(f"Número: {input.numero()}"),
ui.p(f"Slider: {input.slider()}"),
ui.p(f"Data: {input.data()}"),
ui.p(f"Seleção: {input.opcao()}"),
ui.p(f"Grupo: {', '.join(input.grupo())}"),
ui.p(f"Rádio: {input.radio()}"),
ui.p(f"Botão clicado: {input.botao()} vezes")
)
Layouts: Organizando o Espaço
Cards e Value Boxes para KPIs
Value boxes são perfeitos para destacar métricas importantes:
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
# Dados simulados do SUS
np.random.seed(42)
dados_sus = pd.DataFrame({
'mes': pd.date_range('2024-01-01', periods=12, freq='M'),
'atendimentos': np.random.normal(15000, 2000, 12),
'consultas_especializadas': np.random.normal(5000, 800, 12),
'procedimentos': np.random.normal(3000, 500, 12)
})
# Layout com 3 value boxes simples
with ui.layout_columns(col_widths=[4, 4, 4]):
with ui.card():
ui.h4("Atendimentos Totais", style="font-size: 0.5rem; text-align: center; margin-bottom: 10px;")
@render.ui
def total_atendimentos():
total = dados_sus['atendimentos'].sum()
return ui.div(
ui.strong(f"{total:,.0f}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(últimos 12 meses)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Consultas Especializadas", style="text-align: center; margin-bottom: 10px;")
@render.ui
def total_consultas():
total = dados_sus['consultas_especializadas'].sum()
return ui.div(
ui.strong(f"{total:,.0f}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(cardiologia, neurologia, etc.)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Procedimentos Realizados", style="text-align: center; margin-bottom: 10px;")
@render.ui
def total_procedimentos():
total = dados_sus['procedimentos'].sum()
return ui.div(
ui.strong(f"{total:,.0f}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(cirurgias e exames)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
# Gráfico de tendência
with ui.card(full_screen=True):
ui.card_header("Evolução dos Atendimentos")
@render.plot
def grafico_atendimentos():
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(dados_sus['mes'], dados_sus['atendimentos'], marker='o', linewidth=2, color='#2E86C1')
ax.set_title('Evolução Mensal dos Atendimentos no SUS')
ax.set_xlabel('Mês')
ax.set_ylabel('Número de Atendimentos')
ax.grid(True, alpha=0.3)
# Formatação do eixo Y
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/1000:.0f}K'))
plt.tight_layout()
return fig#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 660px
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
# Dados simulados do SUS
np.random.seed(42)
dados_sus = pd.DataFrame({
'mes': pd.date_range('2024-01-01', periods=12, freq='M'),
'atendimentos': np.random.normal(15000, 2000, 12),
'consultas_especializadas': np.random.normal(5000, 800, 12),
'procedimentos': np.random.normal(3000, 500, 12)
})
# Layout com 3 value boxes simples
with ui.layout_columns(col_widths=[4, 4, 4]):
with ui.card(class_="gap-0"):
ui.h4("Atendimentos Totais", style="font-size: 1rem; text-align: center; margin-bottom: 10px;")
@render.ui
def total_atendimentos():
total = dados_sus['atendimentos'].sum()
return ui.div(
ui.strong(f"{total:,.0f}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(últimos 12 meses)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Consultas Especializadas", style="font-size: 1rem; text-align: center; margin-bottom: 10px;")
@render.ui
def total_consultas():
total = dados_sus['consultas_especializadas'].sum()
return ui.div(
ui.strong(f"{total:,.0f}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(cardiologia, neurologia, etc.)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Procedimentos Realizados", style="font-size: 1rem; text-align: center; margin-bottom: 10px;")
@render.ui
def total_procedimentos():
total = dados_sus['procedimentos'].sum()
return ui.div(
ui.strong(f"{total:,.0f}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(cirurgias e exames)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
# Gráfico de tendência
with ui.card(full_screen=True):
ui.card_header("Evolução dos Atendimentos")
@render.plot
def grafico_atendimentos():
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(dados_sus['mes'], dados_sus['atendimentos'], marker='o', linewidth=2, color='#2E86C1')
ax.set_title('Evolução Mensal dos Atendimentos no SUS')
ax.set_xlabel('Mês')
ax.set_ylabel('Número de Atendimentos')
ax.grid(True, alpha=0.3)
# Formatação do eixo Y
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/1000:.0f}K'))
plt.tight_layout()
return fig
Padrões Reativos Avançados
Controlando Atualizações com Eventos
Para dashboards mais sofisticados, você frequentemente quer controlar quando as atualizações acontecem, não apenas o que é atualizado. O Shiny oferece ferramentas poderosas para isso:
Botões de Ação e Processamento Controlado
Use @reactive.event para criar dashboards onde processamentos pesados só acontecem quando o usuário solicita:
from shiny import reactive
from shiny.express import input, render, ui
from shinywidgets import render_widget
import altair as alt
import pandas as pd
import numpy as np
import time
with ui.layout_columns(col_widths=[3, 9]):
with ui.card():
ui.card_header("Configurações")
ui.input_numeric("n_amostras", "Número de amostras:", value=1000, min=100, max=10000)
ui.input_selectize("distribuicao", "Tipo de distribuição:",
choices=["Normal", "Exponencial", "Uniforme"])
ui.input_action_button("gerar", "🎲 Gerar Dados", class_="btn-primary")
ui.input_action_button("analisar", "📊 Executar Análise", class_="btn-success")
with ui.card(full_screen=True):
ui.card_header("Resultados da Análise")
@render.text
@reactive.event(input.gerar)
def status_dados():
return f"✅ {input.n_amostras()} amostras da distribuição {input.distribuicao()} geradas!"
@render_widget
@reactive.event(input.analisar)
def grafico_analise():
# Simula processamento que demora
time.sleep(0.5)
# Gera dados baseado nas configurações
np.random.seed(42)
if input.distribuicao() == "Normal":
dados = np.random.normal(0, 1, input.n_amostras())
elif input.distribuicao() == "Exponencial":
dados = np.random.exponential(1, input.n_amostras())
else: # Uniforme
dados = np.random.uniform(-2, 2, input.n_amostras())
df = pd.DataFrame({'valores': dados})
return alt.Chart(df).mark_bar().encode(
x=alt.X('valores:Q', bin=alt.Bin(maxbins=30), title='Valor'),
y=alt.Y('count()', title='Frequência'),
color=alt.value('#2E86C1')
).properties(width=600, height=300)#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 550px
from shiny import reactive
from shiny.express import input, render, ui
from shinywidgets import render_widget
import altair as alt
import pandas as pd
import numpy as np
import time
with ui.layout_columns(col_widths=[3, 9]):
with ui.card():
ui.card_header("Configurações")
ui.input_numeric("n_amostras", "Número de amostras:", value=1000, min=100, max=10000)
ui.input_selectize("distribuicao", "Tipo de distribuição:",
choices=["Normal", "Exponencial", "Uniforme"])
ui.input_action_button("gerar", "🎲 Gerar Dados", class_="btn-primary")
ui.input_action_button("analisar", "📊 Executar Análise", class_="btn-success")
with ui.card(full_screen=True):
ui.card_header("Resultados da Análise")
@render.text
@reactive.event(input.gerar)
def status_dados():
return f"✅ {input.n_amostras()} amostras da distribuição {input.distribuicao()} geradas!"
@render_widget
@reactive.event(input.analisar)
def grafico_analise():
# Simula processamento que demora
time.sleep(0.5)
# Gera dados baseado nas configurações
np.random.seed(42)
if input.distribuicao() == "Normal":
dados = np.random.normal(0, 1, input.n_amostras())
elif input.distribuicao() == "Exponencial":
dados = np.random.exponential(1, input.n_amostras())
else: # Uniforme
dados = np.random.uniform(-2, 2, input.n_amostras())
df = pd.DataFrame({'valores': dados})
return alt.Chart(df).mark_bar().encode(
x=alt.X('valores:Q', bin=alt.Bin(maxbins=30), title='Valor'),
y=alt.Y('count()', title='Frequência'),
color=alt.value('#2E86C1')
).properties(width=600, height=300)
Validação de Inputs com req()
Para evitar erros quando inputs ainda não estão prontos, use req() para parar a execução até que condições sejam atendidas:
from shiny import reactive, req
from shiny.express import input, render, ui
import pandas as pd
ui.input_text("nome_usuario", "Digite seu nome:", placeholder="Insira seu nome aqui...")
ui.input_numeric("idade", "Sua idade:", value=None, min=0, max=120)
ui.input_selectize("cidade", "Sua cidade:", choices=["São Paulo", "Rio de Janeiro", "Belo Horizonte"], selected=None)
@render.text
def perfil_usuario():
req(input.nome_usuario()) # Só continua se nome foi digitado (não vazio)
req(input.idade()) # Só continua se idade foi informada
req(input.cidade()) # Só continua se cidade foi selecionada
return f"""
Perfil do usuário:
Nome: {input.nome_usuario()}
Idade: {input.idade()} anos
Cidade: {input.cidade()}
"""
@render.text
def validacao_status():
status = []
if input.nome_usuario():
status.append("✅ Nome preenchido")
else:
status.append("❌ Nome necessário")
if input.idade() and input.idade() > 0:
status.append("✅ Idade válida")
else:
status.append("❌ Idade necessária")
if input.cidade():
status.append("✅ Cidade selecionada")
else:
status.append("❌ Cidade necessária")
return "\n".join(status)#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 350px
from shiny import reactive, req
from shiny.express import input, render, ui
import pandas as pd
ui.input_text("nome_usuario", "Digite seu nome:", placeholder="Insira seu nome aqui...")
ui.input_numeric("idade", "Sua idade:", value=None, min=0, max=120)
ui.input_selectize("cidade", "Sua cidade:", choices=["São Paulo", "Rio de Janeiro", "Belo Horizonte"], selected=None)
@render.text
def perfil_usuario():
req(input.nome_usuario()) # Só continua se nome foi digitado (não vazio)
req(input.idade()) # Só continua se idade foi informada
req(input.cidade()) # Só continua se cidade foi selecionada
return f"""
Perfil do usuário:
Nome: {input.nome_usuario()}
Idade: {input.idade()} anos
Cidade: {input.cidade()}
"""
@render.text
def validacao_status():
status = []
if input.nome_usuario():
status.append("✅ Nome preenchido")
else:
status.append("❌ Nome necessário")
if input.idade() and input.idade() > 0:
status.append("✅ Idade válida")
else:
status.append("❌ Idade necessária")
if input.cidade():
status.append("✅ Cidade selecionada")
else:
status.append("❌ Cidade necessária")
return "\n".join(status)
Valores Reativos: Mantendo Estado
Para armazenar estado que pode mudar durante a execução da aplicação, use reactive.value():
from shiny import reactive
from shiny.express import input, render, ui
ui.h2("Histórico de Interações")
ui.input_slider("valor", "Ajuste o valor:", min=0, max=100, value=50)
ui.input_action_button("salvar", "💾 Salvar Valor", class_="btn-primary")
ui.input_action_button("limpar", "🗑️ Limpar Histórico", class_="btn-warning")
# Valor reativo para armazenar histórico
historico = reactive.value([])
@reactive.effect
@reactive.event(input.salvar)
def salvar_valor():
valores_atuais = list(historico()) # Cria uma nova lista
valores_atuais.append(input.valor())
historico.set(valores_atuais)
@reactive.effect
@reactive.event(input.limpar)
def limpar_historico():
historico.set([])
@render.ui
def mostrar_historico():
valores = historico()
if valores:
return ui.div([ui.p(f"Valor {i+1}: {v}") for i, v in enumerate(valores)])
else:
return ui.p("Nenhum valor salvo ainda...")
@render.text
def estatisticas():
valores = historico()
if valores:
return f"Total: {len(valores)} | Média: {sum(valores)/len(valores):.1f}"
else:
return "Adicione valores para ver estatísticas"#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| layout: vertical
##| viewerHeight: 400px
from shiny import reactive
from shiny.express import input, render, ui
ui.h2("Histórico de Interações")
ui.input_slider("valor", "Ajuste o valor:", min=0, max=100, value=50)
ui.input_action_button("salvar", "💾 Salvar Valor", class_="btn-primary")
ui.input_action_button("limpar", "🗑️ Limpar Histórico", class_="btn-warning")
# Valor reativo para armazenar histórico
historico = reactive.value([])
@reactive.effect
@reactive.event(input.salvar)
def salvar_valor():
valores_atuais = list(historico()) # Cria uma nova lista
valores_atuais.append(input.valor())
historico.set(valores_atuais)
@reactive.effect
@reactive.event(input.limpar)
def limpar_historico():
historico.set([])
@render.ui
def mostrar_historico():
valores = historico()
if valores:
return ui.div([ui.p(f"Valor {i+1}: {v}") for i, v in enumerate(valores)])
else:
return ui.p("Nenhum valor salvo ainda...")
@render.text
def estatisticas():
valores = historico()
if valores:
return f"Total: {len(valores)} | Média: {sum(valores)/len(valores):.1f}"
else:
return "Adicione valores para ver estatísticas"
Trabalhando com Dados e Tabelas
Tabelas Interativas com @render.data_frame
Para dashboards que trabalham com dados tabulares, o Shiny oferece componentes poderosos para exibir e filtrar tabelas:
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
# Dados simulados do SUS
np.random.seed(42)
sus_data = pd.DataFrame({
'especialidade': np.random.choice(['Cardiologia', 'Neurologia', 'Ortopedia', 'Pediatria'], 200),
'regiao': np.random.choice(['Norte', 'Sul', 'Leste', 'Oeste'], 200),
'atendimentos': np.random.normal(100, 30, 200).round(0).astype(int),
'data': pd.date_range('2024-01-01', periods=200, freq='D')[:200]
})
with ui.layout_columns(col_widths=[3, 9]):
with ui.card():
ui.card_header("Filtros")
ui.input_selectize("especialidade_filtro", "Especialidades:",
choices=list(sus_data['especialidade'].unique()),
multiple=True,
selected=list(sus_data['especialidade'].unique()))
ui.input_date_range("data_filtro", "Período:",
start="2024-01-01",
end="2024-07-18")
ui.input_slider("atendimentos_min", "Atendimentos mínimos:",
min=int(sus_data['atendimentos'].min()),
max=int(sus_data['atendimentos'].max()),
value=int(sus_data['atendimentos'].min()))
with ui.card():
ui.card_header("Dados Filtrados")
@render.data_frame
def tabela_sus():
# Aplica filtros
df_filtrado = sus_data[
(sus_data['especialidade'].isin(input.especialidade_filtro())) &
(sus_data['data'] >= pd.to_datetime(input.data_filtro()[0])) &
(sus_data['data'] <= pd.to_datetime(input.data_filtro()[1])) &
(sus_data['atendimentos'] >= input.atendimentos_min())
]
return render.DataGrid(
df_filtrado,
filters=True, # Permite filtros adicionais na tabela
selection_mode="rows" # Permite seleção de linhas
)
# Mostra estatísticas dos dados selecionados
with ui.card():
ui.card_header("Resumo dos Dados Filtrados")
@render.text
def resumo_filtrado():
# Recria o mesmo filtro
df_filtrado = sus_data[
(sus_data['especialidade'].isin(input.especialidade_filtro())) &
(sus_data['data'] >= pd.to_datetime(input.data_filtro()[0])) &
(sus_data['data'] <= pd.to_datetime(input.data_filtro()[1])) &
(sus_data['atendimentos'] >= input.atendimentos_min())
]
if len(df_filtrado) > 0:
return f"""
📊 Total de registros: {len(df_filtrado)}
🏥 Atendimentos totais: {df_filtrado['atendimentos'].sum():,}
📈 Média de atendimentos: {df_filtrado['atendimentos'].mean():.1f}
🏆 Maior número: {df_filtrado['atendimentos'].max()}
"""
else:
return "Nenhum dado encontrado com os filtros aplicados."#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 760px
from shiny.express import input, render, ui
import pandas as pd
import numpy as np
# Dados simulados do SUS
np.random.seed(42)
sus_data = pd.DataFrame({
'especialidade': np.random.choice(['Cardiologia', 'Neurologia', 'Ortopedia', 'Pediatria'], 200),
'regiao': np.random.choice(['Norte', 'Sul', 'Leste', 'Oeste'], 200),
'atendimentos': np.random.normal(100, 30, 200).round(0).astype(int),
'data': pd.date_range('2024-01-01', periods=200, freq='D')[:200]
})
with ui.layout_columns(col_widths=[3, 9]):
with ui.card():
ui.card_header("Filtros")
ui.input_selectize("especialidade_filtro", "Especialidades:",
choices=list(sus_data['especialidade'].unique()),
multiple=True,
selected=list(sus_data['especialidade'].unique()))
ui.input_date_range("data_filtro", "Período:",
start="2024-01-01",
end="2024-07-18")
ui.input_slider("atendimentos_min", "Atendimentos mínimos:",
min=int(sus_data['atendimentos'].min()),
max=int(sus_data['atendimentos'].max()),
value=int(sus_data['atendimentos'].min()))
with ui.card():
ui.card_header("Dados Filtrados")
@render.data_frame
def tabela_sus():
# Aplica filtros
df_filtrado = sus_data[
(sus_data['especialidade'].isin(input.especialidade_filtro())) &
(sus_data['data'] >= pd.to_datetime(input.data_filtro()[0])) &
(sus_data['data'] <= pd.to_datetime(input.data_filtro()[1])) &
(sus_data['atendimentos'] >= input.atendimentos_min())
]
return render.DataGrid(
df_filtrado,
filters=True, # Permite filtros adicionais na tabela
selection_mode="rows" # Permite seleção de linhas
)
# Mostra estatísticas dos dados selecionados
with ui.card():
ui.card_header("Resumo dos Dados Filtrados")
@render.text
def resumo_filtrado():
# Recria o mesmo filtro
df_filtrado = sus_data[
(sus_data['especialidade'].isin(input.especialidade_filtro())) &
(sus_data['data'] >= pd.to_datetime(input.data_filtro()[0])) &
(sus_data['data'] <= pd.to_datetime(input.data_filtro()[1])) &
(sus_data['atendimentos'] >= input.atendimentos_min())
]
if len(df_filtrado) > 0:
return f"""
📊 Total de registros: {len(df_filtrado)}
🏥 Atendimentos totais: {df_filtrado['atendimentos'].sum():,}
📈 Média de atendimentos: {df_filtrado['atendimentos'].mean():.1f}
🏆 Maior número: {df_filtrado['atendimentos'].max()}
"""
else:
return "Nenhum dado encontrado com os filtros aplicados."
Interfaces Dinâmicas com @render.ui
Para dashboards que precisam adaptar a interface baseado nos dados ou inputs do usuário, use @render.ui:
from shiny.express import input, render, ui
import pandas as pd
ui.input_selectize("tipo_analise", "Tipo de Análise:",
choices=["Simples", "Avançada", "Comparativa"])
@render.ui
def interface_dinamica():
if input.tipo_analise() == "Simples":
return ui.div(
ui.input_slider("valor_simples", "Valor:", min=0, max=100, value=50),
ui.h4("Análise Simples Ativada")
)
elif input.tipo_analise() == "Avançada":
return ui.div(
ui.input_numeric("n_simulacoes", "Número de simulações:", value=1000),
ui.input_selectize("metodo", "Método:", choices=["Monte Carlo", "Bootstrap"]),
ui.input_checkbox("incluir_intervalo", "Incluir intervalo de confiança", value=True),
ui.h4("Análise Avançada Configurada")
)
else: # Comparativa
return ui.div(
ui.input_selectize("grupo_a", "Grupo A:", choices=["Dataset 1", "Dataset 2"]),
ui.input_selectize("grupo_b", "Grupo B:", choices=["Dataset 1", "Dataset 2"]),
ui.input_selectize("teste_estatistico", "Teste:", choices=["t-test", "Mann-Whitney"]),
ui.h4("Análise Comparativa Pronta")
)
@render.text
def resultado_dinamico():
if input.tipo_analise() == "Simples":
if hasattr(input, 'valor_simples') and input.valor_simples():
return f"Resultado simples: {input.valor_simples() * 2}"
elif input.tipo_analise() == "Avançada":
if hasattr(input, 'n_simulacoes') and input.n_simulacoes():
return f"Executando {input.n_simulacoes()} simulações..."
else:
return "Configure os grupos para comparação"
return "Aguardando configuração..."#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 450px
from shiny.express import input, render, ui
import pandas as pd
ui.input_selectize("tipo_analise", "Tipo de Análise:",
choices=["Simples", "Avançada", "Comparativa"])
@render.ui
def interface_dinamica():
if input.tipo_analise() == "Simples":
return ui.div(
ui.input_slider("valor_simples", "Valor:", min=0, max=100, value=50),
ui.h4("Análise Simples Ativada")
)
elif input.tipo_analise() == "Avançada":
return ui.div(
ui.input_numeric("n_simulacoes", "Número de simulações:", value=1000),
ui.input_selectize("metodo", "Método:", choices=["Monte Carlo", "Bootstrap"]),
ui.input_checkbox("incluir_intervalo", "Incluir intervalo de confiança", value=True),
ui.h4("Análise Avançada Configurada")
)
else: # Comparativa
return ui.div(
ui.input_selectize("grupo_a", "Grupo A:", choices=["Dataset 1", "Dataset 2"]),
ui.input_selectize("grupo_b", "Grupo B:", choices=["Dataset 1", "Dataset 2"]),
ui.input_selectize("teste_estatistico", "Teste:", choices=["t-test", "Mann-Whitney"]),
ui.h4("Análise Comparativa Pronta")
)
@render.text
def resultado_dinamico():
if input.tipo_analise() == "Simples":
if hasattr(input, 'valor_simples') and input.valor_simples():
return f"Resultado simples: {input.valor_simples() * 2}"
elif input.tipo_analise() == "Avançada":
if hasattr(input, 'n_simulacoes') and input.n_simulacoes():
return f"Executando {input.n_simulacoes()} simulações..."
else:
return "Configure os grupos para comparação"
return "Aguardando configuração..."
UI Condicional e Dinâmica
Para dashboards mais sofisticados, você pode mostrar/esconder elementos baseado na interação do usuário:
from shiny import reactive
from shiny.express import input, render, ui
import pandas as pd
ui.h2("Interface Adaptativa")
ui.input_selectize("modo_dashboard", "Modo do Dashboard:",
choices=["Básico", "Avançado", "Executivo"])
# UI condicional baseada em JavaScript
with ui.panel_conditional("input.modo_dashboard === 'Básico'"):
ui.h4("🟢 Modo Básico Ativo")
ui.input_slider("valor_basico", "Configuração simples:", min=0, max=100, value=50)
with ui.panel_conditional("input.modo_dashboard === 'Avançado'"):
ui.h4("🟡 Modo Avançado Ativo")
ui.input_numeric("iteracoes", "Número de iterações:", value=1000)
ui.input_selectize("algoritmo", "Algoritmo:", choices=["A", "B", "C"])
with ui.panel_conditional("input.modo_dashboard === 'Executivo'"):
ui.h4("🔴 Modo Executivo Ativo")
ui.p("Apenas visualizações de alto nível e KPIs")
# Atualizando inputs programaticamente
ui.input_selectize("categoria", "Categoria:", choices=["Vendas", "Marketing", "Operações"])
ui.input_selectize("subcategoria", "Subcategoria:", choices=[])
@reactive.effect
def atualizar_subcategorias():
opcoes = {
"Vendas": ["Receita", "Comissões", "Metas"],
"Marketing": ["Campanhas", "ROI", "Leads"],
"Operações": ["Custos", "Eficiência", "Qualidade"]
}
ui.update_selectize("subcategoria",
choices=opcoes.get(input.categoria(), []))
@render.text
def resultado_configuracao():
if input.categoria() and input.subcategoria():
return f"Analisando: {input.categoria()} → {input.subcategoria()}"
return "Selecione categoria e subcategoria"#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 500px
from shiny import reactive
from shiny.express import input, render, ui
import pandas as pd
ui.h2("Interface Adaptativa")
ui.input_selectize("modo_dashboard", "Modo do Dashboard:",
choices=["Básico", "Avançado", "Executivo"])
# UI condicional baseada em JavaScript
with ui.panel_conditional("input.modo_dashboard === 'Básico'"):
ui.h4("🟢 Modo Básico Ativo")
ui.input_slider("valor_basico", "Configuração simples:", min=0, max=100, value=50)
with ui.panel_conditional("input.modo_dashboard === 'Avançado'"):
ui.h4("🟡 Modo Avançado Ativo")
ui.input_numeric("iteracoes", "Número de iterações:", value=1000)
ui.input_selectize("algoritmo", "Algoritmo:", choices=["A", "B", "C"])
with ui.panel_conditional("input.modo_dashboard === 'Executivo'"):
ui.h4("🔴 Modo Executivo Ativo")
ui.p("Apenas visualizações de alto nível e KPIs")
# Atualizando inputs programaticamente
ui.input_selectize("categoria", "Categoria:", choices=["Vendas", "Marketing", "Operações"])
ui.input_selectize("subcategoria", "Subcategoria:", choices=[])
@reactive.effect
def atualizar_subcategorias():
opcoes = {
"Vendas": ["Receita", "Comissões", "Metas"],
"Marketing": ["Campanhas", "ROI", "Leads"],
"Operações": ["Custos", "Eficiência", "Qualidade"]
}
ui.update_selectize("subcategoria",
choices=opcoes.get(input.categoria(), []))
@render.text
def resultado_configuracao():
if input.categoria() and input.subcategoria():
return f"Analisando: {input.categoria()} → {input.subcategoria()}"
return "Selecione categoria e subcategoria"
Feedback Visual para o Usuário
Para melhor experiência do usuário, forneça feedback visual durante operações:
from shiny import reactive
from shiny.express import input, render, ui
import asyncio
ui.input_action_button("processar_dados", "🚀 Processar Dados", class_="btn-primary")
ui.input_action_button("mostrar_notificacao", "💬 Mostrar Notificação", class_="btn-info")
@reactive.effect
@reactive.event(input.processar_dados)
async def processar_com_progresso():
# Cria barra de progresso
with ui.Progress(min=0, max=10) as progress:
progress.set(0, message="Iniciando processamento...")
for i in range(10):
await asyncio.sleep(0.3) # Simula processamento
progress.set(i + 1,
message=f"Processando etapa {i + 1}/10",
detail=f"Completando análise...")
# Notificação de sucesso
ui.notification_show(
"✅ Processamento concluído com sucesso!",
type="success",
duration=3
)
@reactive.effect
@reactive.event(input.mostrar_notificacao)
def mostrar_notificacoes():
ui.notification_show("ℹ️ Esta é uma informação", type="message")
ui.notification_show("⚠️ Este é um aviso", type="warning")
ui.notification_show("❌ Este é um erro", type="error")
@render.text
def status():
return f"Botão processar clicado: {input.processar_dados()} vezes"#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 500px
from shiny import reactive
from shiny.express import input, render, ui
import asyncio
ui.input_action_button("processar_dados", "🚀 Processar Dados", class_="btn-primary")
ui.input_action_button("mostrar_notificacao", "💬 Mostrar Notificação", class_="btn-info")
@reactive.effect
@reactive.event(input.processar_dados)
async def processar_com_progresso():
# Cria barra de progresso
with ui.Progress(min=0, max=10) as progress:
progress.set(0, message="Iniciando processamento...")
for i in range(10):
await asyncio.sleep(0.3) # Simula processamento
progress.set(i + 1,
message=f"Processando etapa {i + 1}/10",
detail=f"Completando análise...")
# Notificação de sucesso
ui.notification_show(
"✅ Processamento concluído com sucesso!",
type="success",
duration=3
)
@reactive.effect
@reactive.event(input.mostrar_notificacao)
def mostrar_notificacoes():
ui.notification_show("ℹ️ Esta é uma informação", type="message")
ui.notification_show("⚠️ Este é um aviso", type="warning")
ui.notification_show("❌ Este é um erro", type="error")
@render.text
def status():
return f"Botão processar clicado: {input.processar_dados()} vezes"
Dashboards em Tempo Real
Para dashboards que precisam atualizar automaticamente (sem interação do usuário), use reactive.invalidate_later():
from shiny import reactive
from shiny.express import input, render, ui
from datetime import datetime
import pandas as pd
import numpy as np
with ui.layout_columns(col_widths=[4, 4, 4]):
with ui.card():
ui.h4("Hora Atual", style="text-align: center; margin-bottom: 10px;")
@render.ui
def hora_atual():
reactive.invalidate_later(1) # Atualiza a cada 1 segundo
return ui.div(
ui.strong(datetime.now().strftime("%H:%M:%S"), style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(tempo real)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Atendimentos Agora", style="text-align: center; margin-bottom: 10px;")
@render.ui
def valor_aleatorio():
reactive.invalidate_later(3) # Atualiza a cada 3 segundos
return ui.div(
ui.strong(f"{np.random.randint(100, 999)}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(simulação ao vivo)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Status Sistema", style="text-align: center; margin-bottom: 10px;")
@render.ui
def status_sistema():
reactive.invalidate_later(5) # Atualiza a cada 5 segundos
status = np.random.choice(["🟢 Online", "🟡 Lento", "🔴 Offline"], p=[0.8, 0.15, 0.05])
return ui.div(
ui.strong(status, style="font-size: 1.5rem; text-align: center; display: block;"),
ui.p("(monitoramento)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
@render.plot
def grafico_tempo_real():
reactive.invalidate_later(2) # Atualiza a cada 2 segundos
# Simula dados que mudam com o tempo
import matplotlib.pyplot as plt
x = list(range(10))
y = [np.random.randint(1, 20) for _ in range(10)]
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, y, marker='o', linewidth=2, color='#E74C3C')
ax.set_title('Dados em Tempo Real')
ax.set_xlabel('Tempo')
ax.set_ylabel('Valor')
ax.grid(True, alpha=0.3)
return fig#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 600px
from shiny import reactive
from shiny.express import input, render, ui
from datetime import datetime
import pandas as pd
import numpy as np
with ui.layout_columns(col_widths=[4, 4, 4]):
with ui.card():
ui.h4("Hora Atual", style="text-align: center; margin-bottom: 10px;")
@render.ui
def hora_atual():
reactive.invalidate_later(1) # Atualiza a cada 1 segundo
return ui.div(
ui.strong(datetime.now().strftime("%H:%M:%S"), style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(tempo real)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Atendimentos Agora", style="text-align: center; margin-bottom: 10px;")
@render.ui
def valor_aleatorio():
reactive.invalidate_later(3) # Atualiza a cada 3 segundos
return ui.div(
ui.strong(f"{np.random.randint(100, 999)}", style="font-size: 2rem; text-align: center; display: block;"),
ui.p("(simulação ao vivo)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
with ui.card():
ui.h4("Status Sistema", style="text-align: center; margin-bottom: 10px;")
@render.ui
def status_sistema():
reactive.invalidate_later(5) # Atualiza a cada 5 segundos
status = np.random.choice(["🟢 Online", "🟡 Lento", "🔴 Offline"], p=[0.8, 0.15, 0.05])
return ui.div(
ui.strong(status, style="font-size: 1.5rem; text-align: center; display: block;"),
ui.p("(monitoramento)", style="text-align: center; font-size: 0.9rem; color: #666;")
)
@render.plot
def grafico_tempo_real():
reactive.invalidate_later(2) # Atualiza a cada 2 segundos
# Simula dados que mudam com o tempo
import matplotlib.pyplot as plt
x = list(range(10))
y = [np.random.randint(1, 20) for _ in range(10)]
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, y, marker='o', linewidth=2, color='#E74C3C')
ax.set_title('Dados em Tempo Real')
ax.set_xlabel('Tempo')
ax.set_ylabel('Valor')
ax.grid(True, alpha=0.3)
return fig
Use reactive.invalidate_later() com moderação. Atualizações muito frequentes podem sobrecarregar o servidor e a interface do usuário. Para dados críticos, considere intervalos de 5-30 segundos.
Isolamento e Eventos
Use @reactive.event para controlar quando uma função reativa é executada:
from shiny import reactive
from shiny.express import input, render, ui
import time
import asyncio
ui.input_numeric("numero", "Insira um número:", value=10)
ui.input_action_button("processar", "🚀 Processar", class_="btn-success")
@render.text
@reactive.event(input.processar) # Executa APENAS quando o botão é clicado
async def resultado():
numero = input.numero()
# Simula processamento longo
await asyncio.sleep(1)
resultado = numero ** 2
return f"O quadrado de {numero} é {resultado}"
@render.text
def valor_atual():
# Este sempre atualiza quando o input muda
return f"Valor atual: {input.numero()}"#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
##| standalone: true
##| components: [viewer]
##| viewerHeight: 200px
from shiny import reactive
from shiny.express import input, render, ui
import asyncio
ui.input_numeric("numero", "Insira um número:", value=10)
ui.input_action_button("processar", "🚀 Processar", class_="btn-success")
@render.text
@reactive.event(input.processar) # Executa APENAS quando o botão é clicado
async def resultado():
numero = input.numero()
# Simula processamento longo
await asyncio.sleep(1)
resultado = numero ** 2
return f"O quadrado de {numero} é {resultado}"
@render.text
def valor_atual():
# Este sempre atualiza quando o input muda
return f"Valor atual: {input.numero()}"
Conclusão
Parabéns! Você dominou os fundamentos para criar dashboards interativos profissionais com Shiny for Python. Esta ferramenta revolucionária democratiza a criação de aplicações web, permitindo que você transforme análises estáticas em experiências dinâmicas e envolventes.
Ao longo deste módulo, você desenvolveu competências essenciais:
✅ Programação reativa - Entendimento do paradigma que conecta inputs e outputs automaticamente
✅ Arquitetura de dashboards - Domínio de layouts, cards, value boxes e componentes profissionais
✅ Controle avançado de fluxo - Uso de @reactive.event, req() e validação para experiências robustas
✅ Interfaces dinâmicas - Criação de UIs condicionais e feedback visual responsivo
✅ Visualizações interativas - Integração fluida com Altair para gráficos profissionais
✅ Monitoramento em tempo real - Implementação de dashboards que atualizam automaticamente
Com essas habilidades, você está equipado para criar desde dashboards simples para análises pessoais até aplicações complexas para sua organização. O Shiny for Python oferece um caminho escalável: comece simples e evolua conforme suas necessidades crescem.