Pular para conteúdo

Criação de Painéis - Consumidor de Dash

Objetivos

O objetivo deste tutorial é criar um consumidor de dashboards, com filtros para que o usuário gere diferentes respostas baseadas nesses filtros.

Índice

O que é um consumidor de dashboards?

O consumidor de dashboards é uma interface integrada diretamente ao portal, ela permite a exibição de dados de diferentes naturezas conforme o usuário seleciona filtros na interface, oque permite vizualizar diferentes resultados de forma dinâmica.

O que é possível criar com um consumidor de dashboards?

Diferente do consumidor de perguntas, o consumidor de dashboards fornece respostas visuais para os filtros aplicados de forma dinâmica, por meio de uma interface gráfica. Aqui vão algumas demonstrações do potencial da ferramenta:

Filtros com dropdown:

alt text

Filtros com slider:

alt text

Filtros com checkbox:

alt text

Tabelas:

alt text

Mapas Cloropléticos:

alt text

Além disso, é possível inserir qualquer gráfico visto no tutorial anterior.

Preparando o consumer dash basic

Atenção: Para que que a aplicação funcione, todos arquivos devem estar na pasta geoprocess, criada no primeiro tutorial!

Para este tutorial iremos realizar uma cópia do módulo pgst-consumer-dash-basic. Primeiramente, copie o projeto pgst-consumer-basic para um novo projeto chamado consumer-dash-tutorial, utilizando o comando abaixo:

cp -r pgst-consumer-dash-basic consumer-dash-tutorial

Em seguida, vá até dashboards.json e faça as seguintes alterações:

Atenção: Substitua a parte abaixo e mantenha o restante, não exclua nenhuma linha.

