Dashboards com Plotly Express - Parte 4

24 minute read

Olá 😀!

Agora vamos para a parte final (ufa 😅) dessa série de posts sobre o desenvolvimento de Dashboards com plotly express. Na Parte 1 eu montei o layout básico e criei o Dashboard para dados do Brasil, comparando o número de focos a cada ano. Na Parte 2 eu criei o Dashboard para dados de focos de queimadas por Região do Brasil. Na Parte 3 eu criei gráficos para os estados individualmente. E chegou a hora de juntar tudo que foi feito em um único Dashboard.

Como o layout de cada Dasboard desenvolvido foi feito dentro de uma html.Div(), basta copiar e colar cada Div na posição desejada.

De todos as Divs criadas, falta apenas a do footer, que é apenas o meu contato e um link para a fonte dos dados, e por ser muito simples, eu não vou detalhar o passo a passo aqui.

app = dash.Dash() # Criando a instancia da aplicação

app.layout = html.Div([ # Div geral
                    html.Div(), # Div para o Titulo Geral
                    html.Div(), # Div para os dados do Brasil (mapa)
                    html.Div(), # Div para os dados separados por Região
                    html.Div(), # Div para os dados separados por estado
                    html.Div(), # Div para um footer
])


Eu vou fazer igual ao Jack (🎃), e criar o arquivo .py por partes.

  • Importações das bibliotecas utilizadas

Logo no inicio do script devem acontecer a importação das bibliotecas utilizadas, como é o usual.

# importação das bibliotecas utilizadas
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import pandas as pd
import numpy as np
import plotly.express as px
import json
import dash_table
from dash_table.Format import Format, Group, Scheme, Symbol


  • Importação dos dados

Logo em seguida, devemos importar os dados (DataFrames e dados de geo localização). É importante que a importação dos dados ocorra uma única vez, para evitar gasto de banda larga repetido (importar o mesmo arquivo várias vezes).

Neste Dashboard, temos quatro DataFrames para importar, além dos dados de geo localização:

### Carregando os dados
## carregando os DataFrames
# carregando os dados de focos de queimadas
df = pd.read_csv('historico_estados_queimadas.csv', encoding='latin-1')
# carregando os dados de texto com um resumo sobre os focos de queimadas separado por Ano
df_texto_ano = pd.read_csv('info-anos.csv', encoding='latin-1', sep=";")
# carregando os dados de texto com um resumo sobre a geografia das Regiões brasileiras
df_texto_regiao = pd.read_csv('info-regioes.csv', encoding='latin-1', sep=";")
# carregando os dados de texto com um resumo sobre a geografia dos estados brasileiros
df_texto_estados = pd.read_csv('info-estados.csv', encoding='latin-1', sep=";")

## Carregando o arquivo de geo localização
with open('estados_brasil.geojson') as data: # carregando o arquivo ".geojson"
    limites_brasil = json.load(data)


  • Manipulação dos dados

Em seguida, fazemos todas as manipulações que vão acontecer apenas uma vez nos dados. Por exemplo, o ajuste do dados de geo localização; a lista com os Anos para o Dropdown referente aos anos; a lisa com as Regiões para o Dropdown referente as Regiões, etc. Talvez tenha ficado alguma coisa para trás aqui, mas a maioria esta no lugar certo.

## manipulando os dados
# criando as opções que serão apresentadas para o usuario trocar de ano no mapa do Brasil
year_options = []
for ano in df['Ano'].unique():
    year_options.append({'label':str(ano), 'value':ano})

# criando uma lista com os valores únicos de região para utilizar no dropdown da região
regiao_options = []
for reg in df['Regiao'].unique():
    regiao_options.append({'label':reg, 'value':reg})

# criando uma lista para conter as opções que o usuario terá para escolher - estados
state_options = []
for state in df['UF'].unique():
    state_options.append({'label':state, 'value':state})    

# adicionando ID ao arquivo de geo localização    
for feature in limites_brasil ['features']: # adicionado o ID aos dados
    feature['id'] = feature['properties']['name']    


  • Criando a instância da aplicação

Agora criamos a instância de app, com as planilhas de estilo externas (external_stylesheets) já aplicadas. Vou aproveitar e alterar o nome da aplicação.

Você talvez tenha reparado que o ícone da aplicação nos prints é diferente do ícone do Dash. Esta diferente, pois, na pasta onde o servidor está sendo gerado tem uma subpasta chamada “assets”, e nesta pasta em um arquivo chamado “favicon.ico” que é o ícone de um software que eu desenvolvi (o CAVS). O Dash automaticamente procura por um arquivo com nome “favicon.ico” caso exista uma pasta “assets” no root.

# Criando a instancia da aplicação
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.themes.GRID])
# alterando o nome da aplicação
app.title = 'Histórico de focos de queimadas no Brasil'


  • Em seguida criamos o Layout

Agora é necessário criar o Layout da nossa aplicação. Basta alocar um Dashboard abaixo do outro, sendo que cada um esta em uma Div().

# Criando o Layout da aplicação

