Figure Friday 2025 - week 9

Thank you, Marianne. I completely agree. There are numerous avenues to explore, for example, the Commerce Treaty.

1 Like

Yes, there is a solution. I had previously implemented this using the treaty signing date. However, the title’s length necessitates further work on its presentation.

2 Likes

A proof that a player function might look fancy but doesn’t give much insight to every brain, my head can not handle this type of visualization, but I wanted to do it one time. Active + year > 2000. I only had to adjust the dataset.

useless-animation

5 Likes

Finally, I removed the Close button from the Modal because there is an X in the top right corner and I think that is enough.

5 Likes

I really like the color.:sparkles:

1 Like

We are on the same page Marianne. Animation is great but not for this purpose in my opinion

1 Like

I sometimes, like today, have a look at this page: Built-in continuous color scales in Python (25% page, all the colorscales you can choose from before you create your own), {name}_r => reverse scale.

4 Likes

I agree @marieanne . It’s hard for an animated graph/map to provide insight. It’s more like eye candy and it’s helpful to see general trends over time, but usually I end up stopping the animation and using the feature as a Slider to compare between the years I’m interested in.

1 Like

That was a good idea, @Ester . No need for two close buttons. Modal looks good.

2 Likes

Hi Adams,

As you suggested/requested, I’ve updated the Detailed Treaty Information by adding the treaty titles (see attached image). I’ve also updated this information in Py Cafe so you can experiment if you’d like.

The length of the titles presents a challenge for creating an attractive web app. On the other hand, there are a few tweaks I’d like to make, though not immediately:

  1. Close the button by clicking on it.
  2. Sorting by values instead of by index
  3. Address the title length issues. I’m wondering if using modals, or some other approach, might work.
  4. Translate

Regards

updated code

import dash
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd
from dash.exceptions import PreventUpdate
from functools import lru_cache

# Use lru_cache to cache data loading
@lru_cache(maxsize=1)
def load_data():
    df = pd.read_csv("Argentina-bilateral-instruments-1810-2023.csv")
    
    status_dict = {
        'Vigente': 'Active',
        'Extinguido': 'Terminated',
        'No está en vigor': 'Not in Force',
        'Vigente con Modificaciones': 'Active with Modifications',
        'En aplicación provisional': 'Provisionally Applied'
    }
    
    df['Status'] = df['Status'].replace(status_dict).fillna('Not Available')
    df['Tags'] = df.Tags.str.split('\n', expand=True)[0].fillna('-')
    
    return df

# Load data
df = load_data()

# Constants
PERIODS = {
    'Independence and Early Development': (1810, 1852),
    'National Unification and Expansion': (1852, 1916),
    '20th Century Challenges': (1916, 2000),
    '21st Century Globalization': (2000, 2023)
}

REGIONS = ['America', 'Europe', 'Asia and Oceania', 'Africa and Middle East']

STATUS_OPTIONS = [
    {"label": "Active", "value": "Active"},
    {"label": "Active with Modifications", "value": "Active with Modifications"},
    {"label": "Not in Force", "value": "Not in Force"},
    {"label": "Terminated", "value": "Terminated"},
    {"label": "Provisionally Applied", "value": "Provisionally Applied"},
]

# Styles
STYLES = {
    'radio': {
        'display': 'flex',
        'flex-direction': 'row',
        'justify-content': 'space-between',
        'padding': '5px',
        'border-radius': '5px',
        'boxShadow': '3px 3px 3px rgba(177, 250, 249, 0.3)',
        'font-family': 'Aharoni, sans-serif',
        'font-size': '20px',
    },
    'header': {
        'text-align': 'center',
        'margin': '10px 0',
        'background': 'linear-gradient(to bottom, #d7f3fc, #FFFFFF, #d7f3fc)'
    }
}

# Helper functions
def filter_by_period(df, period):
    """Filter dataframe by historical period"""
    start, end = PERIODS[period]
    return df[(df['Sign year'] >= start) & (df['Sign year'] <= end)]

