Dashboards com Plotly Express - Parte 3

26 minute read

Olá 😀!

Vamos agora para a terceira parte desse artigo barra tutorial de dashboard utilizando plotly express e Dash. 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. Nesta parte, vou seguir utilizando os dados de focos de queimadas no Brasil, mas agora vou gerar gráficos para os estados individualmente!

Para deixar o código mais limpo eu vou adicionar apenas os elementos criados aqui para os estado. Sem mais delongas, vamos lá!

Introdução

Nesta parte vamos analisar os dados separados por estado brasileiro. Para isto, vou criar um gráfico de dispersão com linhas para avaliar o número de focos identificados por mês a cada ano, e um gráfico de barras para o número de focos totais identificados por ano.

A versão final ficou assim:

Figura 1 - Estado final do Dashboard desenvolvido neste post.

Print do dashboard com os elementos criados para comparar o número de focos de queimadas dentro de cada estado do Brasil.

Lembrando que a ideia aqui é aprender a utilizar os elementos do ploty express, e não avaliar os dados de focos de queimadas precisamente. Por exemplo, temos estados que tem mais de um bioma e isto não esta sendo levado em conta.

Gráfico de dispersão com linhas para comparar os meses

EU vou começar pelo gráfico de dispersão com linhas. Este gráfico vai ter no eixo x os meses do ano, e no eixo y o número de queimada por mês em todos os anos da série histórica. Mas para começar, precisamos importar os dados:

df = pd.read_csv('historico_estados_queimadas.csv', encoding='latin-1')

Agora é preciso filtrar os dados para um único estado, que eu vou utilizar como exemplo o Acre, mas no Dashboard vai ser a escolha do usuário através do dropdown:

estado = 'Acre' # variável para o nome do estado
df_estado = df[df['UF'] == estado] # novo df com os dados de apenas 1 estado por vez

Agora precisamos juntar todas as colunas referente aos meses em uma única coluna. Eu vou escolher criar um novo DataFrame com os dados de todos os meses. Esse DataFrame vai ter de ter colunas para o Ano, para o número de focos de queimadas por mês, a unidade federativa, a região e uma para o mês:

df_scatter = pd.DataFrame(columns=['Ano', 'Focos de queimadas', 'UF', 'Regiao', 'Mes']) # criando um novo dataframe vazio para o gráfico de dispersão

Este novo DataFrame df_scatter será preenchido com os dados que precisamos. Para preencher, precisamos dos dados que estão no df_estado, mas que é preciso filtrar e selecionar as colunas corretas. Eu vou criar um novo DataFrame (df_aux) com as colunas ‘Ano’, ‘Nome do mês’, ‘UF’ e ‘Regiao’, (onde ‘Nome do mês’ é Janeiro, Fevereiro, …, Dezembro) através de uma cópia do df_estado, substituir o nome da coluna ‘Nome do mes’ por ‘Foco de queimadas’, criar uma lista (com o mesmo número de linhas que o df_estado) onde todos os valores são o nome do mês, adicionar esta lista no df_aux em uma coluna chamada ‘Mes’ e finalmente concatenar o df_aux no df_scatter. Mas como é preciso fazer isto para todos os 12 meses, os passos acima devem se repetidos para cada mês, sendo que a cada iteração o df_aux é concatenado abaixo do mês seguinte.

Para facilitar no for loop, vou criar uma lista com o nome dos meses:

column_names = list(df_estado.columns)[1:13]

Agora crio a cópia do recorte do DataFrame df_estado para o primeiro mês:

i = 0 # setando a posição da lista column_names, para depois iterar ao longo do tamanho dessa lista
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

Agora renomeio a coluna com o nome do mês para “Focos de queimadas”:

df_aux.rename(columns={column_names[i]: 'Focos de queimadas'}, inplace = True) # renomeando a coluna com nome "column_names[i] para "Focos de queimadas"

Agora crio uma nova lista que vai conter em cada elemento apenas o nome do mês:

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

Agora basta colocar esta nova lista em uma nova coluna (chamada ‘Mes’) no DataFrame auxiliar:

df_aux['Mes'] = nome_mes # adicionando a lista contendo o nome do mes a uma nova coluna "Mes" no df_aux

E então concatenar o df_aux no df_scatter:

