Figure Friday 2025 - week 9

join the Figure Friday session on March 7, at noon Eastern Time, to showcase your creation and receive feedback from the community.

Can you find interesting trends in Argentina’s bilateral treaties from 1810-2023?

This week’s data builds on top of the Argentinian government’s Digital Library of Treaties. To explore individual treaties, you can extract a PDF copy of a treaty on their website.

Download data:

  • Go to Bilateral instruments: Argentina and click the button to Download data at the bottom left corner.
  • Save the CSV sheet in the same directory as the Python code provided below (under the sample figure)
  • Update the name of the data file on line 6 of the code, if necessary, and run

Things to consider:

  • what can you improve in the app or sample figure below (Bar Chart)?
  • would you like to tell a different data story using a different graph?
  • can you create a different Dash app?

Sample figure:

Code for sample figure:
from dash import Dash, dcc
import dash_ag_grid as dag
import plotly.express as px
import pandas as pd

df = pd.read_csv("Argentina-bilateral-instruments-1810-2023.csv")

# Filter the dataset to include only the top 20 counterparts
top_20_counterparts = df["Counterpart ENG"].value_counts().nlargest(20).index
filtered_df = df[df["Counterpart ENG"].isin(top_20_counterparts)]
filtered_df = filtered_df.sort_values(by="Counterpart ENG", ascending=True)
fig = px.histogram(filtered_df, x='Counterpart ENG', title="Bilateral international instruments signed by Argentina between 1810 and 2023")
fig.update_layout(xaxis_title="Counterpart")

grid = dag.AgGrid(
    rowData=df.to_dict("records"),
    columnDefs=[{"field": i, 'filter': True, 'sortable': True} for i in df.columns],
    dashGridOptions={"pagination": True}
)

app = Dash()
app.layout = [
    dcc.Markdown("# Argentina Bilateral instruments"),
    grid,
    dcc.Graph(figure=fig)
]


if __name__ == "__main__":
    app.run(debug=True)

Participation Instructions:

  • Create - use the weekly data set to build your own Plotly visualization or Dash app. Or, enhance the sample figure provided in this post, using Plotly or Dash.
  • Submit - post your creation to LinkedIn or Twitter with the hashtags #FigureFriday and #plotly by midnight Thursday, your time zone. Please also submit your visualization as a new post in this thread.
  • Celebrate - join the Figure Friday sessions to showcase your creation and receive feedback from the community.

:point_right: If you prefer to collaborate with others on Discord, join the Plotly Discord channel.

Data Source:

Thank you to Javier I. Santander and to Data Is Plural for the data.

1 Like

Hey everyone,

I’m excited to share my latest project for Figure Friday 2025 Week 9 – an interactive dashboard for Argentina Treaty Data Analysis! This dashboard, built with Dash and Plotly, lets you explore Argentina’s rich treaty history (from 1810 to 2023) through dynamic filtering, interdependent dropdowns, and multiple visualizations (including treemaps, heatmaps, and more).

The app provides deep insights into trends over time, regional distributions, and thematic tag analysis—all styled with an Argentine color scheme.

Looking forward to your feedback and suggestions!
Example of visualization:

7 Likes

@feanor_92 It would be interesting to know what caused the drop in 2020 which many of your visuals show, and why hasn’t the no. of treaties a year recovered more. Besides covid. And another thing I was wondering 'has there been a change in type of treaties over the years and/or a shift in countries/continents". I was thinking about a shift towards asia. In abolute numbers there could be something happening after 2020.

3 Likes

That’s a nice app, @feanor_92 . The use should really choose ether a counterpriast country or region, right? Would there be a reason to select both dropdownS? I see the Region Analysis tab automatically recognizes the region when a country is chosen in the dropdown.

I find the Tags Analysis useful. When it’s a neighboring country the tags are more about borders.

2 Likes

This dashboard uses data matched to the pulldown-selected decade. The bar chart shows the top 10 treaty partners of Argentina for the selected decade. The choropleth map shows the location of all treaty partners of Argentina, also for the selected decade. Hover over the map to see country name, decade and number of treaties between the hovered country and Argentina. In this screenshot, Uruguay (neighboring country) was the top trading partner in the 1880s.

import polars as pl
import plotly.express as px
from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import dash_ag_grid as dag
'''
    This dashboard shows international treaties with Argentina. Data grouped by 
    decade are visualized with a bar chart, a world map and a dash AG table. 
'''