app.layout = html.Div([ # Div geral
                    html.Div(# div para o modal
                        dbc.Modal( # add o modal
                            [
                                dbc.ModalHeader("Aviso!", # add o texto do cabeçalho do modal
                                               style = {'color': 'red'}), # alterando a cor to texto do cabeçalho do modal
                                dbc.ModalBody( # adicionando o corpo do modal, que é um conjunto de elementos html
                                    [
                                        # add texto
                                        html.Label("O mapa do Brasil pode demorar alguns segundos a mais para atualizar do que os demais gráficos em alguns navegadores."),
                                        html.Br(), # adcionando um linha em branco
                                        html.Label("Pedimos desculpas pelo incoveniente"), # adicionando mais texto
                                        html.Label("\U0001F605") # adicionando um emoticon
                                    ]
                                ),
                                dbc.ModalFooter( # add o footer
                                    dbc.Button( # add um botão para fechar o modal
                                        "Ok!", # texto do botão
                                        id = 'close-sm', # id do botão
                                        className = "ml-auto", # dando uma classe de botão para o botão
                                    )
                                ),
                            ],
                            id = 'modal', # add uma ID ao modal
                            is_open = True, # definindo que o modal vai estar aberto quando o usuario abrir o dashboard
                            centered = True, # definindo que o modal vai estar centralizado
                            style = {'textAlign': 'center'} # centralizando o texto do modal
                        )
                    ),
                    html.Div(# Div para o Titulo Geral
                        dbc.Row( # Linha
                            dbc.Col( # Coluna
                                html.H3("Histórico de queimadas no Brasil entre 1998 e 2020.") # Aqui é o elemento de texto
                            ), style = {'textAlign': 'center', 'color': 'white'} # deixando o conteúdo da coluna centralizado
                        ), style = {'paddingTop': "20px", 'paddingBottom': "20px", 'color':'white'} # adicionado espaçamento para a linha
                    ),
                    html.Div( # Div para os dados do Brasil (mapa)
                        [
                            dbc.Row(# Titulo
                                dbc.Col(
                                    html.H3(id="title-year"),
                                ), style = {'textAlign': 'center', 'paddingTop': '40px', 'paddingBottom': '40px'}
                            ),
                            dbc.Row(
                                [
                                    dbc.Col( # texto
                                        html.Label("Escolha um ano"), # texto que será impresso
                                        width = 3, # número de colunas que o texto irá preencher
                                        align = 'left', # posição do elemento dentro do número de colunas setado por width
                                        style = {'diplay': 'inline-block'}, # apenas estilo
                                    ),
                                    dbc.Col( # popover
                                        html.Div(
                                            [
                                                dbc.Button( # botão que compoe o popover
                                                    "+ info", # Texto do botão
                                                    outline = True, # Adiciona contorno ao botão para melhorar o stylo
                                                    id = "popovertarget-mapa", # id do botão
                                                    style= {'fontFamily': 'Garamond', }, # alterando a fonte do botão
                                                    className="mr-2", # alterando o tipo do botão com uma classe do bootstrap
                                                    color="success", # alterando a cor do botão
                                                    size="sm", # alterando o tamanho do botão para pequeno
                                                ),
                                                dbc.Popover( # popover em si
                                                    [
                                                        dbc.PopoverHeader(id='popover-header-mapa'), # id do cabeçalho do popover
                                                        dbc.PopoverBody( # O corpo do popover
                                                            dcc.Markdown( # elemento para rodar tags markdown
                                                                    id='popover-body-mapa', # id do corpo do popover
                                                                    style={'textAlign': 'justify',} # deixando o texto do corpo justificado
                                                                ),
                                                                style= {'overflow': 'auto', # adicionado barra de rolagem ao corpo do popover
                                                                        'max-height': '500px'} # colocando um tamanho máximo para a caixa do popover
                                                            ),
                                                    ],
                                                    id ='popover-mapa', # setando a id
                                                    target = "popovertarget-mapa", # setando o botão de target
                                                    placement='top-end', # definindo a posição que o popver deve abrir na tela em relação ao botão
                                                    is_open = False, # definindo que o estado inicial do popover é fechado
                                                ),
                                            ]
                                        ),
                                        width = 2, # setando o numero de colunas que o elemento de ocupar
                                        align = 'right', # setando a posição que o elemento deve ficar
                                    ),
                                ], style = {'paddingLeft': '12%', 'paddingRight': '5%'}, # adicionando um espaçamento lateral
                                   justify='between', # definindo que as colunas que "sobram" devem ficar entre as colunas setadas
                            ),
                            dbc.Row(# Dropdown
                                dbc.Col(
                                        dcc.Dropdown(id = 'year-picker', # id do dropdown
                                                     value = 2020, # seta o valor inicial,
                                                     options = year_options, # as opções que vão aparecer no dropdown
                                                     clearable = False, # permite remover o valor (acho importante manter false para evitar problemas)
                                                     style = {'width': '50%'} # especifica que a largura do dropdown
                                                     ),
                                    ), style = {'paddingTop': "5px",'paddingLeft': '10%', 'paddingBottom': '10px'}                                    
                                ),
                            dbc.Row( # mapa + tabela
                                [
                                    dbc.Col( # mapa
                                        dcc.Graph(id = 'map-brazil'), # id do mapa
                                        width = 7, # numero de colunas que o mapa irá ocupar
                                        align = 'center', # posição do elemento dentro das colunas
                                        style = {'display': 'inline-block', 'paddingLeft': '2%', 'paddingRight': '2%'} # adicionando um espaçamento para não ficar tudo grudado
                                    ),
                                    dbc.Col( # Tabela
                                        html.Div(id = 'mapa-data-table'), # id da tabela do mapa
                                        width = 5, # número de colunas que a tabela irá ocupar
                                        align = 'center', # centralizando a tabela dentro das colunas
                                        style = {'display': 'inline-block', 'paddingLeft': '2%', 'paddingRight': '2%'} # adicionando um espaçamento para não ficar tudo grudado
                                    ),
                                ]
                            ),

                        ], style = {'paddingBottom': "30px",'border': '4px solid DarkBlue', 'backgroundColor': 'white'}
                    ),
                    html.Div( # Div para os dados das regiões
                        [
                            dbc.Row(# Titulo
                                dbc.Col(
                                    html.H2(id = 'title-regioes') # add uma id para o titulo
                                ), style = {'textAlign': 'center', 'paddingTop': '40px', 'paddingBottom': '20px'} # centralizando o texto e adicionando um espaçamento
                            ),
                            dbc.Row( # texto e popover
                                [
                                    dbc.Col( # texto
                                        html.Label("Escolha uma Região:"), # texto de aviso
                                        width = 3, # número de colunas para expandir
                                        align = 'left', # alinhamento
                                        style = {'display': 'inline-block'} # estilo
                                    ),
                                    dbc.Col( # popover
                                        html.Div(
                                            [
                                                dbc.Button("+ info", # texto do botão do popover
                                                        outline = True, # adiciona linha em torno do botão
                                                        id = "popovertarget-regiao", # id do botão
                                                        style = {'fontFamily': 'Garamond', }, # alterando o tipo de fonte do botão
                                                        className = 'mr-2', # adicionando uma classe de botão do bootstrap
                                                        color = "success", # alterando a cor do botão
                                                        size = "sm", # tamanho do botão
                                                ),
                                                dbc.Popover( # add o popover em si
                                                    [
                                                        dbc.PopoverHeader(id='popover-header-regiao'), # adicionando o ID do cabeçalho
                                                        dbc.PopoverBody( # corpo do popover
                                                            # utilizo o markdown para ter facilidade com o html
                                                            dcc.Markdown(id='popover-body-regiao', # adicionando um id ao markdown
                                                                         style={'textAlign': 'justify',} # deixando o texto do body justificado
                                                            ), style= {'overflow': 'auto', # adicionando barra de rolagem ao body
                                                                       'max-height': '500px'} # determinando a altura maxima do body
                                                        ),
                                                    ],
                                                    id ='popover-regiao', # id do popover
                                                    target = "popovertarget-regiao", # setando o botão de target
                                                    placement='top-end', # definindo a posição que o popover deve abrir na tela em relação ao botão
                                                    is_open = False, # definindo que o estado inicial do popover é fechado
                                                ),
                                            ]
                                        ),
                                        width = 2, # definindo o número de colunas que o popover deve ocupar
                                        align = 'right', # alimento do popover nas colunas
                                    ),
                                ], style = {'paddingLeft': '12%', 'paddingRight': '5%'}, # dando um pequeno espaçamento ao popover
                                   justify='between' # definindo que as colunas que sobram na linha devem ocupar o centro
                            ),
                            dbc.Row( # linha do dropdown
                                dbc.Col(
                                    dcc.Dropdown(id = 'regiao-picker', # id do dropdown
                                                 value = 'Norte', # seta o valor inicial,
                                                 options = regiao_options, # as opções que vão aparecer no dropdown
                                                 clearable = False, # permite remover o valor (acho importante manter false para evitar problemas)
                                                 style = {'width': '50%'}
                                    ),
                                ), style = {'paddingTop': "5px", 'paddingLeft': '10%',}
                            ),
                            dbc.Row( # linha para o gráfico de barras agrupado
                                dbc.Col(
                                    dcc.Graph(id = 'bar-grouped-regioes')
                                ),
                            ),
                            dbc.Row( # linha para o grafico de barras + tabela
                                [
                                    dbc.Col( # coluna para o gráfico de barras
                                        dcc.Graph(id='bar-regioes-total'), # id do grafico de barras para a região
                                        width = 7, # numero de colunas que o gráfico de ocupar
                                        align = 'center', # alinhamento dentro das colunas de width
                                        style = {'display': 'inline-block', 'paddingLeft': '2%', 'paddingRight': '2%'} # espaçamento
                                    ),
                                    dbc.Col( # coluna para a tabela
                                        html.Div(id='bar-regiao-data-table'), # id da tabela para a região
                                        width = 5, # numero de colunas que o gráfico de ocupar
                                        align = 'center', # alinhamento dentro das colunas de width
                                        style = {'display': 'inline-block', 'paddingRight': '4%', "paddingTop": "100px"} # espaçamento
                                    ),
                                ]
                            ),
                        ], style = {'paddingBottom': "30px",'border': '4px solid DarkBlue', 'backgroundColor': 'white'}
                    ),
                    html.Div( # Div para os dados das estados
                        [
                            # Titulo
                            dbc.Row(
                                dbc.Col(
                                    html.H2(id='title-states') # adicionando uma ID
                                ), style = {'textAlign': 'center', 'paddingTop': '40px', 'paddingBottom': '20px'} # aplicando estilo
                            ),
                            # texto simples + popover
                            dbc.Row(
                                [
                                    dbc.Col( # texto simples
                                        html.Label("Escolha um estado:"), # texto do texto simples
                                        width = 3, # número e coluna que o elemento deve ocupar
                                        align = 'left', # setando o alinhamento do texto
                                        style = {'display': 'inline-block'} # add estido
                                    ),
                                    dbc.Col( # coluna para o popover
                                        html.Div(
                                            [
                                                dbc.Button('+ info', # botão do popover
                                                           outline=True, # colocando contorno no botão
                                                           id = 'popovertarget-estado', # adicionando o id do botão
                                                           style= {'fontFamily': 'Garamond', }, # alterando a fonte do botão
                                                           className="mr-2", # aplicando um tipo de botão do bootstrap
                                                           color="success", # alterando a cor do botão
                                                           size="sm", # alterando o tamanho do botão
                                                          ),
                                                dbc.Popover( # elemento do popover
                                                    [
                                                        # cabeçalho
                                                        dbc.PopoverHeader(id='popover-header-estado'), # id do cabeçalho do popover
                                                        # corpo
                                                        dbc.PopoverBody(
                                                            # add um elemento de markdonw para as marcações funcionarem vindas do texto nodata frame
                                                            dcc.Markdown(
                                                                        id = 'popover-body-estado', # id do markdown
                                                                        style={'textAlign': 'justify',} # deixando o texto no markdown justificado
                                                                        ),
                                                            style= {'overflow': 'auto', 'max-height': '500px'} # deixando o body do popover com uma barra de rolamento e fixando um tamanho máximo
                                                        ),
                                                    ],
                                                    id ='popover-estado', # adicionando um id ao popover
                                                    target = "popovertarget-estado", # definindo o elemento target do popover (tem de ser a id do botão)
                                                    placement='top-end', # definindo a posição em que o popover vai popar
                                                    is_open = False, # definindo que o estado inicial do popover é fechado
                                                ),
                                            ]
                                        ),
                                        width = 2, # definindo o número de colunas que o popover deve expandir
                                        align = 'right' # definindo o seu alinhamento na linha
                                    ),
                                ], style = {'paddingLeft': '12%', 'paddingRight': '5%'}, # adicionando um espaçamento para a linha
                                    justify='between' # definindo que as colunas que "sobram" deve ser alocadas entre as colunas
                            ),
                            # linha para o dropdown
                            dbc.Row(
                                dbc.Col( # coluna unica para o dropdown
                                    dcc.Dropdown(id = 'state-picker', # id do dropdown
                                        value = 'Acre', # seta o valor inicial,
                                        options = state_options, # as opções que vão aparecer no dropdown
                                        clearable = False, # permite remover o valor (acho importante manter false para evitar problemas)
                                        style = {'width': '50%'} # setando que o tamanho do dropdown deve preencher metade do espaço disponível
                                    ),
                                ), style = {'paddingTop': "5px",'paddingLeft': '10%',} # dando um espaçamento entre as linhas
                            ),
                            # grafico de dispersão com linhas
                            dbc.Row(
                                dbc.Col( # coluna para o gra´fico de dispersão
                                    dcc.Graph(id='scatter-plot-states'), # adicionado ID para o gráfico de dispersão com linhas
                                    style = {'textAlign': 'center', 'paddingBottom': '60px'} # adicoinado um estilo para a coluna
                                ),
                            ),
                            # grafico de barras para os estados
                            dbc.Row(
                                dbc.Col(
                                    dcc.Graph(id='bar-plot-states'),
                                    style = {'textAlign': 'center'}
                                ),
                            ),
                        ], style = {'paddingBottom': "30px",'border': '4px solid DarkBlue', 'backgroundColor': 'white'}
                    ),
                    # footer
                    html.Div([
                        # primeira linha do footer, adicionando a referência dos dados
                        dbc.Row(
                            dbc.Col(
                                html.Label([
                                    "Fonte: ",
                                    html.A(
                                        'queimadas.dgi.inpe.br',
                                        href='http://queimadas.dgi.inpe.br/queimadas/portal-static/estatisticas_estados/'
                                    ),
                                ])
                            ),
                        ),
                        # segunda linha do footer, adicionado a data de acesso dos dados
                        dbc.Row(
                            dbc.Col(
                                html.Label("Acesso em 27/01/2021")
                            ),
                        ),
                        # terceira linha do footer, adicionando meu contato
                        dbc.Row(
                            dbc.Col(
                                html.Label("Desenvolvido por Anderson Canteli (andersonmdcanteli@gmail.com)")
                            ),
                        ),

                    ], style = {'textAlign': 'center',
                           'fontFamily' : "Roboto",
                           'paddingTop': "15px",
                           'color': 'white',
                           'backgroundColor': 'DarkBlue',
                           "paddingBottom": "20px",}
                    )


            ], style = {'paddingRight': "1.5%", 'paddingLeft': "1.5%", 'backgroundColor': 'DarkBlue'}
)

