Metadata-Version: 2.4
Name: pumpwood-streamlit
Version: 0.18.12
Summary: Package to deploy Streamlit at Pumpwood Systems
License: BSD-3-Clause License
License-File: LICENSE
Author: André Andrade Baceti
Author-email: a.baceti@murabei.com
Requires-Python: >=3.6
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Dist: extra-streamlit-components
Requires-Dist: loguru (>=0.7.3)
Requires-Dist: lxml
Requires-Dist: openpyxl
Requires-Dist: pandas
Requires-Dist: pumpwood-communication (>=2.2.4)
Requires-Dist: pyarrow
Requires-Dist: streamlit
Requires-Dist: streamlit-cookies-controller
Project-URL: Homepage, https://github.com/Murabei-OpenSource-Codes/pumpwood-streamlit-pkg
Description-Content-Type: text/markdown

# pumpwood-streamlit-pkg
Python Package to deploy Streamlit Dashboards at Pumpwood Systems.

<p align="center" width="60%">
  <img src="static_doc/sitelogo-horizontal.png" /> <br>

  <a href="https://en.wikipedia.org/wiki/Cecropia">
    Pumpwood is a native brasilian tree
  </a> which has a symbiotic relation with ants (Murabei)
</p>

This packages help to deploy Streamlit Dashboard associated with Pumpwood
based Systems registering routes at Kong for single end-point for all
microservice.

It implements an abstract class that should be inherited. When rendering the
dashboard, it uses cookie `PumpwoodAuthorization` to get authentication
token and validates if user is logged at Pumpwood Auth microservice. If
authentication token is not valid, `authentication_error_page` is
called to render unauthorized page.