df_scatter = pd.concat([df_scatter, df_aux]) # concatenando o df_scatter com o df_aux

Agora basta colocar todas as linhas acima dentro de um loop for e iterar ao longo dos 12 meses:

df_scatter = pd.DataFrame(columns=['Ano', 'Focos de queimadas', 'UF', 'Regiao', 'Mes']) # criando um novo dataframe vazio para o gráfico de dispersão
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
df_scatter.head(10)   

Figura 2 - DataFrame contendo os dados ajustados para todos os meses.

print do output gerado ao apresentar o DataFrame com os dados ajustados para todos os meses fiquem em uma única coluna

Agora vem a parte muito fácil do plotly express: gerar o gráfico interativo. Basta criar uma figura do tipo px.scatter, passar como primeiro parâmetro o DataFrame df_scatter, e então atribuir aos eixos as respectivas colunas do DataFrame. Para diferenciar as séries (cada ano), passamos o parâmetro color com a coluna que desejamos separar, que neste caso é a coluna ‘Ano’. Podemos dar o nome para o hover (neste caso, cada mes), e ainda podemos definir o que será apresentado ao passar o mouse por cima do dado, através do hover_data.

Mas para gerar o gráfico de dispersão com linhas é necessário atualizar os traços para o modo ‘lines+markers’, pois o plotly express tem ou um gráfico de dispersão (px.scatter) ou um gráfico de linhas (px.lines).

Vou aproveitar e adicionar linhas pretas no eixos, alterar formato dos ticks, adicionar um titulo, dentre outras formatações que já utilizamos anteriormente. Estas atualizações de estilo deve ser feitas através do fig.update_layout().

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 do Acre por mês.',  # adicionando titulo ao gráfico
              title_x = 0.5, # reposicionando o titulo para que ele fique ono centro do gráfico
              )
pyo.offline.plot(fig, filename = "scatter-plot.html")

Figura 3 - Gráfico de dispersão com linhas para o número de focos de queimadas no estado do Acre ao longo dos meses entre os anos de 1998 e 2020.

print do gráfico interativo obtido para o número de focos de queimadas identificado ao longo dos meses, separado por anos, para o estado do Acre.

Você pode ver o gráfico interativo clicando aqui.

A parte complicada de desenhar este gráfico foi adaptar o DataFrame para poder utilizar no plotly express, mas desenhar o gráfico é realmente muito simples.

Para gerar o mesmo gráfico para os outros estados, bastaria trocar o filtro utilizado para criar o DataFrame df_estado.

Gráfico de barras para comparar os anos

Desenhar o gráfico de barras com o plotly express também é muito simples. Basta criar uma figura do tipo gráfico de barras, passar o DataFrame como primeiro argumento, o nome da coluna do DataFrame que será utilizado para o eixo x, o nome da coluna do DataFrame que será utilizada para o eixo y e o nome da coluna para ser o título do hover. E como eu vou desenhar o gráfico de barras para comparar o total de focos de queimadas por ano em um estado, posso utilizar o df_estado sem fazer nenhuma alteração.

Também vou aplicar alteração de estilo como fiz para o gráfico de dispersão com linhas, utilizando novamente o fig.update_layout().

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 do Acre por ano',  # adicionando titulo ao gráfico
                  title_x = 0.5, # reposicionando o titulo para que ele fique ono centro do gráfico
                 )
pyo.offline.plot(fig, filename = "bar-plot.html")

Figura 4 - Gráfico de barras para o total de focos de queimadas identificados no estado do Acre entre 1998 e 2020.

print do gráfico de barras interativo para o total de focos de queimadas identificados no estado do Acre entre 1998 e 2020.

Você pode ver o gráfico interativo clicando aqui.

E com isso finalizo os gráficos para um único estado, e podemos gerar o dashboard.

Layout

O layout para este dashboard é muito semelhante aos anteriores, com a principal diferença de que ele não tem uma tabela. Então ele é composto de uma linha para o título (que é atualizado com um dropdown), uma linha com um texto fixo e um popover (que é atulizado com o mesmo dropdown), uma linha com um dropdown, uma linha com o gráfico de dispersão com linhas e uma linha com um gráfico de barras.

O layout base fica desta forma (omitindo os elementos feitos na Parte 1 e Parte 2.):