Eu aproveitei e fiz algumas alterações no Layout. Adicionei alguns espaçamentos entre as Divs, coloquei um fundo azul no Dashboard, fundo branco em algumas Divs e também criei um footer. Tirando o footer, que é uma composição de elementos html.Label(), todas as alterações foram de estilo e fica ao gosto de cliente. Particularmente, eu não sou nada bom com estilização, mas até que não ficou muito feio 😵.


  • As funções da aplicação

Agora adicionamos todas as funções que criamos para atualizar os elementos. A ordem delas não importa, mas elas não podem ter o mesmo nome.

### Funções

## Div do Modal
# Função para fechar o modal
@app.callback(Output('modal', 'is_open'),
              [Input('close-sm', 'n_clicks')],
              [State('modal', 'is_open')])
def close_modal(n, is_open):
    if n:
        return not is_open
    return is_open

## Div do mapa

# Função para atualizar a tabela do mapa quando o usuário alterar o dropdown
@app.callback(Output('mapa-data-table', 'children'),
            [Input('year-picker', 'value')])
def update_table_map(selected_year):
    df_ano = df[df['Ano'] == selected_year] # filtrando o data frame com o selected_year
    df_ano = df_ano.drop(['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro',
                'Dezembro', 'Ano'], axis=1) # removendo as colunas que não são utilizadas
    df_ano.sort_values(by='Total', inplace=True, ascending=False) # ordenando os dados
    df_ano.reset_index(inplace=True, drop=True) # resetando o indice
    df_ano['Rank'] = df_ano.index # criando uma nova coluna com o indice
    df_ano['Rank'] = df_ano['Rank'] + 1 # somando 1 a nova coluna para que o rank varie entre 1 e 27 ao invés de 0 e 26
    df_ano = df_ano[['Rank', 'Total', 'UF', 'Regiao']] # reordenando a posição das colunas

    return [
            dash_table.DataTable(
                columns=[{"name": i, "id": i} for i in df_ano.columns], # passando o nome das colunas com um id
                data=df_ano.to_dict('records'), # passando os dados
                fixed_rows={'headers': True}, # fixando o cabeçalho para que a barra de rolamento não esconda o cabeçalho
                style_table={'height': '400px', 'overflowY': 'auto'}, # adicionando uma barra de rolamento, e fixando o tamanho da tabela em 400px
                style_header={'textAlign': 'center'}, # centralizando o texto do cabeçalho
                style_cell={'textAlign': 'center', 'font-size': '14px'}, # centralizando o texto das céluas e alterando o tamanho da fonte
                style_as_list_view=True, # deixa a tabela sem bordas entre as colunas
                style_data_conditional=[ # este parametro altera a cor da célula quando o usuário clica na célula
                                        {
                                            "if": {"state": "selected"},
                                            "backgroundColor": "rgba(205, 205, 205, 0.3)",
                                            "border": "inherit !important",
                                        }
                                        ],
                                )
                ]