#----- DEFINE CONSTANTS---------------------------------------------------------
citation = (
    'Santander Javier I., La distribución de tratados como reflejo de la ' +
    'política exterior , Revista de Investigación en Política Exterior ' +
    'Argentina, Volumen 3, Número 6, Septiembre - Diciembre 2023.'
)
style_space = {
    'border': 'none', 
    'height': '5px', 
    'background': 'linear-gradient(to right, #007bff, #ff7b00)', 
    'margin': '10px 0'
    }

# these are the columns to show in Dash AG table, in listed order
table_cols = ['YEAR', 'Title', 'PARTNER', 'TREATIES', 'REGION']

#----- DEFINE FUNCTIONS---------------------------------------------------------
def create_bar_plot(df_decade, decade):
    fig = px.bar(
        df_decade
            .unique('PARTNER')
            .sort(['TREATIES', 'PARTNER'], descending =[False, True])
            .tail(10)
            , 
        x='TREATIES', 
        y='PARTNER', 
        title=(
            f"Argentina's Treaty Partners in the {decade}".upper() +
            f'<br><sup>Top 10 or Fewer</sup>'
        ),
        template='simple_white',
        text_auto=True,   # superimpose data on each bar
    )
    fig.update_layout(yaxis_title='')  # Y-axis is obvious, no label needed
    fig.update_traces(  # disable hover traces on the bar chart
        hovertemplate = None,
        hoverinfo='skip'
    )
    return fig

def get_world_map(df_decade, decade):
    fig = px.choropleth(
        data_frame=df_decade,
        locations='PARTNER',
        locationmode='country names',
        color='TREATIES',
        color_continuous_scale='viridis',
        custom_data=['PARTNER', 'DECADE', 'TREATIES'],
        title=f"Argentina's Treaty Partners in the {decade}".upper(),
        projection='equirectangular',
    )
    fig.update_traces(
        hovertemplate="<br>".join([
            "%{customdata[0]} %{customdata[1]}",
            "%{customdata[2]} Treaty(s)",
        ])
    )
    fig.update_layout(
        boxgap=0.25,
        height=500,
        margin=dict(l=10, r=0, b=10, t=100), # , pad=50),
        coloraxis_showscale=False # turn off the legend/color_scale 
    )
    return fig

def get_table_data(df_decade, decade):
    df_table = (
        df_decade
        .select(pl.col('YEAR', 'Title', 'PARTNER','TREATIES', 'REGION'))
        .sort(
            ['TREATIES', 'YEAR', 'PARTNER', 'Title'],
            descending=[True, False, False, False]
        )
    )
    return df_table

#----- LOAD AND CLEAN DATA -----------------------------------------------------
df = (
    pl.scan_csv('Argentina-bilateral-instruments-1810-2023.csv')
    .select(
        'Title', 'Counterpart ENG', 'Region ENG', 'Sign year',
        DECADE = pl.col('Sign date').str.slice(-4,3) + '0s'
    )
    .rename(
        {'Counterpart ENG':'PARTNER',
        'Region ENG': 'REGION',
        'Sign year' : 'YEAR'
        }
    )
    .drop_nulls('PARTNER')
    .with_columns(
        TREATIES = pl.col('PARTNER').count().over('DECADE', 'PARTNER'),
    )
    .sort('DECADE', 'PARTNER')
    .collect()
)

drop_down_list = sorted(list(set(df['DECADE'])))
grid = dag.AgGrid(
    rowData=[],
    columnDefs=[{"field": i, 'filter': True, 'sortable': True} for i in table_cols],
    dashGridOptions={"pagination": True},
    id='data_table'
)

#----- DASH APP ----------------------------------------------------------------
app = Dash(external_stylesheets=[dbc.themes.SANDSTONE])