app.layout = html.Div([ # Div geral
                    html.Div(), # Div para o Modal (otimido)
                    html.Div(),# Div para o Titulo Geral (omitido)
                    html.Div(), # Div para a primeira parte (omitido)
                    html.Div(), # Div para a segunda parte (omitido),
                    html.Div( # Div para os dados das estados
                        [
                            dbc.Row(), # Titulo
                            dbc.Row(# Texto + Popover
                                [
                                    dbc.Col(), # Texto
                                    dbc.Col(
                                        html.Div(
                                            [
                                                dbc.Button(), # botão do popover
                                                dbc.Popover( # o popover
                                                    [
                                                        dbc.PopoverHeader(), # O cabeçalho do popover
                                                        dbc.PopoverBody( # o corpo do popover
                                                            dcc.Markdown() # O markdown para facilitar o html
                                                        )
                                                    ]
                                                )
                                            ]
                                        )
                                    ),
                                ]
                            ),
                            dbc.Row(), # Dropdown
                            dbc.Row(), # Grafico de dispersão com linhas
                            dbc.Row(), # Grafico de barras
                        ]
                    ),
])

Título

Vamos começar pelo título. O título (que na verdade é um subtítulo) é gerado com um elemento html.H2()m que vai receber apenas um id='title-states', que será utilizado por uma função para atualizar o nome do estado. Também vou utilizar um estilo para centralizar o texto e um espaçamento.

app.layout = html.Div([ # Div geral
                    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
                            ),
                        ]
                    ),
])

Agora é preciso escrever a função que atualiza este título. Esta função vai receber o valor setado no dropdown (que ainda não foi feito, mas terá uma id='state-picker'), e retornar um texto concatenado com o nome do estado. O retorno vai ter como output o elemento html.H2() com id='title-states'.

# 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)

Texto simples e Popover

Agora vamos para a próxima linha, onde temos dois elementos: um texto simples e o popover. O texto simples é adicionado através de um elemento html.Label(), onde podemos passar o texto diretamente neste elemento ('children'). Também precisamos setar o número de colunas que o elemento deve expandir (width=3), e algumas opções de edição do elemento.

O popover deve ser inserido em uma html.Div(), que vai ter um botão e o popover em si. O elemento de botão (dbc.Button()) deve ter uma id que será utilizada para controlar o estado (True or False</code) do popover. O elemento dbc.Popover() deve ter uma id (que vai ser utilizado em uma função) e um target (que liga o popover ao botão, e por isso, o target deve receber a id do botão) e também o parâmetro target, que liga o popover ao botão (deve receber o id do botão). O popover é composto de dois elementos: o cabeçalho (dbc.PopoverHeader()), que como vai ser atualizado sempre que o usuário alterar o estado, precisa receber apenas uma id; e um corpo (dbc.PopoverBody()), que vai receber um elemento de Markdown (dcc.Markdown()), pois o texto que será adicionado no corpo do popover vai ter marcação. Esse elemento precisa de um id.

A coluna do popover também deve ter definido um tamanho para expandir (width=2), e podemos adicionar estilos. Também podemos adicionar estilos a linha, e determinar que as colunas que “sobram” devem ser alocadas entre as duas colunas da linha.

app.layout = html.Div([ # Div geral
...
                            # 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='bottom-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
                            ),
...
])

Agora vamos as funções relacionadas ao popover, começando pelo seu cabeçalho. A função que vai atualizar o texto do cabeçalho deve receber como input o valor setado no dropdown (que ainda não criei, mas que vai ter id='state-picker'), e retornar o nome do estado setado para o elemento de cabeçalho do popover (dbc.PopoverHeader()) que tem id='popover-header-estado'.

# 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)

Agora vamos adicionar a função para o corpo do popover. A função que vai atualizar o texto do corpo do popover vai receber como input o valor setado no dropdown (que ainda não criei, mas que vai ter id='state-picker'), e retornar o texto filtrado de um DataFrame que contém informações sobre a geografia do estado. Esse DataFrame (que ainda não importei) tem duas colunas: uma (‘Estado’) contém o nome de cada um dos estados brasileiros; a segunda (‘Texto’) contém um resumo da geografia do estado. Esse resumo contém marcação para que seja interpretado pelo Markdown (por exemplo, dois espaços em branco ao final da linha para indicar que é uma quebra de linha).