# Função para atualizar o mapa quando o usuario alterar o dropdown
@app.callback(Output('map-brazil','figure'),
             [Input('year-picker','value')])
def update_map_brazil(selected_year):

    df_ano = df[df['Ano'] == selected_year] # novo df com os dados de apenas 1 estado por vez
    # criando o mapa
    fig = px.choropleth_mapbox(
                                df_ano, # primeiro parâmetro é o dataframe com os dados
                                locations = 'UF', # coluna do DF que referencia as IDs do mapa
                                geojson = limites_brasil, # arquivo com os limites dos estados
                                color = 'Total', # indicando qual coluna será utilizada para pintar os estados
                                mapbox_style = "carto-positron", # estilo do mapa
                                center = {'lon':-55, 'lat':-14}, # definindo a posição inicial do mapa
                                zoom = 3, # definindo o zoom do mapa (número inteiro entre 0 e 20)
                                opacity = 1.0, # definindo uma opacidade para a cor do mapa
                                hover_name = "UF", # nome do hover
                                color_continuous_scale = 'reds', # muda a escala de cor
                                range_color = [0, df['Total'].max()], # limites do eixo Y

    )
    fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})

    return fig


# Alterando o estado do popover, de False para True, de True para false ao clicar
@app.callback(Output("popover-mapa", "is_open"),
            [Input('popovertarget-mapa',"n_clicks")],
            [State("popover-mapa", "is_open")])
