<filename>app.py
"""
 Main application file for a Dash dashboard using python
 
 -----------------------------------
 Created on Fri Mar 12 10:34:31 2021
 @author: matthew.mcfahn
"""

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import plotly.graph_objects as go

import sys
sys.path.append('/Users/matthew.mcfahn/Documents/GitHub/who-api-analysis')
sys.path.append('/Users/matthew.mcfahn/Documents/GitHub/who-api-analysis/99_Shared')


##############################################################################
#   - Setup data needed for app, and helper functions from database connection
##############################################################################
from dash_data_extraction import __get_years_tickvals, db_file, get_static_data_assets, __get_available_areas, __get_linegraph_data, __get_worldmap_data, __get_parameter_types_for_ind, __get_param_options


print('[DATA LOAD] Loading static assets...')
indicators, categories, areas = get_static_data_assets(db_file)

indicators_dict = [{'label': indicators.loc[i]['indicator_name'], 'value': indicators.loc[i]['indicator_code']} for i in indicators.index]
areas_dict = [{'label': areas.loc[i]['area_name'], 'value': areas.loc[i]['area_code']} for i in areas.index]
categories_dict = [{'label':categories.category_name[i], 'value':categories.category_name[i]} for i in categories.category_name.index]
print('[DATA LOAD] Complete <<< Launching app')

##############################################################################
#   - Setup data needed for app
##############################################################################