{
    "projects": [
        {
            "consumer_project_id": "TUTORIAL",
            "version": "1.0.0",
            "title": "Projetos Tutoriais",
            "role_tag": "TUTORIAL"
        }
    ],
    "dashboards": [
        {
            "id": "DASHT",
            "version": 1.0,
            "consumer_project_id": "TUTORIAL",
            "role_tag": "DASHT",

Após copiar a pasta, vá até geoprocess-install/dev_consumers/configs/portal.ini na linha 46 faça as seguintes alterações:

DEVELOPMENT_PERMISSIONS=*

Como você já fez o tutorial 1 um conjunto de recursos já foram instalados no docker. Em seguida abra o terminal e acesse:

cd geoprocess-install

No terminal faça as seguintes operações:

source venv/bin/activate
cd dev_consumers
docker compose up -d portal --build

Atenção: Verifique se a instalação ocorreu de forma desejada antes de processeguir no tutorial, antes de executar os próximos passos o docker (gerado pelo dev_consumers) todas as aplicações devem estar executando.

Abra um terminal na pasta consumer-dash-tutorial, copie o arquivo de configuração do ambiente:

cp conf_samples/env.conf env.conf

Abra o arquivo env.conf e atualize o nome do consumidor (propriedade CONSUMER_CODE) para o nome tutorial3. Verique se está igual após a linha 9:

MESSAGE_BROKER_HOST=localhost
MESSAGE_BROKER_PORT=5672
MESSAGE_BROKER_USER=admin
MESSAGE_BROKER_PASSWORD=password
CACHE_HOST=localhost
CACHE_PORT=6379

Instale o ambiente virtual para isolar as dependências do Python. Utilize o comando abaixo:

python3 -m venv venv

Ative o ambiente virtual no seu computador utilizando o comando abaixo:

source venv/bin/activate

Instale as dependências do projeto.

pip3 install -r requirements.txt

Instale as dependências do projeto pgst-lib dentro do projeto consumidor.

pip3 install -e ../pgst-lib/ 

Para executar a aplicação, abra um terminal na pasta e digite:

cd consumer_dash
python3 manage.py dash_service

Abra a aplicação através da URL: http://localhost:8000/.

A seguinte tela deve ser exibida:

alt text

Atenção: Se não mostrar a tela acima, no docker pare a aplicação dc_portal e dc_gateway.

Criando um consumidor de dash

Faça o download do arquivo dados.csv localizado na pasta docs e crie uma pasta chamada data no raiz do projeto, insira o arquivo nesta pasta.

O arquivo dados.csvcontém dados sobre a covid-19 por região e ano. Vamos criar uma seção introdutória e em seguida criaremos gráficos.

Para realizar alterações no consumer iremos trabalhar com dois arquivos dashboards.json e consumer_dash/basic/service.py. O arquivo dashboards.json define as entradas e saídas do consumer além de definir a organização da tela e os valores dos filtros. Já service.py configura os dados e gráficos que serão exibidos.

Em dashboards.jsonsubstitua todo conteúdo do código pelo seguinte trecho:

{
  "projects": [
    {
      "consumer_project_id": "TUTORIAL",
      "version": "1.0.0",
      "title": "Projeto Dashboard Tutorial",
      "role_tag": "TUTORIAL"
    }
  ],
  "dashboards": [
    {
      "id": "DASHT",
      "version": 1.0,
      "consumer_project_id": "TUTORIAL",
      "role_tag": "DASHT",
      "title": "Dashboard covid-19",
      "description": "Painel didático com dados sobre a covid-19",
      "components": [
        {
          "type": "div",
          "components": [
            {
              "type": "markdown",
              "id": "markdown_1"
            }
          ]
        }
      ],
      "callbacks": [
        {
          "outputs": [
            "markdown_1"
          ]
        }
      ]
    }
  ]
}

Agora vá até consumer_dash/basic/service.py e substitua todo código pelo seguinte trecho:

from baseconsumer.dash_worker import DashWorker, dashProcess
from loguru import logger
import pandas as pd
import requests
import plotly.express as px

class Service(DashWorker):

    @dashProcess(componentId="markdown_1")
    def process1(self, input):
        logger.info("Processando markdown_1")
        answer = f'''
                #### Sobre:

                Este painel utiliza dados fictícios sobre a covid-19 para fins didáticos.
                ___
            '''
        return answer

Para executar em modo teste de contrato inclua o trecho em tests/test_001:

from loguru import logger
from consumer_dash.basic.service import Service

class Test001():

  service = Service()

  def test(self):
    logger.info("Testando o método test do módulo test_001.py")
    service = Service()
    answer = service.test("markdown_1", [])
    assert type(answer) == str

Abra o terminal e execute:

python3 main.py CONTRACT_TEST 001
Para visualizar os resultados digite (executar modo DASH_DEBUG):

python3 main.py DASH_DEBUG

Acesse a URL [http://0.0.0.0:8050/]

Em dashboards.json incremente o número das versões de DASHT e reinicie o container dc_portal no docker.

Execute o comando no terminal:

python3 manage.py dash_service

Acesse a URL abaixo para visualizar o resultado:

http://localhost:8000/

Resultado esperado:

alt text

Cada div possui um tipo, nesta div criamos um markdown, cada componente deve receber uma id que será chamado na seção callback. Em service.py definimos qual será o conteúdo do markdown através da variável answer, que retorna o conteúdo no portal.

Agora, iremos inserir um mapa cloroplético que relaciona dados da covid com um filtro selecionado.

Em dashboards.json, insira o contéudo a partir da linha 27:

,
        {
          "type": "div",
          "style": {
            "width": "49%",
            "display": "inline-block",
            "marginRight": "10px"
          },
          "components": [
            {
              "type": "dropdown",
              "title": "Selecione um ano:",
              "id": "data_1",
              "properties": {
                "options": [
                  {
                    "label": "2025",
                    "value": 2025
                  },
                  {
                    "label": "2024",
                    "value": 2024
                  },
                  {
                    "label": "2023",
                    "value": 2023
                  },
                  {
                    "label": "2022",
                    "value": 2022
                  },
                  {
                    "label": "2021",
                    "value": 2021
                  },
                  {
                    "label": "2020",
                    "value": 2020
                  }
                ],
                "default": 2020
              }
            },
            {
              "type": "graph",
              "id": "graph_1"
            }
          ]
        }
Em callbacks, na linha 82 insira o seguinte trecho:

,
        {
          "outputs": [
            "graph_1"
          ],
          "inputs": [
            "data_1"
          ]
        }

Agora em service.py insira o trecho após o fim de process1:

      @dashProcess(componentId="graph_1")
      def process2(self, input):

        logger.info("Processando graph_1",input)

        # Ano selecionado no dropdown
        if input[0] is None:
            ano_selecionado = 2020
        else:
            ano_selecionado = int(input[0])

        # Ler CSV
        path = "../data/dados.csv"

        if not os.path.exists(path):
                    path = "data/dados.csv"

        df = pd.read_csv(path)
        df_filtrado = df[df["ano"] == ano_selecionado]

        # Carregar GeoJSON dos estados do Brasil
        url = "https://raw.githubusercontent.com/codeforamerica/click_that_hood/master/public/data/brazil-states.geojson"
        geojson = requests.get(url).json()

        # Construir gráfico no formato dict
        answer = dict({
            "data": [
                {
                    "type": "choropleth",
                    "geojson": geojson,
                    "locations": df_filtrado["estado"].tolist(),
                    "z": df_filtrado["casos covid-19"].tolist(),
                    "featureidkey": "properties.name",  # precisa bater com o GeoJSON
                    "colorscale": "rdylgn_r",
                    "colorbar": {"title": "Casos"},
                    "hovertext": df_filtrado["estado"].tolist(),
                    "hoverinfo": "location+z+text"
                }
            ],
            "layout": {
                "title": f"Casos de Covid-19 no Brasil - {ano_selecionado}",
                "geo": {
                    "fitbounds": "locations",
                    "visible": False
                },
                "plot_bgcolor": "#F5F6F9",
                "paper_bgcolor": "#F5F6F9"
            },
            "frames": []
        })

        return answer    

No início de service.pydigite:

import os

Para executar em modo teste de contrato inclua o trecho em tests/test_002:

from loguru import logger
from consumer_dash.basic.service import Service

class Test002():

  service = Service()

  def test(self):
    logger.info("Testando o método test do módulo test_002.py")
    service = Service()
    answer = service.test("graph_1", ["2021"])
    assert type(answer) == dict

Abra o terminal e execute:

python3 main.py CONTRACT_TEST 002

Para visualizar os resultados digite (executar modo DASH_DEBUG):

python3 main.py DASH_DEBUG

Acesse a URL [http://0.0.0.0:8050/]

Em dashboards.json, incremente o número das versões de DASHT e reinicie o container dc_portal no docker.

Execute o comando no terminal:

python3 manage.py dash_service

Acesse a URL abaixo para visualizar o resultado:

http://localhost:8000/

Resultado esperado:

alt text

Em dashboards.json, definimos uma nova div para inserir o mapa cloroplético, dentro dessa div definimos dois componentes: um dropdown de ano com suas opções e um gráfico, em callbacks definimos um entrada data_1 e uma saída graph_1. Agora em service.pydefinimos o tipo de gŕafico a ser retornado, os parâmetros a serem inseridos em answer variam conforme o tipo de gráfico selecionado, a resposta é passada em formato de dicionário.

Agora, iremos inserir um gráfico de bolhas que possui região e ano como parâmetros e em seguida relaciona o número de casos, óbitos e taxa de vacinação para cada estado da região no ano escolhido.

Vá até dashboards.json e insira o seguinte trecho na linha 75:

,
        {
          "type": "div",
          "style": {
            "width": "49%",
            "display": "inline-block"
          },
          "components": [
            {
              "type": "dropdown",
              "title": "Selecione um ano:",
              "id": "data_2",
              "properties": {
                "options": [
                  {
                    "label": "2025",
                    "value": 2025
                  },
                  {
                    "label": "2024",
                    "value": 2024
                  },
                  {
                    "label": "2023",
                    "value": 2023
                  },
                  {
                    "label": "2022",
                    "value": 2022
                  },
                  {
                    "label": "2021",
                    "value": 2021
                  },
                  {
                    "label": "2020",
                    "value": 2020
                  }
                ],
                "default": 2020
              }
            },
            {
              "type": "dropdown",
              "title": "Selecione uma região:",
              "id": "data_3",
              "properties": {
                "options": [
                  {
                    "label": "Norte",
                    "value": "Norte"
                  },
                  {
                    "label": "Nordeste",
                    "value": "Nordeste"
                  },
                  {
                    "label": "Centro-Oeste",
                    "value": "Centro-Oeste"
                  },
                  {
                    "label": "Sudeste",
                    "value": "Sudeste"
                  },
                  {
                    "label": "Sul",
                    "value": "Sul"
                  }
                ],
                "default": "Nordeste"
              }
            },
            {
              "type": "graph",
              "id": "graph_2"
            }
          ]
        }

Em callbacks, insira o conteúdo na linha 167:

,
        {
          "outputs": [
            "graph_2"
          ],
          "inputs": [
            "data_2",
            "data_3"
          ]
        }

Agora em service.py insira o seguinte conteúdo após process2:

    @dashProcess(componentId="graph_2")
    def process3(self, input):
        logger.info("Processando graph_2", input)

        # Input esperado: [região, ano]
        if input[0] is None:
            ano = 2020
        else:
            ano = int(input[0])

        if input[1] is None:
            regiao = "Nordeste"
        else:
            regiao = input[1]

        path = "../data/dados.csv"

        if not os.path.exists(path):
                    path = "data/dados.csv"

        df = pd.read_csv(path)

        # Filtrar por ano e região
        df["casos_100k"] = df["casos covid-19"] / (df["população"] / 100000)
        df["obitos_100k"] = df["óbitos covid"] / (df["população"] / 100000)

        df_filtro = df[df["ano"] == ano]
        df_filtro = df_filtro[df_filtro["regiao"] == regiao] 

        data = []
        # Criar uma bolha para cada estado
        for _, row in df_filtro.iterrows():
            data.append({
                "type": "scatter",
                "mode": "markers",
                "x": [row["casos_100k"]],
                "y": [row["obitos_100k"]],
                "text": row["estado"],
                "marker": {
                    "size": [row["taxa vacina"]],
                    "sizemode": "area",
                    "sizeref": 2. * df_filtro["taxa vacina"].max() / (40. ** 2),
                    "sizemin": 4,
                    "opacity": 0.7
                },
                "name": row["estado"],  # cor diferente por estado
                "hovertemplate": (
                    f"<b>{row['estado']}</b><br>"
                    f"Ano: {ano}<br>"
                    f"Vacinação: {row['taxa vacina']}%<br>"
                    f"Casos/100k: {row['casos_100k']:.1f}<br>"
                    f"Óbitos/100k: {row['obitos_100k']:.1f}<extra></extra>"
                )
            })

        # Layout
        answer = {
            "data": data,
            "layout": {
                "title": f"Região {regiao} - Ano {ano} (Bolhas por estado)",
                "xaxis": {"title": "Casos por 100k"},
                "yaxis": {"title": "Óbitos por 100k"},
                "autosize": True,
                "legend": {"title": "Estado"}
            },
            "frames": []
        }

        return answer

Para executar em modo teste de contrato inclua o trecho em tests/test_003:

from loguru import logger
from consumer_dash.basic.service import Service

class Test003():

  service = Service()

  def test(self):
    logger.info("Testando o método test do módulo test_003.py")
    service = Service()
    answer = service.test("graph_2", ["2020", "Nordeste"])
    assert type(answer) == dict

Abra o terminal e execute:

python3 main.py CONTRACT_TEST 003

Para visualizar os resultados digite (executar modo DASH_DEBUG):

python3 main.py DASH_DEBUG

Acesse a URL [http://0.0.0.0:8050/]

Em dashboards.json, incremente o número das versões de DASHT e reinicie o container dc_portal no docker.

Execute o comando no terminal:

python3 manage.py dash_service

Acesse a URL abaixo para visualizar o resultado:

http://localhost:8000/

Resultado esperado:

alt text

Em todos gráficos, realizamos uma filtragem na base de dados com base no input passado, input é uma lista que possuirá como tamanho o número de parâmetros passados como input no callback. É necessário realizar um tratamento condicional selecionando um valor padrão caso o usuário não escolha nenhuma opção a fim de evitar erros na exibição.

Agora, iremos inserir um gráfico de barras que realiza a filtragem dos dados conforme dois parâmetros: Número de casos ou número de óbitos e exibe para cada ano, o total de casos ou óbitos e mostra através de uma escala de cores a taxa de vacinação daquele ano.

Em dashboards.json, insira o seguinte trecho na linha 152:

,
        {
          "type": "div",
          "style": {
            "width": "100%",
            "display": "inline-block"
          },
          "components": [
            {
              "type": "dropdown",
              "title": "Selecione um parâmetro:",
              "id": "data_4",
              "properties": {
                "options": [
                  {
                    "label": "Casos",
                    "value": "Casos"
                  },
                  {
                    "label": "Óbitos",
                    "value": "Óbitos"
                  }
                ],
                "default": "Casos"
              }
            },
            {
              "type": "graph",
              "id": "graph_3"
            }
          ]
        }

Em callbacks, na linha 207 insira o seguinte trecho:

,
        {
          "outputs": [
            "graph_3"
          ],
          "inputs": [
            "data_4"
          ]
        }

Vá até service.pye insira o contéudo após process3:

    @dashProcess(componentId="graph_3")
    def process4(self, input):
        logger.info("Processando graph_3", input)

        # input esperado: ["casos"] ou ["obitos"]
        if input[0] is None:
            metrica = "casos"
        else:
            metrica = input[0].lower()

        path = "../data/dados.csv"

        if not os.path.exists(path):
                    path = "data/dados.csv"

        df = pd.read_csv(path)

        # Seleciona a métrica (y do gráfico)
        if metrica == "casos":
            ycol = "casos covid-19"
        else:
            ycol = "óbitos covid"

        # Agrupamento: soma casos/óbitos, média da taxa de vacina
        df_agg = (
            df.groupby("ano")
            .agg({
                "casos covid-19": "sum",
                "óbitos covid": "sum",
                "taxa vacina": "mean"
            })
            .reset_index()
        )

        answer = {
            "data": [
                {
                    "type": "bar",
                    "x": df_agg['ano'].tolist(),
                    "y": df_agg[ycol].tolist(),
                    "marker": {
                        "color": df_agg['taxa vacina'].tolist(),
                        "colorscale": "Viridis",
                        "colorbar": {
                            "title": {
                                "text": "Taxa Vacina"
                            }
                        }
                    },
                    "hovertemplate": "Ano=%{x}<br>Casos de COVID=%{y}<br>Taxa de vacinação=%{marker.color:.2f}%<extra></extra>"

                }
            ],
            "layout": {
                "height": 400,
                "title": {
                    "text": f"{ycol.capitalize()} por ano (colorido pela taxa de vacinação)"
                },
                "xaxis": {
                    "title": {
                        "text": "Ano"
                    }
                },
                "yaxis": {
                    "title": {
                        "text": ycol
                    }
                },
                "coloraxis": {
                    "colorscale": "Viridis",
                    "colorbar": {
                        "title": {
                            "text": "Taxa Vacina"
                        }
                    }
                }
            }
        }

        return answer

Para executar em modo teste de contrato inclua o trecho em tests/test_004:

from loguru import logger
from consumer_dash.basic.service import Service

class Test004():

  service = Service()

  def test(self):
    logger.info("Testando o método test do módulo test_004.py")
    service = Service()
    answer = service.test("graph_3", ["casos"])
    assert type(answer) == dict

Abra o terminal e execute:

python3 main.py CONTRACT_TEST 004

Para visualizar os resultados digite (executar modo DASH_DEBUG):

python3 main.py DASH_DEBUG

Acesse a URL [http://0.0.0.0:8050/]

Em dashboards.json, incremente o número das versões de DASHT e reinicie o container dc_portal no docker.

Execute o comando no terminal:

python3 manage.py dash_service

Acesse a URL abaixo para visualizar o resultado:

http://localhost:8000/

Resultado esperado:

alt text

Uma vez que todos gráficos em service.py são criados em formato de dicionário, o endereço abaixo pode fornecer gráficos em formato JSON prontos para serem adaptados ao código, no gráfico de interesse, basta escolher a opção Python & R.

https://chart-studio.plotly.com/feed/#/

Por fim, vamos inserir uma tabela que seleciona um ano através de um dropdown e uma taxa de vacinação por meio de um slider.

Vá até dashboards.json e insira o conteúdo na linha 183:

,
        {
          "type": "div",
          "style": {
            "width": "100%",
            "display": "inline-block"
          },
          "components": [
            {
              "type": "dropdown",
              "title": "Selecione um ano:",
              "id": "data_5",
              "properties": {
                "options": [
                  {
                    "label": "2025",
                    "value": 2025
                  },
                  {
                    "label": "2024",
                    "value": 2024
                  },
                  {
                    "label": "2023",
                    "value": 2023
                  },
                  {
                    "label": "2022",
                    "value": 2022
                  },
                  {
                    "label": "2021",
                    "value": 2021
                  },
                  {
                    "label": "2020",
                    "value": 2020
                  }
                ],
                "default": 2020
              }
            },
            {
              "type": "slider",
              "title": "Filtro - Selecione uma Taxa de Vacinação",
              "id": "data_6",
              "properties": {
                  "begin": 0,
                  "end": 100,
                  "step": 2,
                  "value": 0
              }
            },
            {
              "type": "datatable",
              "id": "datatable_1"
            }
          ]
        }

Em callbacks, na linha 273 insira o trecho de código:

,
        {
          "outputs": [
            "datatable_1"
          ],
          "inputs":[
            "data_5",
            "data_6"
          ]
        }

Vá até service.py e insira o contéudo após process4:

    @dashProcess(componentId="datatable_1")
    def process5(self, input):
        logger.info("Processando datatable_1")


        if input[0] is None:
            ano = 2020
        else:
            ano = input[0]

        if input[1] is None:
            taxavacina = 0.0
        else:
            taxavacina = input[1]

        path = "../data/dados.csv"

        if not os.path.exists(path):
                    path = "data/dados.csv"

        df = pd.read_csv(path)

        df = df[df["ano"] == ano]
        df = df[df["taxa vacina"] >= taxavacina]   
        answer = df.to_dict('records')

        return answer

Para executar em modo teste de contrato inclua o trecho em tests/test_005:

from loguru import logger
from consumer_dash.basic.service import Service

class Test005():

  service = Service()

  def test(self):
    logger.info("Testando o método test do módulo test_005.py")
    service = Service()
    answer = service.test("datatable_1", [2020, 1])
    assert type(answer) == list

Abra o terminal e execute:

python3 main.py CONTRACT_TEST 005

Para visualizar os resultados digite (executar modo DASH_DEBUG):

python3 main.py DASH_DEBUG

Acesse a URL [http://0.0.0.0:8050/]

Em dashboards.json, incremente o número das versões de DASHT e reinicie o container dc_portal no docker.

Execute o comando no terminal:

python3 manage.py dash_service

Acesse a URL abaixo para visualizar o resultado:

http://localhost:8000/

Resultado esperado:

alt text

Pronto! Agora você é capaz de criar seus próprios dashboards com filtros.

Resultado Final:

alt text