def toggle_popover_mapa(n, is_open):
    if n:
        return not is_open
    return is_open

# Header para o popover do mapa
@app.callback(Output('popover-header-mapa', 'children'),
             [Input('year-picker', 'value')])
def update_pop_over_header_mapa(selected_year):
    return "Brasil em " + str(selected_year)

# Conteudo do corpo para o popover do mapa
@app.callback(Output('popover-body-mapa', 'children'),
             [Input('year-picker', 'value')])
def update_pop_over_body_mapa(selected_year):
    return df_texto_ano[df_texto_ano['Ano'] == selected_year]['Texto']

# Função para atualizar o titulo da Div do Mapa + tabela
@app.callback(Output('title-year', 'children'),
             [Input('year-picker', 'value')])
def update_mapa(selected_year):
    return "Total de focos de queimadas identificados por estado no ano de " + str(selected_year)




## Div para os dados separados por Região

# Tabela para o grafico de barras da regiao
@app.callback(Output('bar-regiao-data-table', 'children'),
            [Input('regiao-picker', 'value')])
def update_table_bar_regiao(selected_regiao):

    df_regiao = df[df['Regiao']== selected_regiao]
    df_regiao['UF'].unique() # primeira coluna com o nome dos estados
    df_regiao_tabela = pd.DataFrame(columns=['UF'])
    df_regiao_tabela['UF'] = df_regiao['UF'].unique() # primeira coluna com o nome dos estados
    # calculando o total por estado na região
    lista_total_por_estado = []
    lista_media_por_estado = []

    for state in df_regiao['UF'].unique():
        lista_total_por_estado.append(df_regiao[df_regiao['UF'] == state]['Total'].sum())
        lista_media_por_estado.append(df_regiao[df_regiao['UF'] == state]['Total'].mean())

    lista_media_estado = []
    lista_media_pais = []
    for i in range(len(lista_total_por_estado)):
        if i == 0:
            lista_media_estado.append(np.mean(lista_media_por_estado))
            lista_media_pais.append(df['Total'].mean())
        else:
            lista_media_estado.append(" ")
            lista_media_pais.append(" ")
    df_regiao_tabela['Total acumulado'] = lista_total_por_estado
    df_regiao_tabela['Média acumulada'] = lista_media_por_estado
    nome_aux = 'Média da Região ' + selected_regiao
    df_regiao_tabela[nome_aux] = lista_media_estado
    df_regiao_tabela['Média Brasil'] = lista_media_pais


    return [
            dash_table.DataTable(
                columns=[{"name": i, "id": i, 'type': 'numeric', 'format': Format(scheme=Scheme.fixed,
                            precision=1,
                            group=Group.yes,
                            groups=3,
                            group_delimiter='.',
                            decimal_delimiter=',')}
                            for i in df_regiao_tabela.columns],
                data=df_regiao_tabela.to_dict('records'),
                fixed_rows={'headers': True},
                style_table={'height': '400px', 'overflowY': 'auto'},
                style_header={'textAlign': 'center', 'whiteSpace':'normal', 'minWidth': '0px', 'maxWidth': '180px'},
                style_cell={'textAlign': 'center', 'font-size': '14px', },
                style_as_list_view=True,
                style_data_conditional=[
                                        {
                                            "if": {"state": "selected"},
                                            "backgroundColor": "rgba(205, 205, 205, 0.3)",
                                            "border": "inherit !important",
                                        }
                                        ],
                                )
                ]