Figura 5 - Captura de tela do arquivo csv contendo informações sobre a geografia de cada estado.

print do arquivo csv contendo informações sobre a geografia de cada estado.

Você pode fazer o download deste arquivo clicando aqui.

Mas para poder utilizar este arquivo, é preciso importa-lo antes:

# importando o resumo dos geograficos dos estados
df_texto_estados = pd.read_csv('info-estados.csv', encoding='latin-1', sep=";")

E a função fica desta forma:

# 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']

Agora é preciso adicionar a função para o botão. Essa função vai receber como input o número de clicks (n_clicks) do botão com id='popovertarget-estado', e utilizar o número de clicks para alterar o parâmetro is_open entre False (popover fechado) e True (popover aberto). Para funcionar, a função deve retornar o valor de is_open como output para o popover de id='popover-estado', que tem como target o botão com id='popovertarget-estado'. Essa função precisa ter um Stete() com os mesmos parâmetros que o Output().

# 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

Dropdown

Com isso finalizamos esta linha, e partimos para a próxima que irá conter apenas o dropdown. O elemento dcc.Dropdown() deve ter pelo menos um id='state-picker', um valor inicial (value) e uma lista com os valores que o usuário pode escolher (options). Esta lista pode ser facilmente obtida a partir do DataFrame df:

state_options = []
for state in df['UF'].unique():
    state_options.append({'label':state, 'value':state})

E o layout fica desta forma:

app.layout = html.Div([ # Div geral
...
                            # 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
                            ),
...
])

Gráfico de dispersão com linhas

Agora falta apenas adicionar os gráficos, começando pelo gráfico de dispersão com linhas. Como é apenas um gráfico na linha toda, precisamos apenas uma linha, uma coluna, e então um elemento de gráfico (dcc.Graph()) com um id que será utilizado na função de callback.

app.layout = html.Div([ # Div geral
...
                            # 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
                                ),
                            ),
...
])

A função para este gráfico deve receber como input o valor setado no dropdown que tem id='state-picker', gerar o gráfico de dispersão com linhas baseado neste valor, e retornar a figura para o output com id='scatter-plot-states'. O script para desenhar o gráfico é o mesmo que fizemos aqui, com atenção para o título do gráfico que agora está dinâmico.

# 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
                     )
    return fig

Figura 6 - Captura de tela do Dashboard com o gráfico de dispersão com linhas.

print do Dashboard com o gráfico de dispersão com linhas.

Gráfico de barras

Agora falta apenas o gráfico de barras. O layout dele é exatamente igual ao layout do gráfico de dispersão:

app.layout = html.Div([ # Div geral
...
                            # grafico de barras para os estados
                            dbc.Row(
                                dbc.Col(
                                    dcc.Graph(id='bar-plot-states'),
                                    style = {'textAlign': 'center'}
                                ),
                            ),
...
])

Agora falta apenas a função, que é semelhante a anterior. A função deve receber o valor que está no dropdown com id='state-picker', utilizar este valor para gerar o gráfico de barras, e retornar para o output com id='bar-plot-states' a figura gerada. O script é o mesmo que utilizei para desenhar o gráfico de barras anteriormente, apenas alterei o título do gráfico para que fique dinâmico:

# 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

Figura 7 - Captura de tela do Dashboard com o gráfico de dispersão com linhas e o gráfico de barras.

print do Dashboard com o gráfico de dispersão com linhas e o gráfico de colunas.

E com isso finalizo este dashboard. Agora falta apenas juntar os três dashboards em um único, o que é bem simples pois cada dashboard foi inserido em um html.Div, e vai ser basicamente copiar e color. Mas isto fica para a última parte.

Script finalizado

O script desta etapa ficou assim:

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
import plotly.offline as pyo
from dash_table.Format import Format, Group, Scheme, Symbol
import plotly.graph_objects as go

# importando os dados
df = pd.read_csv('historico_estados_queimadas.csv', encoding='latin-1')
# importando o resumo dos geograficos dos estados
df_texto_estados = pd.read_csv('info-estados.csv', encoding='latin-1', sep=";")
# 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})

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.themes.GRID]) # Criando a instancia da aplicação
app.layout = html.Div([ # Div geral
                    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='bottom-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'}
                                ),
                            ),
                        ]
                    ),
])

# 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
                     )
    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)


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