def create_treaty_line_chart(df_filtered):
    """Create line chart for treaty status over time"""
    # Prepare the line chart data - more efficient groupby
    status_df = df_filtered.groupby(['Sign year', 'Status']).size().reset_index(name='count')
    
    fig = px.line(
        status_df, 
        x='Sign year', 
        y='count',
        color='Status',
        labels={'count': 'Number of Agreements', 'Status': ''},
        category_orders={"Status": ['Active', 'Active with Modifications', 'Not in Force',
                                    'Terminated', 'Provisionally Applied']},
        markers=True, 
        template='xgridoff'
    )
    
    fig.update_layout(
        legend=dict(orientation="h", y=1.1, yanchor='top', x=0.5, xanchor='center')
    )
    
    return fig

def create_choropleth_map(df_filtered):
    """Create choropleth map of treaties by country"""
    # More efficient groupby
    treaties_by_country = df_filtered.groupby(['Counterpart ISO', 'Counterpart ENG']).size().reset_index(name='count')
    
    if treaties_by_country.empty:
        fig = px.choropleth(title="No data available for selected options")
        fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
    else:
        fig = px.choropleth(
            treaties_by_country,
            locations='Counterpart ISO',
            locationmode="ISO-3",
            color='count',
            hover_name='Counterpart ENG',
            labels={'count': 'Number of Treaties'},
            projection="aitoff",
            color_continuous_scale="haline"
        )
        
        fig.update_layout(
            margin={"r": 0, "t": 0, "l": 0, "b": 0},
            coloraxis_colorbar=dict(len=0.5, thickness=15, orientation="h", y=-0.15, x=0.5, xanchor='center')
        )
    
    return fig

def create_treaty_accordion(country_treaties):
    """Create an accordion with treaty information grouped by tags"""
    if country_treaties.empty:
        return html.Div("No treaty information available for the selected country with current filters.")
    
    country_name = country_treaties['Counterpart ENG'].iloc[0]
    
    # Group treaties by tag
    treaties_by_tag = country_treaties.groupby('Tags')['Title'].apply(list).reset_index()
    
    # Create accordion items
    accordion_items = []
    for _, row in treaties_by_tag.iterrows():
        topic = row['Tags']
        titles = row['Title']
        
        accordion_items.append(
            dbc.AccordionItem(
                html.Ul([html.Li(title) for title in titles], className="ps-3"),
                title=f"{topic} ({len(titles)})",
                style={"background": "#edfbff"}
            )
        )
    
    # Create the full accordion component
    return html.Div([
        html.H5(f"Treaties with {country_name}", className="mb-3"),
        html.H6("Treaties by Topic:", className="mb-2"),
        dbc.Accordion(
            accordion_items,
            start_collapsed=True,
            flush=True
        )
    ])

# Initialize the Dash app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.ZEPHYR])
app.title = 'Argentina Bilateral Treaties'

# App layout
app.layout = dbc.Container([
    html.H1("An Overall Look at Argentina's Bilateral Treaty Evolution", style=STYLES['header']),
    
    dbc.Row([
        dbc.Col([
            dbc.Select(
                id='period-dropdown',
                options=[{'label': period, 'value': period} for period in PERIODS],
                value='National Unification and Expansion'
            ),
            dbc.Tooltip("Select A Period", target="period-dropdown")
        ], width=3),
        
        dbc.Col([
            dbc.Select(
                id='region-select',
                options=[{'label': region, 'value': region} for region in REGIONS],
                value='America'
            ),
            dbc.Tooltip("Select A Region", target="region-select")
        ], width=3),
    ], style={'margin-bottom': '20px'}),
    
    html.Hr(),
    
    dbc.Row([
        dbc.Col([
            dbc.Card([
                html.H4("Treaty Status Over the Years", style=STYLES['header']),
                dcc.Graph(id='treaty-graph')
            ], className="mb-4"),
            dbc.Card([
                html.H4("Bilateral Treaties by Region-Country", style=STYLES['header']),
                dcc.Graph(id='choropleth-map')
            ])
        ], width=7),

        dbc.Col([
            dbc.Card([
                html.H4("Treaty Status", style=STYLES['header']),
                dbc.Checklist(
                    id='status-filter',
                    options=STATUS_OPTIONS,
                    value=['Active'],  
                    inline=True,
                    style=STYLES['radio'], 
                ),
            ], className="mb-4"),
            
            dbc.Card([
                html.H4("Detailed Treaty Information", style=STYLES['header']),
                html.Div(id='treaty-info', style={"padding": "15px"}, className="treaty-info-panel")
            ])
        ], width=5)  
    ])
], fluid=True)