# Grafico de barras com a soma de todos os estados da região
@app.callback(Output('bar-regioes-total','figure'),
             [Input('regiao-picker','value')])
def update_bar_regioes_total(selected_regiao):

    #Filtrando os dados para a região
    df_regiao = df[df['Regiao']== selected_regiao]
    total_regiao = [] # lista vazia para armazenar o valor somado para cada região
    lista_ano = df['Ano'].unique() # lista onde cada elemento é uma ano da serie historica
    lista_regiao = [] # lista vazia para armazernar o nome da região e facilitar para setar o hover

    for ano in lista_ano: # iterando o ano dentro da lista com os anos
        total_regiao.append(df_regiao[df_regiao['Ano'] == ano]['Total'].sum()) # filtrando o ano da lista_ano[i] contido no data frame df_regiao e somando todos os valores da coluna 'Total' do data frame auxiliar que contém apenas dados do lista_ano[i], e appendando esse valor a lista total_regiao
        lista_regiao.append(selected_regiao) # appendando o nome da região escolhida a cada iteração
    # Criando um novo data frame para plotar o gráfico com os valores totais da região selecionada com o plotly express
    df_total = pd.DataFrame({'Ano': lista_ano, # coluna com os anos da série
                             'Total de focos de queimadas por ano': total_regiao, # coluna com o total de focos na região
                             'Regiao': lista_regiao} # coluna com o nome da região
                           )
    fig = px.bar(df_total,
            x = 'Ano', # coluna para os dados do eixo x
            y = 'Total de focos de queimadas por ano', # coluna para os dados do eixo y
            hover_name = 'Regiao',

            )
    fig.update_layout(xaxis = dict(linecolor='rgba(0,0,0,1)', # adicionando linha em y = 0
                                    tickmode = 'array', # alterando o modo dos ticks
                                    tickvals = df_regiao['Ano'], # setando o valor do tick de x
                                    ticktext = df_regiao['Ano'], # setando o valor do tick de x
                                    tickangle = -45),
                     yaxis = dict(linecolor='rgba(0,0,0,1)', # adicionando linha em x = 0
                                  tickformat=False), # removendo a formatação no eixo y
                    title_text = 'Total de focos de queimadas identificados na região ' + selected_regiao,  # adicionando titulo ao gráfico
                    title_x = 0.5, # reposicionando o titulo para que ele fique ono centro do gráfico
                    margin={"r":0,"l":0,"b":0}, # resetando o tamanho das margens
                     )

    return fig