app.layout = dbc.Container([
    html.Hr(style=style_space),
    html.H2(
        'International Treaties with Argentina by Decade', 
        style={
            'text-align': 'left'
        }
    ),
    html.Hr(style=style_space),
    html.Div([
        html.P(
            f'Citation: {citation}',
            style={
                'text-align': 'left',
                'margin-top': '20px',
                'font-style':
                'italic','font-size':
                '16px',
                'color': 'gray'
                }
            ),
        html.Hr(style=style_space)
    ]),

    dbc.Row(
        [dcc.Dropdown(
            drop_down_list,       # list of choices
            drop_down_list[0],    # use first item on list as default
            id='my_dropdown', 
            style={'width': '50%'}),
        ],
    ),
    
    html.Div(id='dd-output-container'),

    dbc.Row([
        dbc.Col(dcc.Graph(id='bar_plot')),    
        dbc.Col([grid]),

    ]),

    dbc.Row([
        dbc.Col(dcc.Graph(id='world_map'),),  # width=4),
    ])
])

@app.callback(
        Output('bar_plot', 'figure'),
        Output('world_map', 'figure'),
        Output('data_table', 'rowData'),
        Input('my_dropdown', 'value')
)

def update_dashboard(decade):
    df_selected = df.filter(pl.col('DECADE') == decade)
    bar_plot = create_bar_plot(df_selected, decade)
    df_table = get_table_data(df_selected, decade)
    world_map = get_world_map(df_selected, decade)
    return bar_plot, world_map, df_table.to_dicts()

#----- RUN THE APP -------------------------------------------------------------
if __name__ == "__main__":
    app.run(debug=True)
6 Likes

@Mike_Purtell : always jealous of the very readable and structured way you setup your code. Easy to read along.

2 Likes

Thanks for your thoughtful question!
While COVID-19 was a major factor in the 2020 drop, Argentina’s ongoing economic challenges likely slowed the recovery in treaty activity. Also, since we’re only partway through the current decade, the numbers may still adjust as more data comes in. There are hints of shifting priorities—potentially a gradual move toward more treaties with Asian partners—but more detailed analysis of treaty types and regional trends is needed to confirm this.

4 Likes

Thanks! Yes, selecting either a region or a counterpart country makes the most sense. The dual dropdowns allow for flexibility—choosing a region first filters the available countries, while selecting a country automatically sets its region. This way, users can explore treaties at both levels.

Great observation on the Tags Analysis! Neighboring countries indeed have more treaties focused on borders, while agreements with distant partners tend to cover trade, diplomacy, or cooperation in specific sectors.

1 Like

Last Figure Friday we talked about modals, a feature I really liked and in which I see a lot of potential. I’ll improve its functionality by Friday if I can :slight_smile:

7 Likes

Absolutely modals is an amazing feature.Good work

2 Likes

That’s what I though about the dropdowns. Another user-experience possibility is to display only the regions dropdown with an All-regions option. Only once the user selects a region, will the countries dropdown appear.

2 Likes

I like how you added the cards inside the modal :rocket:

2 Likes

I think the best thing about it is that I can also include the chart as a tooltip. I’ve been looking for this for a long time. :ok_hand:

1 Like

It’s hard to believe this is only your second or third Dash app, @Mike_Purtell .

Good choice going for the dropdown. In case you think it would be beneficial to analyze multi-decades in the app you built, you could go for a range slider.

3 Likes

I would consider putting the close button in the upper right corner. Than, on mobile it will also be visibe without the need to scroll.

2 Likes

Thank you for the great suggestion!
I’ve updated the dashboard so that only the regions dropdown is displayed initially—with an “All Regions” option—and the counterparts (countries) dropdown appears only when a specific region is selected.
You can check out the updated dashboard here: Interactive Dashboard for Argentina Treaty Data Analysis.
Thanks again for the valuable feedback!

2 Likes

Hello everyone,

This is my web app/dashboard for week 9.

Here’s a brief overview:

  1. Data Handling: The app processes a CSV file containing treaty information, including cleaning and translating the “Status” column and extracting the first tag from the “Tags” column.

  2. Filtering: Users can filter treaties by:

    • Historical Period (e.g., Independence and Early Development)
    • Region (e.g., America, Europe)
    • Treaty Status (e.g., Active, Terminated)
  3. Visualizations: The app includes two main visualizations:

    • Line Chart: Shows the trend of treaty status over the years, filtered by the selected criteria.
    • Choropleth Map: Displays the number of treaties per country, also filtered by the selected criteria.
  4. Interactive Details: Clicking on a country in the map reveals a “Detailed Treaty Information” section. This section lists the number of treaties for that country, grouped and ordered by their tags (topics). The country name is also included in this section’s title.

Images



Web App Link:

Any comments/suggestion are more than welcome

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

