from dash import Dash, dcc, html, Output, Input, State
import plotly.express as px
import pandas as pd
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
# Load the data
df = pd.read_csv("Argentina-bilateral-instruments-1810-2023.csv")
# Filter for years after 2000
df = df[df["Sign year"] >= 2000]
# Initialize the Dash app with Bootstrap theme
app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP], suppress_callback_exceptions=True)
# App Layout
app.layout = html.Div(
children=[
dcc.Interval(id="title-delay", interval=2000, n_intervals=0, max_intervals=10),
# Header Section with Title in a 100% wide container
html.Div(
dbc.Row(
dbc.Col(
html.H1("Argentina's Bilaterals Treaties (from 2000 - 2023)", className="header-title"),
width=12 # Full width for the header
),
justify="center", # Center the row content
),
className="header-container" # 100% width container for header
),
html.Br(),
html.Br(),
# Main chart (only annotation and smooth line)
dcc.Graph(id="main-chart", style={"height": "500px", "width": "80%"}, className="main-chart"),
# Hidden Store to manage modal state
dcc.Store(id="modal-state", data=False),
# Modal (initially hidden)
dbc.Modal([
dbc.ModalHeader("Treaties Details", className="modal-header "),
dbc.ModalBody(id="modal-content", className="modal-body") # Dynamic content
], id="year-modal", is_open=False, className="modal-container")
],
className="layout-container"
)
# Callback to show the title after delay
@app.callback(
Output("header-container", "style"),
Input("title-delay", "n_intervals")
)
def show_title(n):
if n > 0:
return {"display": "block"}
return {"display": "none"}
# Callback to display the modal when a year is clicked
@app.callback(
[Output("year-modal", "is_open"),
Output("modal-content", "children"),
Output("modal-state", "data")], # We only manage modal state
[Input("main-chart", "clickData")],
[State("modal-state", "data")],
prevent_initial_call=True
)
def display_year_modal(clickData, is_open):
if clickData:
selected_year = clickData["points"][0]["x"]
year_df = df[df["Sign year"] == selected_year]
if year_df.empty:
return True, html.P("No data for this year."), True
country_counts = year_df["Counterpart ENG"].value_counts().reset_index()
country_counts.columns = ["Country", "Count"]
total_agreements = year_df.shape[0]
top_country = country_counts.iloc[0]["Country"] if not country_counts.empty else "N/A"
# KPI Cards
kpi_cards = dbc.Row([
dbc.Col(dbc.Card([
dbc.CardBody([
html.H6("Treaties", className="text-uppercase"),
html.H3(f"{total_agreements}", className="text-primary")
])
], className="kpi-card shadow-sm p-3"), width=6),
dbc.Col(dbc.Card([
dbc.CardBody([
html.H6("Top Country", className="text-uppercase"),
html.H3(f"{top_country}", className="text-primary")
])
], className="kpi-card shadow-sm p-3"), width=6)
], className="mb-3")
# Horizontal bar chart
bar_chart = dcc.Graph(
figure=px.bar(
country_counts.sort_values("Count", ascending=True),
x="Count", y="Country", orientation="h",
title="Treaties per Country",
labels={"Count": "Number of Treaties", "Country": "Country"},
color_discrete_sequence=["lightblue"],
template="plotly_dark",
),
style={"height": "500px", "width": "100%"}
)
# Remove Y-axis title from the bar chart
bar_chart.figure.update_layout(
yaxis_title="" # Set the Y-axis title to an empty string to remove it
)
# Modal content
modal_content = html.Div([
html.H5(f"Year: {selected_year}"),
kpi_cards,
bar_chart
])
return True, modal_content, True # Open modal and reset state
return is_open, None, is_open # Keep modal closed if no click data
# Callback to update the main chart
@app.callback(
Output("main-chart", "figure"),
Input("main-chart", "id") # This is just for initialization
)
def update_main_chart(_):
# Aggregate data by year
yearly_data = df.groupby('Sign year').size().reset_index(name='Count')
# Find the max value for annotation
max_value = yearly_data["Count"].max()
max_year = yearly_data[yearly_data["Count"] == max_value]["Sign year"].values[0]
# Create the main chart without bars (just annotation and smooth line)
main_fig = go.Figure()
# Smooth line
smooth_line = go.Scatter(
x=yearly_data["Sign year"],
y=yearly_data["Count"],
mode='lines+markers',
name='Count Line',
line=dict(width=5, color='red'),
marker=dict(color='red', size=15)
)
main_fig.add_trace(smooth_line)
# Annotation for max value
main_fig.add_annotation(
x=max_year,
y=max_value,
text=f"Max Treaties: {max_value} in {max_year}",
showarrow=True,
arrowhead=2,
ax=0,
ay=-50,
font=dict(size=24, color="red", family="Arial"),
bgcolor="white",
arrowcolor="white"
)
# Layout customization
main_fig.update_layout(
margin={"t": 50, "b": 100, "l": 50, "r": 50},
height=500,
showlegend=False,
plot_bgcolor="black",
paper_bgcolor="black",
font=dict(color="white"),
xaxis=dict(showgrid=False, zeroline=False, showline=False),
yaxis=dict(showgrid=False, gridcolor='gray', zeroline=False, showline=False)
)
return main_fig
# Run the app
if __name__ == "__main__":
app.run(debug=True)