# Gráfico de barras agrupado para as regiões
@app.callback(Output('bar-grouped-regioes','figure'),
             [Input('regiao-picker','value')])
def update_bar_regioes(selected_regiao):
    # filtrando os dados para a região selecionada
    df_regiao = df[df['Regiao']== selected_regiao]

    fig = px.bar(df_regiao, # data frame com os dados
            x = 'Ano', # coluna para os dados do eixo x
            y = 'Total', # coluna para os dados do eixo y
            barmode = 'group', # setando que o gráfico é do tipo group
            color = 'UF', # setando a coluna que irá serparar as colunas dentro do grupo
            hover_name = 'UF', # coluna para setar o titulo do hover
            hover_data = {'UF': False}, # Removendo o Mes para que não fique repetido no título do hover e no conteúdo do hover
            )
    fig.update_layout(xaxis = dict(linecolor='rgba(0,0,0,1)', # adicionando linha em y = 0
                                    tickmode = 'array', # alterando o modo dos ticks
                                    tickvals = df_regiao['Ano'], # setando o valor do tick de x
                                    ticktext = df_regiao['Ano']), # setando o valor do tick de x
                     yaxis = dict(title = 'Total de focos de queimadas por ano',  # alterando o titulo do eixo y
                                  linecolor='rgba(0,0,0,1)', # adicionando linha em x = 0
                                  tickformat=False), # removendo a formatação no eixo y
                                  title_text = 'Total de focos de queimadas identificados por ano para cada estado na região ' + selected_regiao, # adicionando titulo ao gráfico
                                  title_x = 0.5, # reposicionando o titulo para que ele fique ono centro do gráfico
                     )
    return fig

# Botão do popover para os gráficos dseparados por Regiao
@app.callback(Output("popover-regiao", "is_open"),
            [Input('popovertarget-regiao',"n_clicks")],
            [State("popover-regiao", "is_open")])
def toggle_popover_regiao(n, is_open):
    if n:
        return not is_open
    return is_open

# Conteudo do body para o popover da regiao
@app.callback(Output('popover-body-regiao', 'children'),
             [Input('regiao-picker', 'value')])
def update_pop_over_body_regiao(selected_regiao):
    return df_texto_regiao[df_texto_regiao['Regiao'] == selected_regiao]['Texto']

# Header para o popover da regiao
@app.callback(Output('popover-header-regiao', 'children'),
             [Input('regiao-picker', 'value')])
def update_pop_over_header_regiao(selected_regiao):
    return "Região " + str(selected_regiao)

# Titulo da Div para as regiões
@app.callback(Output('title-regioes', 'children'),
             [Input('regiao-picker', 'value')])
def update_graficos_estado(selected_regiao):
    return "Focos de queimadas identificados na regiao: " + str(selected_regiao)




## Div para o dados separados por estado

# Gráfico de barras para um único estado
@app.callback(Output('bar-plot-states', 'figure'),
             [Input('state-picker', 'value')])
def update_bar_plot_states(selected_state):
    df_estado = df[df['UF']== selected_state] # Filtrando os dados para o estado
    fig = px.bar(df_estado, # data frame com os dados
            x = 'Ano', # coluna para os dados do eixo x
            y = 'Total', # coluna para os dados do eixo y
            hover_name = 'UF', # coluna para setar o titulo do hover
            )
    fig.update_layout(xaxis = dict(linecolor='rgba(0,0,0,1)', # adicionando linha em y = 0
                                    tickmode = 'array', # alterando o modo dos ticks
                                    tickvals = df_estado['Ano'], # setando o valor do tick de x
                                    ticktext = df_estado['Ano']), # setando o valor do tick de x
                     yaxis = dict(title = 'Total de focos de queimadas por ano',  # alterando o titulo do eixo y
                                  linecolor='rgba(0,0,0,1)', # adicionando linha em x = 0
                                  tickformat=False, # removendo a formatação no eixo y
                                  ),
                      title_text = 'Total de focos de queimadas identificados no estado ' + selected_state + ' por ano',  # adicionando titulo ao gráfico
                      title_x = 0.5, # reposicionando o titulo para que ele fique ono centro do gráfico
                     )
    return fig

# Gráfico de dispersão para um único estado
@app.callback(Output('scatter-plot-states', 'figure'),
             [Input('state-picker', 'value')])
