import dash
from dash import dcc, html, Input, Output
import pandas as pd
import plotly.graph_objects as go
# Load data
df = pd.read_csv('OECD.csv')
df = df[df['Measure'] == "Access to green space"]
df = df[df['Year'].isin([2012, 2018])]
# Create pivot table
pivot_df = df.pivot(index="Country", columns="Year", values="OBS_VALUE").dropna()
# Initialize app
app = dash.Dash(__name__)
# KPI style
kpi_style = {
'width': '30%',
'display': 'inline-block',
'textAlign': 'center',
'margin': '10px',
'padding': '25px',
'borderRadius': '15px',
'boxShadow': '0 8px 16px rgba(0, 0, 0, 0.1)',
'backgroundColor': 'white',
'border': '1px solid #f0f0f0',
'transition': 'all 0.3s ease'
}
kpi_title_style = {
'color': '#2c3e50',
'marginBottom': '15px',
'fontSize': '16px',
'fontWeight': '600',
'textTransform': 'uppercase',
'letterSpacing': '1px'
}
kpi_value_style = {
'fontSize': '28px',
'fontWeight': '700',
'margin': '0'
}
# Layout
app.layout = html.Div([
html.H2("OECD wellbeing: Access to green space (2012 vs 2018)",
style={'textAlign': 'left', 'color': '#2c3e50', 'marginBottom': '10px', 'marginLeft': '20px'}),
html.P("This dashboard compares the percentage of urban population with access to green space across OECD countries between 2012 and 2018. The visualization shows the change in access levels, with countries sorted by their 2018 values. Positive changes indicate improvements in green space accessibility.",
style={'textAlign': 'left', 'color': '#666666', 'marginBottom': '30px', 'marginLeft': '20px', 'fontSize': '14px'}),
# Filters
html.Div([
html.Div([
html.Label("Select Countries", style={'fontWeight': 'bold', 'marginBottom': '10px'}),
dcc.Dropdown(
id='country-dropdown',
options=[{'label': 'Select All', 'value': 'ALL'}] +
[{'label': country, 'value': country} for country in sorted(pivot_df.index)],
value=sorted(pivot_df.index)[:5],
multi=True,
style={'boxShadow': '0 2px 4px rgba(0, 0, 0, 0.1)'}
)
], style={'width': '30%', 'display': 'inline-block', 'margin': '10px'})
]),
# KPIs
html.Div([
html.Div([
html.H4("Best Performing Country", style=kpi_title_style),
html.H2(id='best-country-kpi',
style={**kpi_value_style, 'color': '#2c3e50'})
], style={**kpi_style, 'borderTop': '4px solid #2c3e50'}),
html.Div([
html.H4("Improvement", style=kpi_title_style),
html.H2(id='best-improvement-kpi',
style={**kpi_value_style, 'color': '#2ecc71'})
], style={**kpi_style, 'borderTop': '4px solid #2ecc71'}),
html.Div([
html.H4("Worst Performing Country", style=kpi_title_style),
html.H2(id='worst-country-kpi',
style={**kpi_value_style, 'color': '#2c3e50'})
], style={**kpi_style, 'borderTop': '4px solid #2c3e50'}),
html.Div([
html.H4("Decline", style=kpi_title_style),
html.H2(id='worst-decline-kpi',
style={**kpi_value_style, 'color': '#e74c3c'})
], style={**kpi_style, 'borderTop': '4px solid #e74c3c'})
], style={'marginBottom': '30px', 'display': 'flex', 'justifyContent': 'flex-start', 'marginLeft': '20px'}),
# Graph
dcc.Graph(id="dumbbell-chart", style={'height': '600px'})
])
@app.callback(
[Output("dumbbell-chart", "figure"),
Output('best-country-kpi', 'children'),
Output('best-improvement-kpi', 'children'),
Output('worst-country-kpi', 'children'),
Output('worst-decline-kpi', 'children')],
[Input('country-dropdown', 'value')]
)
def update_chart(selected_countries):
if not selected_countries:
return go.Figure(), "N/A", "N/A", "N/A", "N/A"
# Handle "Select All" option
if 'ALL' in selected_countries:
selected_countries = sorted(pivot_df.index)
# Filter data for selected countries
filtered_df = pivot_df.loc[selected_countries]
# Sort countries by 2018 values in descending order
filtered_df = filtered_df.sort_values(by=2018, ascending=False)
# Calculate KPIs
changes = filtered_df[2018] - filtered_df[2012]
best_country = filtered_df.index[changes.argmax()]
best_improvement = changes.max()
worst_country = filtered_df.index[changes.argmin()]
worst_decline = changes.min()
# Create figure
fig = go.Figure()
# Points - 2012
fig.add_trace(go.Scatter(
x=filtered_df[2012],
y=filtered_df.index,
mode='markers',
name='2012',
marker=dict(
color='#90EE90', # Light green
size=20,
line=dict(width=2, color='white')
)
))
# Points - 2018
fig.add_trace(go.Scatter(
x=filtered_df[2018],
y=filtered_df.index,
mode='markers',
name='2018',
marker=dict(
color='#006400', # Dark green
size=20,
line=dict(width=2, color='white')
)
))
# Lines and annotations
for country in filtered_df.index:
x1, x2 = filtered_df.loc[country, 2012], filtered_df.loc[country, 2018]
y = country
change = changes[country]
# Line
fig.add_trace(go.Scatter(
x=[x1, x2],
y=[y, y],
mode='lines',
line=dict(color='black', width=3),
showlegend=False
))
# Percentage change annotation
fig.add_annotation(
x=x2 + 5, # Position further to the right of the 2018 point
y=y,
text=f"{change:+.1f}%",
showarrow=False,
font=dict(
size=12,
color='#2c3e50'
)
)
fig.update_layout(
title="",
xaxis_title="Percentage of urban population with access",
yaxis_title="",
template="plotly_white",
height=800,
margin={'l': 150, 'r': 150, 't': 50, 'b': 20}, # Increased right margin for annotations
plot_bgcolor='white',
paper_bgcolor='white',
font=dict(family="Arial, sans-serif", size=12),
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
)
)
return fig, best_country, f"+{best_improvement:.1f}%", worst_country, f"{worst_decline:.1f}%"
if __name__ == '__main__':
app.run(debug=True)