##############################################################################
#   - Main Dash app functions
##############################################################################
# Initalise Dash app  class
app = dash.Dash(__name__)
app.title = 'World Health Organisation: Dash data explorer'
def main():
    """Just testing"""
    # Set defaults to be loaded    
    app.layout = html.Div( # First \div element
    
    children=[
              html.Div(className='row',  # Define the row element. This'll have two cols
              children=[
                        html.Div(className='four columns div-user-controls',  # Define the left element
                                 children = [html.H6(children="Use dropdowns to select indicators and areas to display",
                                                     style={'marginBottom': 0, 'marginTop': 0}),
                                             html.Div([html.Label(['Choose category:'],style={'font-weight': 'bold', "text-align": "left"}),
                                                       dcc.Dropdown(id='category_dropdown',
                                                                    options=categories_dict,
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value="Mortality and Global Health Estimates",#dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    ),                                  #'memory': browser tab is refreshed
                                                       
                                                            ],className='DropDown'),
                            
                                             html.Div([html.Label(['Choose indicator:'],style={'font-weight': 'normal', "text-align": "left"}),
                                                       
                                                       dcc.Dropdown(id='indicator_dropdown',
                                                                    options=indicators_dict,
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value='WHOSIS_000014',              #dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    # className='select_box',           #activate separate CSS document in assets folder
                                                                    # persistence=True,                 #remembers dropdown value. Used with persistence_type
                                                                    # persistence_type='memory'         #remembers dropdown value selected until...
                                                                    ),   
                                                       ],className='DropDown'),
                                             # Optional 'parameter type' dropdown
                                             html.Div(id = 'parameter_type_dropdown_div',
                                                      children = [html.Label(['Choose parameter type:'],style={'font-weight': 'normal', "text-align": "left"}),
                                                       
                                                       dcc.Dropdown(id='parameter_type_dropdown',
                                                                    options=[{'label':0,'value':0}],
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value=None,                         #dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    # className='select_box',           #activate separate CSS document in assets folder
                                                                    # persistence=True,                 #remembers dropdown value. Used with persistence_type
                                                                    # persistence_type='memory'         #remembers dropdown value selected until...
                                                                    ),   
                                                       ],className='DropDown',style={"display":"none"},
                                                      ),
                                             
                                             # Optional 'parameter' dropdown
                                             html.Div(id = 'parameter_dropdown_div',
                                                      children = [html.Label(['Choose parameter:'],style={'font-weight': 'normal', "text-align": "left"}),
                                                       
                                                       dcc.Dropdown(id='parameter_dropdown',
                                                                    options=[{'label':0,'value':0}],
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value=None,                         #dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    # className='select_box',           #activate separate CSS document in assets folder
                                                                    # persistence=True,                 #remembers dropdown value. Used with persistence_type
                                                                    # persistence_type='memory'         #remembers dropdown value selected until...
                                                                    ),
                                                       ],className='DropDown',style={"display":"none"}
                                                      ),
                                             
                                            ]
                                 ),
            
                        html.Div(className='eight columns div-for-charts bg-grey',  # Define the right element
                                 children = [
                                             html.Div(children=[html.H1(children="WHO Global Health Observatory: Dash Analytics", 
                                                                        style={'marginBottom': 25, 'marginTop': 65}),
                                                                
                                                                html.H2(children ="Explore GHO indicators across the world"),
                                                                dcc.Graph(id ='globe_graph',className = 'Graph', style={'width': '126vh', 
                                                                                                                        'height': '77.87vh'}),
                                                                
                                                                html.H2(children ="Single country explorer: Explore one country's performance over time"),
                                                                
                                                                html.Div([html.Label(['Choose area:'],style={'font-weight': 'bold', "text-align": "left"}),
                                                      
                                                                          dcc.Dropdown(id='area_dropdown',
                                                                                       options=areas_dict,
                                                                                       optionHeight=65,                    #height/space between dropdown options
                                                                                       value='GBR',                        #dropdown value selected automatically when page loads
                                                                                       disabled=False,                     #disable dropdown value selection
                                                                                       multi=False,                        #allow multiple dropdown values to be selected
                                                                                       searchable=True,                    #allow user-searching of dropdown values
                                                                                       search_value='',                    #remembers the value searched in dropdown
                                                                                       placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                                       clearable=True,                     #allow user to removes the selected value
                                                                                       style={'width':"100%"},              #use dictionary to define CSS styles of your dropdown
                                                                                       ),                                  #'memory': browser tab is refreshed
                                                                          ],className='DropDown'),
                                                                dcc.Graph(id ='single_country_graph',className = 'Graph'),
                                                                ], style = {'backgroundColor':'rgb(50, 50, 50)'}
                                                      )
                                             ],
                                 )
                        ]
              )
              ]
    )
    
    return None

###############################################################################
# - Callbacks to update dropdowns
###############################################################################
### - Callback: Update area and parameter options, based on indicator
@app.callback(
    dash.dependencies.Output('area_dropdown', 'options'),
    dash.dependencies.Output('parameter_type_dropdown_div', 'style'),
    dash.dependencies.Output('parameter_dropdown_div', 'style'),
    dash.dependencies.Output('parameter_type_dropdown', 'options'),
    [dash.dependencies.Input('indicator_dropdown', 'value')])

def __restrict_areas_dropdown_add_param_dropdowns(ind_code):
    """Restrict to only areas with a value, and update parameter types. Needs to:
        > Update areas options
        > Update parameter_type dropdown visibility
        > Update parameter_type dropdown OPTIONS
        > Update parameter dropdown visibility
        """
    # Find present country codes, update areas_dict using this
    area_codes = __get_available_areas(ind_code)
    cut_areas = areas.loc[areas['area_code'].isin(area_codes)]
    cut_areas = cut_areas.sort_values(by = 'area_code').reset_index(drop = True)
    areas_dict = [{'label': cut_areas.loc[i]['area_name'], 'value': cut_areas.loc[i]['area_code']} for i in cut_areas.index]
    
    # Update visibility based on whether there are any parameter_type options
    param_types = __get_parameter_types_for_ind(ind_code)
    if param_types.empty:
        style = {"display":"none"}
        param_type_dict = [{'label':0,'value':0}]
    else:
        style = {"display":"block"}
        param_type_dict = [{'label':x,'value':x} for x in param_types['parameter_name'].values]
    
    return areas_dict, style, style, param_type_dict

### - Callback: Update indicator options, based on category
@app.callback(
    dash.dependencies.Output('indicator_dropdown', 'options'),
    [dash.dependencies.Input('category_dropdown', 'value')
     ])

def __restrict_indicator_dropdown(category_name):
    """Restrict to only indicators with a value"""
    cut_indicators = indicators.loc[indicators['category_name'] == category_name]
    
    indicators_dict = [{'label': cut_indicators.loc[i]['indicator_name'], 'value': cut_indicators.loc[i]['indicator_code']} for i in cut_indicators.index]
    return indicators_dict

### - Callback: Update parameter options based on parameter_type
@app.callback(
    dash.dependencies.Output('parameter_dropdown', 'options'),
    [dash.dependencies.Input('parameter_type_dropdown', 'value')
     ])

def __get_paramater_options(param_name):
    """Helper to get parameter options given the parameter name selected"""
    data = __get_param_options(param_name)
    
    params_dict = [{'label':x,'value':x} for x in data['parameter_value'].values]
    return params_dict

### - Callback: Update parameter value based on new indicator
@app.callback(
    dash.dependencies.Output('parameter_dropdown', 'value'),
    [dash.dependencies.Input('indicator_dropdown', 'value')
     ])

def __overwrite_param_value(ind_code):
    """Helper to get parameter options given the parameter name selected"""
    return None

###############################################################################
# - Callbacks to update dropdowns
###############################################################################


###############################################################################
# - Callbacks to update graphics
###############################################################################
### - Callback: Update line graph, based on inputs
@app.callback(
    dash.dependencies.Output(component_id = 'single_country_graph', component_property = 'figure'),
    [dash.dependencies.Input(component_id = 'area_dropdown', component_property = 'value'),
     dash.dependencies.Input(component_id = 'indicator_dropdown', component_property = 'value'),
     dash.dependencies.Input(component_id = 'parameter_dropdown', component_property = 'value'),
     ])

def __update_lineplot(area_code, ind_code, param_value):
    """Helper to render a lineplot for the area and indicator"""
    area_name = areas.loc[areas['area_code'] ==area_code].reset_index(drop=True).loc[0]['area_name']
    ind_name = indicators.loc[indicators['indicator_code'] ==ind_code].reset_index(drop=True).loc[0]['indicator_name']
    
    # Read the data
    data = __get_linegraph_data(area_code, ind_code, param_value)
    if data.empty:
        fig = go.Figure()
        fig.add_annotation(text = """No data for the selected area and indicator""")
        return fig
    
    if not param_value:
        title = f"""{area_name}: <br>{ind_name}"""
    else:
        title = f"""{area_name}: <br>{ind_name} - {param_value}"""
    
    years = data['year'].unique()
    if len(years) > 1:
        x = list(data["year"])
        y = list(data["numeric_value"])
        y_lower = list(data["low"])
        y_upper = list(data["high"])
        
        fig = go.Figure([
                go.Scatter(
                           x=x,
                           y=y,
                           line=dict(color='rgb(0,100,80)'),
                           mode='lines',
                           name='Value',
                           hovertemplate='Value: %{y:.1f}'+'<br>Year: %{x}')
                ,
                go.Scatter(
                           x=x+x[::-1], # x, then x reversed
                           y=y_upper+y_lower[::-1], # upper, then lower reversed
                           fill='toself',
                           fillcolor='rgba(0,100,80,0.2)',
                           line=dict(color='rgba(255,255,255,0)'),
                           showlegend=True,
                           name='Low & high bounds',
                           hoverinfo='skip')
                       ])
        fig.update_layout(xaxis_title='Year',
                          yaxis_title='Value',
                          title = title,
                          xaxis = dict(tickmode = 'array',
                                       tickvals = __get_years_tickvals(years)),
                          plot_bgcolor='#ced4da',
                          paper_bgcolor='#ced4da'
                          )
    else:
        y = data[['low','numeric_value','high']].reset_index(drop = True)
        y = list(y.loc[0])
        groups = ['Low','Estimate','High']
        fig = go.Figure(data=[
                        go.Bar(name='SF Zoo', x=groups, y=y)
                        ])
        # Change the bar mode
        fig.update_layout(barmode='group',
                          xaxis_title='Area',
                          yaxis_title='Value',
                          title = title,
                          plot_bgcolor='#ced4da',
                          paper_bgcolor='#ced4da'
                          )
    return fig

### - Callback: Update world graph, based on inputs
@app.callback(
    dash.dependencies.Output(component_id = 'globe_graph', component_property = 'figure'),
     [dash.dependencies.Input(component_id = 'indicator_dropdown', component_property = 'value'),
      dash.dependencies.Input(component_id = 'parameter_dropdown', component_property = 'value'),
     ])

def __update_globe_graphic(ind_code, param_value):
    """Helper to render a world heat map based on the indicator selected"""
    indicator_name = indicators.loc[indicators['indicator_code'] == ind_code].reset_index(drop = True).loc[0].indicator_name
    
    data = __get_worldmap_data(ind_code, param_value)
    data.drop_duplicates(subset = ['area_code','year','numeric_value'], inplace = True)
    # Deal with no data cases. This does sometimes happen unfortunately (as data is available at a more granular level)
    if data.empty:
        fig = go.Figure(data = go.Choropleth(locations = areas['area_code']))
        fig.add_annotation(text = 'No data available at the top level. Select a lower granularity')
        return fig
    
    # Cut to just the latest year for all areas. Could pass this upstream into the SQL, but it's easier to handle the edge case here 
    if list(data.year.unique()) == [None]:
        max_year = 'Unknown'
    else:
        max_year = int(data['year'].max())
        max_yr_df = data.groupby('area_code')['year'].max().reset_index()
        data = max_yr_df.merge(data, on = ['area_code','year'], how = 'left', validate = 'one_to_one')
    
    if not param_value:
        title_text=f'{indicator_name}: <br>Data up to {max_year}'
    else:
        title_text=f'{indicator_name} - {param_value}: <br>Data up to {max_year}'
        
    text = 'Country: ' + data['area_name'] + '<br>Year: ' +\
            data['year'].astype(int).astype(str) +\
                '<br>Value: ' + data['numeric_value'].round(1).astype(str)
    
    fig = go.Figure(data=go.Choropleth(locations = data['area_code'],
                                       z = data['numeric_value'],
                                       text = text,
                                       colorscale = 'Blues',
                                       autocolorscale=False,
                                       reversescale=False,
                                       marker_line_color='darkgray',
                                       marker_line_width=0.5,
                                       colorbar_title = 'Value',
                                       hovertext = text,
                                       hoverinfo = 'text'
                                       )
                    )
    fig.update_layout(title_text=title_text,
                      geo=dict(showframe=False,
                               showcoastlines=False,
                               projection_type='equirectangular'
                               ),
                      plot_bgcolor='#ced4da',
                      paper_bgcolor='#ced4da'
                     )
    
    return fig
###############################################################################
# - Callbacks to update graphics
###############################################################################



# Startup app on running module
if __name__ == "__main__":
    main()
    app.run_server(debug=True)
<filename>app.py
"""
 Main application file for a Dash dashboard using python
 
 -----------------------------------
 Created on Fri Mar 12 10:34:31 2021
 @author: matthew.mcfahn
"""

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import plotly.graph_objects as go

import sys
sys.path.append('/Users/matthew.mcfahn/Documents/GitHub/who-api-analysis')
sys.path.append('/Users/matthew.mcfahn/Documents/GitHub/who-api-analysis/99_Shared')


##############################################################################
#   - Setup data needed for app, and helper functions from database connection
##############################################################################
from dash_data_extraction import __get_years_tickvals, db_file, get_static_data_assets, __get_available_areas, __get_linegraph_data, __get_worldmap_data, __get_parameter_types_for_ind, __get_param_options


print('[DATA LOAD] Loading static assets...')
indicators, categories, areas = get_static_data_assets(db_file)

indicators_dict = [{'label': indicators.loc[i]['indicator_name'], 'value': indicators.loc[i]['indicator_code']} for i in indicators.index]
areas_dict = [{'label': areas.loc[i]['area_name'], 'value': areas.loc[i]['area_code']} for i in areas.index]
categories_dict = [{'label':categories.category_name[i], 'value':categories.category_name[i]} for i in categories.category_name.index]
print('[DATA LOAD] Complete <<< Launching app')

##############################################################################
#   - Setup data needed for app
##############################################################################

##############################################################################
#   - Main Dash app functions
##############################################################################
# Initalise Dash app  class
app = dash.Dash(__name__)
app.title = 'World Health Organisation: Dash data explorer'
def main():
    """Just testing"""
    # Set defaults to be loaded    
    app.layout = html.Div( # First \div element
    
    children=[
              html.Div(className='row',  # Define the row element. This'll have two cols
              children=[
                        html.Div(className='four columns div-user-controls',  # Define the left element
                                 children = [html.H6(children="Use dropdowns to select indicators and areas to display",
                                                     style={'marginBottom': 0, 'marginTop': 0}),
                                             html.Div([html.Label(['Choose category:'],style={'font-weight': 'bold', "text-align": "left"}),
                                                       dcc.Dropdown(id='category_dropdown',
                                                                    options=categories_dict,
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value="Mortality and Global Health Estimates",#dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    ),                                  #'memory': browser tab is refreshed
                                                       
                                                            ],className='DropDown'),
                            
                                             html.Div([html.Label(['Choose indicator:'],style={'font-weight': 'normal', "text-align": "left"}),
                                                       
                                                       dcc.Dropdown(id='indicator_dropdown',
                                                                    options=indicators_dict,
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value='WHOSIS_000014',              #dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    # className='select_box',           #activate separate CSS document in assets folder
                                                                    # persistence=True,                 #remembers dropdown value. Used with persistence_type
                                                                    # persistence_type='memory'         #remembers dropdown value selected until...
                                                                    ),   
                                                       ],className='DropDown'),
                                             # Optional 'parameter type' dropdown
                                             html.Div(id = 'parameter_type_dropdown_div',
                                                      children = [html.Label(['Choose parameter type:'],style={'font-weight': 'normal', "text-align": "left"}),
                                                       
                                                       dcc.Dropdown(id='parameter_type_dropdown',
                                                                    options=[{'label':0,'value':0}],
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value=None,                         #dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    # className='select_box',           #activate separate CSS document in assets folder
                                                                    # persistence=True,                 #remembers dropdown value. Used with persistence_type
                                                                    # persistence_type='memory'         #remembers dropdown value selected until...
                                                                    ),   
                                                       ],className='DropDown',style={"display":"none"},
                                                      ),
                                             
                                             # Optional 'parameter' dropdown
                                             html.Div(id = 'parameter_dropdown_div',
                                                      children = [html.Label(['Choose parameter:'],style={'font-weight': 'normal', "text-align": "left"}),
                                                       
                                                       dcc.Dropdown(id='parameter_dropdown',
                                                                    options=[{'label':0,'value':0}],
                                                                    optionHeight=65,                    #height/space between dropdown options
                                                                    value=None,                         #dropdown value selected automatically when page loads
                                                                    disabled=False,                     #disable dropdown value selection
                                                                    multi=False,                        #allow multiple dropdown values to be selected
                                                                    searchable=True,                    #allow user-searching of dropdown values
                                                                    search_value='',                    #remembers the value searched in dropdown
                                                                    placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                    clearable=True,                     #allow user to removes the selected value
                                                                    style={'width':"100%"},             #use dictionary to define CSS styles of your dropdown
                                                                    # className='select_box',           #activate separate CSS document in assets folder
                                                                    # persistence=True,                 #remembers dropdown value. Used with persistence_type
                                                                    # persistence_type='memory'         #remembers dropdown value selected until...
                                                                    ),
                                                       ],className='DropDown',style={"display":"none"}
                                                      ),
                                             
                                            ]
                                 ),
            
                        html.Div(className='eight columns div-for-charts bg-grey',  # Define the right element
                                 children = [
                                             html.Div(children=[html.H1(children="WHO Global Health Observatory: Dash Analytics", 
                                                                        style={'marginBottom': 25, 'marginTop': 65}),
                                                                
                                                                html.H2(children ="Explore GHO indicators across the world"),
                                                                dcc.Graph(id ='globe_graph',className = 'Graph', style={'width': '126vh', 
                                                                                                                        'height': '77.87vh'}),
                                                                
                                                                html.H2(children ="Single country explorer: Explore one country's performance over time"),
                                                                
                                                                html.Div([html.Label(['Choose area:'],style={'font-weight': 'bold', "text-align": "left"}),
                                                      
                                                                          dcc.Dropdown(id='area_dropdown',
                                                                                       options=areas_dict,
                                                                                       optionHeight=65,                    #height/space between dropdown options
                                                                                       value='GBR',                        #dropdown value selected automatically when page loads
                                                                                       disabled=False,                     #disable dropdown value selection
                                                                                       multi=False,                        #allow multiple dropdown values to be selected
                                                                                       searchable=True,                    #allow user-searching of dropdown values
                                                                                       search_value='',                    #remembers the value searched in dropdown
                                                                                       placeholder='Please select...',     #gray, default text shown when no option is selected
                                                                                       clearable=True,                     #allow user to removes the selected value
                                                                                       style={'width':"100%"},              #use dictionary to define CSS styles of your dropdown
                                                                                       ),                                  #'memory': browser tab is refreshed
                                                                          ],className='DropDown'),
                                                                dcc.Graph(id ='single_country_graph',className = 'Graph'),
                                                                ], style = {'backgroundColor':'rgb(50, 50, 50)'}
                                                      )
                                             ],
                                 )
                        ]
              )
              ]
    )
    
    return None

###############################################################################
# - Callbacks to update dropdowns
###############################################################################
### - Callback: Update area and parameter options, based on indicator
@app.callback(
    dash.dependencies.Output('area_dropdown', 'options'),
    dash.dependencies.Output('parameter_type_dropdown_div', 'style'),
    dash.dependencies.Output('parameter_dropdown_div', 'style'),
    dash.dependencies.Output('parameter_type_dropdown', 'options'),
    [dash.dependencies.Input('indicator_dropdown', 'value')])

def __restrict_areas_dropdown_add_param_dropdowns(ind_code):
    """Restrict to only areas with a value, and update parameter types. Needs to:
        > Update areas options
        > Update parameter_type dropdown visibility
        > Update parameter_type dropdown OPTIONS
        > Update parameter dropdown visibility
        """
    # Find present country codes, update areas_dict using this
    area_codes = __get_available_areas(ind_code)
    cut_areas = areas.loc[areas['area_code'].isin(area_codes)]
    cut_areas = cut_areas.sort_values(by = 'area_code').reset_index(drop = True)
    areas_dict = [{'label': cut_areas.loc[i]['area_name'], 'value': cut_areas.loc[i]['area_code']} for i in cut_areas.index]
    
    # Update visibility based on whether there are any parameter_type options
    param_types = __get_parameter_types_for_ind(ind_code)
    if param_types.empty:
        style = {"display":"none"}
        param_type_dict = [{'label':0,'value':0}]
    else:
        style = {"display":"block"}
        param_type_dict = [{'label':x,'value':x} for x in param_types['parameter_name'].values]
    
    return areas_dict, style, style, param_type_dict

### - Callback: Update indicator options, based on category
@app.callback(
    dash.dependencies.Output('indicator_dropdown', 'options'),
    [dash.dependencies.Input('category_dropdown', 'value')
     ])

def __restrict_indicator_dropdown(category_name):
    """Restrict to only indicators with a value"""
    cut_indicators = indicators.loc[indicators['category_name'] == category_name]
    
    indicators_dict = [{'label': cut_indicators.loc[i]['indicator_name'], 'value': cut_indicators.loc[i]['indicator_code']} for i in cut_indicators.index]
    return indicators_dict

### - Callback: Update parameter options based on parameter_type
@app.callback(
    dash.dependencies.Output('parameter_dropdown', 'options'),
    [dash.dependencies.Input('parameter_type_dropdown', 'value')
     ])

def __get_paramater_options(param_name):
    """Helper to get parameter options given the parameter name selected"""
    data = __get_param_options(param_name)
    
    params_dict = [{'label':x,'value':x} for x in data['parameter_value'].values]
    return params_dict

### - Callback: Update parameter value based on new indicator
@app.callback(
    dash.dependencies.Output('parameter_dropdown', 'value'),
    [dash.dependencies.Input('indicator_dropdown', 'value')
     ])

def __overwrite_param_value(ind_code):
    """Helper to get parameter options given the parameter name selected"""
    return None

###############################################################################
# - Callbacks to update dropdowns
###############################################################################


###############################################################################
# - Callbacks to update graphics
###############################################################################
### - Callback: Update line graph, based on inputs
@app.callback(
    dash.dependencies.Output(component_id = 'single_country_graph', component_property = 'figure'),
    [dash.dependencies.Input(component_id = 'area_dropdown', component_property = 'value'),
     dash.dependencies.Input(component_id = 'indicator_dropdown', component_property = 'value'),
     dash.dependencies.Input(component_id = 'parameter_dropdown', component_property = 'value'),
     ])

def __update_lineplot(area_code, ind_code, param_value):
    """Helper to render a lineplot for the area and indicator"""
    area_name = areas.loc[areas['area_code'] ==area_code].reset_index(drop=True).loc[0]['area_name']
    ind_name = indicators.loc[indicators['indicator_code'] ==ind_code].reset_index(drop=True).loc[0]['indicator_name']
    
    # Read the data
    data = __get_linegraph_data(area_code, ind_code, param_value)
    if data.empty:
        fig = go.Figure()
        fig.add_annotation(text = """No data for the selected area and indicator""")
        return fig
    
    if not param_value:
        title = f"""{area_name}: <br>{ind_name}"""
    else:
        title = f"""{area_name}: <br>{ind_name} - {param_value}"""
    
    years = data['year'].unique()
    if len(years) > 1:
        x = list(data["year"])
        y = list(data["numeric_value"])
        y_lower = list(data["low"])
        y_upper = list(data["high"])
        
        fig = go.Figure([
                go.Scatter(
                           x=x,
                           y=y,
                           line=dict(color='rgb(0,100,80)'),
                           mode='lines',
                           name='Value',
                           hovertemplate='Value: %{y:.1f}'+'<br>Year: %{x}')
                ,
                go.Scatter(
                           x=x+x[::-1], # x, then x reversed
                           y=y_upper+y_lower[::-1], # upper, then lower reversed
                           fill='toself',
                           fillcolor='rgba(0,100,80,0.2)',
                           line=dict(color='rgba(255,255,255,0)'),
                           showlegend=True,
                           name='Low & high bounds',
                           hoverinfo='skip')
                       ])
        fig.update_layout(xaxis_title='Year',
                          yaxis_title='Value',
                          title = title,
                          xaxis = dict(tickmode = 'array',
                                       tickvals = __get_years_tickvals(years)),
                          plot_bgcolor='#ced4da',
                          paper_bgcolor='#ced4da'
                          )
    else:
        y = data[['low','numeric_value','high']].reset_index(drop = True)
        y = list(y.loc[0])
        groups = ['Low','Estimate','High']
        fig = go.Figure(data=[
                        go.Bar(name='SF Zoo', x=groups, y=y)
                        ])
        # Change the bar mode
        fig.update_layout(barmode='group',
                          xaxis_title='Area',
                          yaxis_title='Value',
                          title = title,
                          plot_bgcolor='#ced4da',
                          paper_bgcolor='#ced4da'
                          )
    return fig

### - Callback: Update world graph, based on inputs
@app.callback(
    dash.dependencies.Output(component_id = 'globe_graph', component_property = 'figure'),
     [dash.dependencies.Input(component_id = 'indicator_dropdown', component_property = 'value'),
      dash.dependencies.Input(component_id = 'parameter_dropdown', component_property = 'value'),
     ])

def __update_globe_graphic(ind_code, param_value):
    """Helper to render a world heat map based on the indicator selected"""
    indicator_name = indicators.loc[indicators['indicator_code'] == ind_code].reset_index(drop = True).loc[0].indicator_name
    
    data = __get_worldmap_data(ind_code, param_value)
    data.drop_duplicates(subset = ['area_code','year','numeric_value'], inplace = True)
    # Deal with no data cases. This does sometimes happen unfortunately (as data is available at a more granular level)
    if data.empty:
        fig = go.Figure(data = go.Choropleth(locations = areas['area_code']))
        fig.add_annotation(text = 'No data available at the top level. Select a lower granularity')
        return fig
    
    # Cut to just the latest year for all areas. Could pass this upstream into the SQL, but it's easier to handle the edge case here 
    if list(data.year.unique()) == [None]:
        max_year = 'Unknown'
    else:
        max_year = int(data['year'].max())
        max_yr_df = data.groupby('area_code')['year'].max().reset_index()
        data = max_yr_df.merge(data, on = ['area_code','year'], how = 'left', validate = 'one_to_one')
    
    if not param_value:
        title_text=f'{indicator_name}: <br>Data up to {max_year}'
    else:
        title_text=f'{indicator_name} - {param_value}: <br>Data up to {max_year}'
        
    text = 'Country: ' + data['area_name'] + '<br>Year: ' +\
            data['year'].astype(int).astype(str) +\
                '<br>Value: ' + data['numeric_value'].round(1).astype(str)
    
    fig = go.Figure(data=go.Choropleth(locations = data['area_code'],
                                       z = data['numeric_value'],
                                       text = text,
                                       colorscale = 'Blues',
                                       autocolorscale=False,
                                       reversescale=False,
                                       marker_line_color='darkgray',
                                       marker_line_width=0.5,
                                       colorbar_title = 'Value',
                                       hovertext = text,
                                       hoverinfo = 'text'
                                       )
                    )
    fig.update_layout(title_text=title_text,
                      geo=dict(showframe=False,
                               showcoastlines=False,
                               projection_type='equirectangular'
                               ),
                      plot_bgcolor='#ced4da',
                      paper_bgcolor='#ced4da'
                     )
    
    return fig
###############################################################################
# - Callbacks to update graphics
###############################################################################



# Startup app on running module
if __name__ == "__main__":
    main()
    app.run_server(debug=True)
Main application file for a Dash dashboard using python    -----------------------------------  Created on Fri Mar 12 10:34:31 2021  @author: matthew.mcfahn ############################################################################## #   - Setup data needed for app, and helper functions from database connection ############################################################################## ############################################################################## #   - Setup data needed for app ############################################################################## ############################################################################## #   - Main Dash app functions ############################################################################## # Initalise Dash app  class Just testing # Set defaults to be loaded # First \div element # Define the row element. This'll have two cols # Define the left element #height/space between dropdown options #dropdown value selected automatically when page loads #disable dropdown value selection #allow multiple dropdown values to be selected #allow user-searching of dropdown values #remembers the value searched in dropdown #gray, default text shown when no option is selected #allow user to removes the selected value #use dictionary to define CSS styles of your dropdown #'memory': browser tab is refreshed #height/space between dropdown options #dropdown value selected automatically when page loads #disable dropdown value selection #allow multiple dropdown values to be selected #allow user-searching of dropdown values #remembers the value searched in dropdown #gray, default text shown when no option is selected #allow user to removes the selected value #use dictionary to define CSS styles of your dropdown # className='select_box',           #activate separate CSS document in assets folder # persistence=True,                 #remembers dropdown value. Used with persistence_type # persistence_type='memory'         #remembers dropdown value selected until... # Optional 'parameter type' dropdown #height/space between dropdown options #dropdown value selected automatically when page loads #disable dropdown value selection #allow multiple dropdown values to be selected #allow user-searching of dropdown values #remembers the value searched in dropdown #gray, default text shown when no option is selected #allow user to removes the selected value #use dictionary to define CSS styles of your dropdown # className='select_box',           #activate separate CSS document in assets folder # persistence=True,                 #remembers dropdown value. Used with persistence_type # persistence_type='memory'         #remembers dropdown value selected until... # Optional 'parameter' dropdown #height/space between dropdown options #dropdown value selected automatically when page loads #disable dropdown value selection #allow multiple dropdown values to be selected #allow user-searching of dropdown values #remembers the value searched in dropdown #gray, default text shown when no option is selected #allow user to removes the selected value #use dictionary to define CSS styles of your dropdown # className='select_box',           #activate separate CSS document in assets folder # persistence=True,                 #remembers dropdown value. Used with persistence_type # persistence_type='memory'         #remembers dropdown value selected until... # Define the right element #height/space between dropdown options #dropdown value selected automatically when page loads #disable dropdown value selection #allow multiple dropdown values to be selected #allow user-searching of dropdown values #remembers the value searched in dropdown #gray, default text shown when no option is selected #allow user to removes the selected value #use dictionary to define CSS styles of your dropdown #'memory': browser tab is refreshed ############################################################################### # - Callbacks to update dropdowns ############################################################################### ### - Callback: Update area and parameter options, based on indicator Restrict to only areas with a value, and update parameter types. Needs to:         > Update areas options         > Update parameter_type dropdown visibility         > Update parameter_type dropdown OPTIONS         > Update parameter dropdown visibility # Find present country codes, update areas_dict using this # Update visibility based on whether there are any parameter_type options ### - Callback: Update indicator options, based on category Restrict to only indicators with a value ### - Callback: Update parameter options based on parameter_type Helper to get parameter options given the parameter name selected ### - Callback: Update parameter value based on new indicator Helper to get parameter options given the parameter name selected ############################################################################### # - Callbacks to update dropdowns ############################################################################### ############################################################################### # - Callbacks to update graphics ############################################################################### ### - Callback: Update line graph, based on inputs Helper to render a lineplot for the area and indicator # Read the data No data for the selected area and indicator {area_name}: <br>{ind_name} {area_name}: <br>{ind_name} - {param_value} # x, then x reversed # upper, then lower reversed # Change the bar mode ### - Callback: Update world graph, based on inputs Helper to render a world heat map based on the indicator selected # Deal with no data cases. This does sometimes happen unfortunately (as data is available at a more granular level) # Cut to just the latest year for all areas. Could pass this upstream into the SQL, but it's easier to handle the edge case here ############################################################################### # - Callbacks to update graphics ############################################################################### # Startup app on running module