# Callbacks
@app.callback(
    [Output('treaty-graph', 'figure'),
     Output('choropleth-map', 'figure'),
     Output('treaty-info', 'children')],
    [Input('period-dropdown', 'value'),
     Input('region-select', 'value'),
     Input('status-filter', 'value'),
     Input('choropleth-map', 'clickData')]
)
def update_graphs(selected_period, region, status_filter, clickData):
    # If no inputs were triggered (first load), use default values
    if not dash.callback_context.triggered:
        raise PreventUpdate
    
    # Filter data based on selections
    df_filtered = filter_by_period(df, selected_period)
    df_region = df_filtered[df_filtered['Region ENG'] == region]
    
    if status_filter:
        df_region = df_region[df_region['Status'].isin(status_filter)]
    
    # Create figures
    fig_line = create_treaty_line_chart(df_region)
    fig_map = create_choropleth_map(df_region)
    
    # Default treaty info message
    treaty_info = html.Div([
        html.P("Click on a country in the map to see treaty details.", className="text-muted"),
        html.Div(id="country-treaties")
    ])
    
    # Handle country selection for treaty info
    if clickData:
        country_iso = clickData['points'][0]['location']
        country_treaties = df_region[df_region['Counterpart ISO'] == country_iso]
        treaty_info = create_treaty_accordion(country_treaties)
    
    return fig_line, fig_map, treaty_info



if __name__ == '__main__':
    app.run_server(debug=True, jupyter_mode='external')
6 Likes

So nice to see this :star_struck:. Thank you @Avacsiglo21

2 Likes

Hi @Avacsiglo21,
then you could make a modal, I would love to see your version, there were a lot of new things in it for me. :grinning_face::thinking:

2 Likes

Thanks @adamschroeder,
I’ll try to make it more mobile-friendly next time, but then I’ll start the task differently. Anyway, the modal that popped up on my phone inspired me to do this.:slightly_smiling_face:

1 Like

Hi @Avacsiglo21

I think what you were looking for when you created the Detailed Treaty Information was this component: Accordion - dbc docs
It wouldn’t look much different but works a little smoother. You can make it as dynamic as you want (also the title).

I used this for translating the tags, Topics in you dashboard?

from deep_translator import GoogleTranslator
#translated tags with google deep_translator
df_tags_raw = df['Tags'].str.split('\n+', expand=True).stack().value_counts().reset_index()
df_tags_raw["english_tags"] = df_tags_raw["index"].apply(lambda x: GoogleTranslator(source="es", target="en").translate(x))

It all went very smooth and fast, although I didn’t try the titles. The idea was to summarize all those tags into the 5 or distinct 6 categories. “Other” stayed very significant :slight_smile:

2 Likes

Imagine Thanks you all. :muscle: :muscle:

1 Like

Well I ´gonna try and for sure I will share the code, but I´m not sure if possible. :face_with_tears_of_joy: :winking_face_with_tongue:

2 Likes

Excellent Marianne, thanks for sharing the code. It might work, but the problem is that the titles are very long. When you have many topics, it expands too far down, making it difficult and unattractive to read. By the way, I’m going to update the code there in the post.

1 Like

Thank you so much @marieanne . Easy for me to forget how things work in my own code if I don’t clean it up and try to make it readable.

Thank you for your kind words and encouragement @adamschroeder. I had hoped to add a range slider or other tool that could select more than 1 decade but ran out of time. As my dashboard skills are evolving, I have this on my short list of tools to add to my skill set and expect to use that in a near future Figure Friday.

1 Like