# Documentation Page
You can check the doc page [here](https://murabei-opensource-codes.github.io/pumpwood-streamlit-pkg/pumpwood_streamlit.html).

# Example
Example using dashboard example made avaiable by Streamlit oficial
website.
```
import os
import streamlit as st
import pandas as pd
import altair as alt
import plotly.express as px
from pumpwood_streamlit.dashboard import PumpwoodStreamlitDashboard


class Dashboard(PumpwoodStreamlitDashboard):
    def set_page_config(self):
        #######################
        # Page configuration
        st.set_page_config(
            page_title="US Population Dashboard",
            page_icon="🏂",
            layout="wide",
            initial_sidebar_state="expanded")

    def main_view(self):
        alt.themes.enable("dark")

        #######################
        # Load data
        file_path = os.path.join(
            os.path.dirname(__file__),
            'data/us-population-2010-2019-reshaped.csv')
        df_reshaped = pd.read_csv(file_path)


        #######################
        # Sidebar
        with st.sidebar:
            st.title('🏂 US Population Dashboard')

            year_list = list(df_reshaped.year.unique())[::-1]

            selected_year = st.selectbox('Select a year', year_list)
            df_selected_year = df_reshaped[df_reshaped.year == selected_year]
            df_selected_year_sorted = df_selected_year.sort_values(by="population", ascending=False)

            color_theme_list = ['blues', 'cividis', 'greens', 'inferno', 'magma', 'plasma', 'reds', 'rainbow', 'turbo', 'viridis']
            selected_color_theme = st.selectbox('Select a color theme', color_theme_list)


        #######################
        # Plots

        # Heatmap
        def make_heatmap(input_df, input_y, input_x, input_color, input_color_theme):
            heatmap = alt.Chart(input_df).mark_rect().encode(
                    y=alt.Y(f'{input_y}:O', axis=alt.Axis(title="Year", titleFontSize=18, titlePadding=15, titleFontWeight=900, labelAngle=0)),
                    x=alt.X(f'{input_x}:O', axis=alt.Axis(title="", titleFontSize=18, titlePadding=15, titleFontWeight=900)),
                    color=alt.Color(f'max({input_color}):Q',
                                     legend=None,
                                     scale=alt.Scale(scheme=input_color_theme)),
                    stroke=alt.value('black'),
                    strokeWidth=alt.value(0.25),
                ).properties(width=900
                ).configure_axis(
                labelFontSize=12,
                titleFontSize=12
                )
            # height=300
            return heatmap

        # Choropleth map
        def make_choropleth(input_df, input_id, input_column, input_color_theme):
            choropleth = px.choropleth(input_df, locations=input_id, color=input_column, locationmode="USA-states",
                                       color_continuous_scale=input_color_theme,
                                       range_color=(0, max(df_selected_year.population)),
                                       scope="usa",
                                       labels={'population':'Population'}
                                      )
            choropleth.update_layout(
                template='plotly_dark',
                plot_bgcolor='rgba(0, 0, 0, 0)',
                paper_bgcolor='rgba(0, 0, 0, 0)',
                margin=dict(l=0, r=0, t=0, b=0),
                height=350
            )
            return choropleth


        # Donut chart
        def make_donut(input_response, input_text, input_color):
          if input_color == 'blue':
              chart_color = ['#29b5e8', '#155F7A']
          if input_color == 'green':
              chart_color = ['#27AE60', '#12783D']
          if input_color == 'orange':
              chart_color = ['#F39C12', '#875A12']
          if input_color == 'red':
              chart_color = ['#E74C3C', '#781F16']

          source = pd.DataFrame({
              "Topic": ['', input_text],
              "% value": [100-input_response, input_response]
          })
          source_bg = pd.DataFrame({
              "Topic": ['', input_text],
              "% value": [100, 0]
          })

          plot = alt.Chart(source).mark_arc(innerRadius=45, cornerRadius=25).encode(
              theta="% value",
              color= alt.Color("Topic:N",
                              scale=alt.Scale(
                                  #domain=['A', 'B'],
                                  domain=[input_text, ''],
                                  # range=['#29b5e8', '#155F7A']),  # 31333F
                                  range=chart_color),
                              legend=None),
          ).properties(width=130, height=130)

          text = plot.mark_text(align='center', color="#29b5e8", font="Lato", fontSize=32, fontWeight=700, fontStyle="italic").encode(text=alt.value(f'{input_response} %'))
          plot_bg = alt.Chart(source_bg).mark_arc(innerRadius=45, cornerRadius=20).encode(
              theta="% value",
              color= alt.Color("Topic:N",
                              scale=alt.Scale(
                                  # domain=['A', 'B'],
                                  domain=[input_text, ''],
                                  range=chart_color),  # 31333F
                              legend=None),
          ).properties(width=130, height=130)
          return plot_bg + plot + text

        # Convert population to text
        def format_number(num):
            if num > 1000000:
                if not num % 1000000:
                    return f'{num // 1000000} M'
                return f'{round(num / 1000000, 1)} M'
            return f'{num // 1000} K'

        # Calculation year-over-year population migrations
        def calculate_population_difference(input_df, input_year):
          selected_year_data = input_df[input_df['year'] == input_year].reset_index()
          previous_year_data = input_df[input_df['year'] == input_year - 1].reset_index()
          selected_year_data['population_difference'] = selected_year_data.population.sub(previous_year_data.population, fill_value=0)
          return pd.concat([selected_year_data.states, selected_year_data.id, selected_year_data.population, selected_year_data.population_difference], axis=1).sort_values(by="population_difference", ascending=False)


        #######################
        # Dashboard Main Panel
        col = st.columns((1.5, 4.5, 2), gap='medium')

        with col[0]:
            st.markdown('#### Gains/Losses')

            df_population_difference_sorted = calculate_population_difference(df_reshaped, selected_year)

            if selected_year > 2010:
                first_state_name = df_population_difference_sorted.states.iloc[0]
                first_state_population = format_number(df_population_difference_sorted.population.iloc[0])
                first_state_delta = format_number(df_population_difference_sorted.population_difference.iloc[0])
            else:
                first_state_name = '-'
                first_state_population = '-'
                first_state_delta = ''
            st.metric(label=first_state_name, value=first_state_population, delta=first_state_delta)

            if selected_year > 2010:
                last_state_name = df_population_difference_sorted.states.iloc[-1]
                last_state_population = format_number(df_population_difference_sorted.population.iloc[-1])
                last_state_delta = format_number(df_population_difference_sorted.population_difference.iloc[-1])
            else:
                last_state_name = '-'
                last_state_population = '-'
                last_state_delta = ''
            st.metric(label=last_state_name, value=last_state_population, delta=last_state_delta)


            st.markdown('#### States Migration')

            if selected_year > 2010:
                # Filter states with population difference > 50000
                # df_greater_50000 = df_population_difference_sorted[df_population_difference_sorted.population_difference_absolute > 50000]
                df_greater_50000 = df_population_difference_sorted[df_population_difference_sorted.population_difference > 50000]
                df_less_50000 = df_population_difference_sorted[df_population_difference_sorted.population_difference < -50000]

                # % of States with population difference > 50000
                states_migration_greater = round((len(df_greater_50000)/df_population_difference_sorted.states.nunique())*100)
                states_migration_less = round((len(df_less_50000)/df_population_difference_sorted.states.nunique())*100)
                donut_chart_greater = make_donut(states_migration_greater, 'Inbound Migration', 'green')
                donut_chart_less = make_donut(states_migration_less, 'Outbound Migration', 'red')
            else:
                states_migration_greater = 0
                states_migration_less = 0
                donut_chart_greater = make_donut(states_migration_greater, 'Inbound Migration', 'green')
                donut_chart_less = make_donut(states_migration_less, 'Outbound Migration', 'red')

            migrations_col = st.columns((0.2, 1, 0.2))
            with migrations_col[1]:
                st.write('Inbound')
                st.altair_chart(donut_chart_greater)
                st.write('Outbound')
                st.altair_chart(donut_chart_less)

        with col[1]:
            st.markdown('#### Total Population')

            choropleth = make_choropleth(df_selected_year, 'states_code', 'population', selected_color_theme)
            st.plotly_chart(choropleth, use_container_width=True)

            heatmap = make_heatmap(df_reshaped, 'year', 'states', 'population', selected_color_theme)
            st.altair_chart(heatmap, use_container_width=True)

        with col[2]:
            st.markdown('#### Top States')

            st.dataframe(df_selected_year_sorted,
                         column_order=("states", "population"),
                         hide_index=True,
                         width=None,
                         column_config={
                            "states": st.column_config.TextColumn(
                                "States",
                            ),
                            "population": st.column_config.ProgressColumn(
                                "Population",
                                format="%f",
                                min_value=0,
                                max_value=max(df_selected_year_sorted.population),
                             )}
                         )

            with st.expander('About', expanded=True):
                st.write('''
                    - Data: [U.S. Census Bureau](https://www.census.gov/data/datasets/time-series/demo/popest/2010s-state-total.html).
                    - :orange[**Gains/Losses**]: states with high inbound/ outbound migration for selected year
                    - :orange[**States Migration**]: percentage of states with annual inbound/ outbound migration > 50,000
                    ''')
```