def update_scatter_states(selected_state):
    df_estado = df[df['UF']== selected_state] # Filtrando os dados para o estado
    # criando um novo dataframe vazio para o gráfico de dispersão
    df_scatter = pd.DataFrame(columns=['Ano', 'Focos de queimadas', 'UF', 'Regiao', 'Mes'])
    # lista com o nome dos estados
    column_names = list(df_estado.columns)[1:13]
    for i in range(len(column_names)):
        df_aux = df_estado[['Ano', column_names[i], 'UF', 'Regiao']].copy() # criando uma copia do df_estado com as colunas necessárias para o gráfico de dispersão
        df_aux.rename(columns={column_names[i]: 'Focos de queimadas'}, inplace = True) # renomeando a coluna com nome "column_names[i] para "Focos de queimadas"
        nome_mes = [] # lista vazia
        for j in range(df_aux.shape[0]): # iterando ao longo de todos os anos para o mês
            nome_mes.append(column_names[i]) # appendando o nome do mês
        df_aux['Mes'] = nome_mes # adicionando a lista contendo o nome do mes a uma nova coluna "Mes" no df_aux
        df_scatter = pd.concat([df_scatter, df_aux]) # concatenando o df_scatter com o df_aux


    fig = px.scatter(
                df_scatter, # o data frame contendo os dados
                x = 'Mes', # a coluna para os dados de x
                y = 'Focos de queimadas', # a coluna para os dados de y
                color = 'Ano', # a coluna para diferenciar as séries com cores diferentes
                hover_name = 'Mes', # o nome que aparece ao passar o nome
                hover_data = {'Mes': False}, # Removendo o Mes para que não fique repetido no título do hover e no conteúdo do hover
                )
    fig.update_traces(mode='lines+markers') # Deixando o gráfico com linhas e marcadores (por default px.scatter é apenas dispersão e px.lines é apenas linhas)
    fig.update_layout(xaxis = dict(linecolor='rgba(0,0,0,1)', # adicionando linha em y = 0
                                   title = 'Mês', # Alterando o nome do eixo
                                ),
                 yaxis = dict(title = 'Focos de queimadas por mês',  # alterando o titulo do eixo y
                              linecolor='rgba(0,0,0,1)', # adicionando linha em x = 0
                              tickformat=False, # removendo a formatação no eixo y
                             ),
                 title_text = 'Focos de queimadas identificados no estado ' + selected_state + ' por mês.',  # adicionando titulo ao gráfico
                 title_x = 0.5, # reposicionando o titulo para que ele fique ono centro do gráfico
                 margin={"r":0,"l":0,"b":0}, # resetando as margens
                 )
    return fig

# Botao para o popover dos estados
@app.callback(Output("popover-estado", "is_open"),
            [Input('popovertarget-estado',"n_clicks")],
            [State("popover-estado", "is_open")])
def toggle_popover_estado(n, is_open):
    if n:
        return not is_open
    return is_open

# Conteudo do body para o popover do estado
@app.callback(Output('popover-body-estado', 'children'),
             [Input('state-picker', 'value')])
def update_pop_over_body_estado(selected_state):
    return df_texto_estados[df_texto_estados['Estado'] == selected_state]['Texto']


# Header para o popover do estado
@app.callback(Output('popover-header-estado', 'children'),
             [Input('state-picker', 'value')])
def update_pop_over_header_estado(selected_state):
    return str(selected_state)

# Titulo da Div para os estados
@app.callback(Output('title-states', 'children'),
             [Input('state-picker', 'value')])
def update_graficos_estado(selected_state):
    return "Focos de queimadas identificados no estado: " + str(selected_state)

Eu fiz algumas pequenas modificações nos gráficos. A mais importante delas foi alterar o tamanho das margens, mas novamente, é apenas mudança de estilo.


  • Gerar o servidor para rodar a aplicação

Agora falta apenas rodar a aplicação:

# Rodando a aplicação através de um servidor
if __name__ == '__main__':
    app.run_server()

E obtemos este dashboard:

Figura 1 - Dashboard finalizado.

Captura de página do dashboard finalizado.


  • Publicando em um servidor

Agora falta apenas publicar o dashboard em um servidor. Eu peguei este Dashboard e hospedei ele no Heroku, e funcionou corretamente. Eu segui os passos indicados pelo próprio Heroku para fazer o deployment. A dificuldade que apareceu foi relacionado as versões do NumPy na hora de criar o ambiente virtual, mas uma vez que isso foi solucionado, foi bem tranquilo. O problema com o NumPy provavelmente ocorreu devido a versão do Python que utilizei (3.7) não ser compatível com a versão 1.20 do NumPy (isso é um chute, ok?!). Instalando a versão 1.16, o deployment foi tranquilo. PS: o NumPy é necessário pois ele é uma dependência do Pandas.

Talvez você consiga acessar ele clicando em andersoncanteli.herokuapp.com, mas eu faço atualizações neste site as vezes (utilizo como teste mesmo), então provavelmente o link estará quebrado ou te levará a um outro projeto.

Um outro problema, que até o momento não consegui resolver é o favicon no Heroku. Por algum motivo desconhecido, a aplicação acabou ficando sem ícone atualizado.

De qualquer forma, o código completo (incluindo os outros arquivos necessários para o deployment, como o Procfile, requirments, etc) esta disponível abaixo e no meu git-hub.