df = pd.read_csv("Argentina-bilateral-instruments-1810-2023.csv", parse_dates=['Sign date'], date_format='mixed')


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]


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

def period_filtered(df, period):
    start, end = periods[period]
    return df[(df['Sign year'] >= start) & (df['Sign year'] <= end)]

radio_style = {
    '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_style ={'text-align': 'center','margin': '10px 0',
               'background': 'linear-gradient(to bottom, #d7f3fc, #FFFFFF, #d7f3fc)'}

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

app.layout = dbc.Container([
    html.H1("An Overall Look at Argentina´s Bilateral Treaty Evolution", style=header_style),
    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 ['America', 'Europe', 'Asia and Oceania', 'Africa and Middle East']],
            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=header_style),
                dcc.Graph(id='treaty-graph')
            ]),
            dbc.Card([
                html.H4("Bilateral Treaties by Region-Country", style=header_style),
                dcc.Graph(id='choropleth-map')
            ])
        ], width=7),

        dbc.Col([
            dbc.Card([
                html.H4("Treaty Status", style=header_style),
                dbc.Checklist(
                    id='status-filter',
                    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"},
                    ],
                    value=['Active'],  
                    inline=True,
                    style=radio_style, 
                ),
            ], style={"margin-bottom": "20px"}),
            dbc.Card([
                html.H4("Detailed Treaty Information", style=header_style),
                html.Div(id='treaty-info', style={"padding": "10px"})
            ])
        ], width=5)  
    ])
], fluid=True)

@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):
    df_filtered = period_filtered(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)]

    
    status_df = (df_region.groupby(['Sign year', 'Status'], as_index=False)
                 ['Counterpart (original)'].count())
    
    fig_line = px.line(status_df, x='Sign year', y='Counterpart (original)',
                       color='Status',
                       labels={'Counterpart (original)': 'Number of Agreements', 'Status': ''},
                       category_orders={"Status": ['Active', 'Active with Modifications', 'Not in Force',
                                                   'Terminated', 'Provisionally Applied']},
                       markers=True, template='xgridoff')
    fig_line.update_layout(
        legend=dict(orientation="h", y=1.1, yanchor='top', x=0.5, xanchor='center')
    )

 
    treaties_by_country = (df_region.groupby(['Counterpart ISO','Counterpart ENG'])
                           .size()
                           .reset_index(name='count'))

    if treaties_by_country.empty:  
        fig_map = px.choropleth(title="No data available for selected options")
        fig_map.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})  # Ajustar márgenes si es necesario
    else:
        fig_map = px.choropleth(treaties_by_country,
                                locations='Counterpart ISO',
                                locationmode="ISO-3",
                                color='count',
                                hover_name='Counterpart ENG',
                                labels={'count': 'Number of Treats'},
                                projection="aitoff",
                                color_continuous_scale="haline")

        fig_map.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')
        )

    country_info = "Click on a country in the map to see treaty details."
    country_name = ""  

    if clickData:
        country_iso = clickData['points'][0]['location']
        country_treaties = df_region[df_region['Counterpart ISO'] == country_iso]

        if not country_treaties.empty:
            country_name = country_treaties['Counterpart ENG'].iloc[0]  

        
        treaties_by_tag = (country_treaties.groupby('Tags')
                           .size()
                           .reset_index(name='count')
                           .sort_values('count', ascending=False))

        if not treaties_by_tag.empty:
            country_info = html.Div([  # Contenedor para el título y la información
                html.H6(f"Treaties with {country_name}"),
                html.H6(f"Number of Treaties by Topic: "),
                html.Ul([
                    html.Li(f"{row['Tags']}: {row['count']}")
                    for _, row in treaties_by_tag.iterrows()
                ])
            ])
        else:
            country_info = html.Div([
                html.H6(f"Treaties with {country_name}"),
                "No treaty information available for this country with the current filters."
            ])
    
    return fig_line, fig_map, country_info

   

if __name__ == '__main__':
    app.run_server(debug=True)type or paste code here
5 Likes

I like your app, @Avacsiglo21. Is there a way to add the treaty titles to the Detailed Treaty Information section?

2 Likes

Wow, the map is so informative. Look what happens when you select 21st century globalization and active treaties (china 93, usa 47 and spain 56). It suggests an interesting topic to dive into. Great app!

3 Likes