| import plotly.express as px |
| import plotly.graph_objects as go |
| import pandas as pd |
| from src.config_manager import get_config |
|
|
| def get_sdg_colors(): |
| """ |
| Get SDG colors from configuration or use defaults. |
| """ |
| config = get_config() |
| sdg_colors_config = config.get('visualization.sdg_colors', {}) |
| |
| |
| default_colors = { |
| "1": '#E5243B', |
| "2": '#DDA63A', |
| "3": '#4C9F38', |
| "4": '#C5192D', |
| "5": '#FF3A21', |
| "6": '#26BDE2', |
| "7": '#FCC30B', |
| "8": '#A21942', |
| "9": '#FD6925', |
| "10": '#DD1367', |
| "11": '#FD9D24', |
| "12": '#BF8B2E', |
| "13": '#3F7E44', |
| "14": '#0A97D9', |
| "15": '#56C02B', |
| "16": '#00689D', |
| "17": '#19486A' |
| } |
| |
| |
| colors = sdg_colors_config or default_colors |
| |
| |
| return {int(k): v for k, v in colors.items()} |
|
|
|
|
| |
| SDG_COLORS = get_sdg_colors() |
|
|
| SDG_NAMES = { |
| 1: 'No Poverty', |
| 2: 'Zero Hunger', |
| 3: 'Good Health', |
| 4: 'Quality Education', |
| 5: 'Gender Equality', |
| 6: 'Clean Water', |
| 7: 'Clean Energy', |
| 8: 'Decent Work', |
| 9: 'Industry & Innovation', |
| 10: 'Reduced Inequalities', |
| 11: 'Sustainable Cities', |
| 12: 'Responsible Consumption', |
| 13: 'Climate Action', |
| 14: 'Life Below Water', |
| 15: 'Life on Land', |
| 16: 'Peace & Justice', |
| 17: 'Partnerships' |
| } |
|
|
| def create_world_map(df): |
| """ |
| Create a Choropleth map for the overall SDG index score. |
| """ |
| fig = px.choropleth( |
| df, |
| locations="country", |
| locationmode="country names", |
| color="sdg_index_score", |
| hover_name="country", |
| hover_data={'sdg_index_score': ':.1f'}, |
| color_continuous_scale=[ |
| [0, '#FF6B6B'], |
| [0.25, '#FFE66D'], |
| [0.5, '#4ECDC4'], |
| [0.75, '#45B7D1'], |
| [1.0, '#2ECC71'] |
| ], |
| title="π Global SDG Index Progress (Latest Year)", |
| labels={'sdg_index_score': 'SDG Index Score'} |
| ) |
| fig.update_layout( |
| geo=dict( |
| showframe=False, |
| showcoastlines=True, |
| coastlinecolor='#ddd', |
| projection_type='equirectangular', |
| bgcolor='rgba(0,0,0,0)', |
| landcolor='#f5f5f5', |
| countrycolor='#fff' |
| ), |
| margin=dict(l=0, r=0, b=0, t=50), |
| paper_bgcolor='rgba(0,0,0,0)', |
| plot_bgcolor='rgba(0,0,0,0)', |
| coloraxis_colorbar=dict( |
| title=dict(text="Score", font=dict(size=14)), |
| tickfont=dict(size=12), |
| len=0.6, |
| thickness=15 |
| ), |
| title=dict(font=dict(size=18, color='#0d4f6c')) |
| ) |
| return fig |
|
|
| def create_radar_chart(df, country, year): |
| """ |
| Create a radar chart for the 17 SDG goals with official colors and labels. |
| """ |
| target_row = df[(df['country'] == country) & (df['year'] == year)] |
| if target_row.empty: |
| return None |
| |
| |
| categories = [f"SDG {i}: {SDG_NAMES[i]}" for i in range(1, 18)] |
| |
| |
| values = [] |
| for i in range(1, 18): |
| col_name = f"goal_{i}_score" |
| if col_name not in target_row.columns: |
| values.append(0.0) |
| continue |
| |
| val = target_row[col_name] |
| if isinstance(val, pd.DataFrame): |
| val = val.iloc[0, 0] |
| elif isinstance(val, pd.Series): |
| val = val.iloc[0] |
| |
| try: |
| val = float(val) |
| if pd.isna(val): val = 0.0 |
| except: |
| val = 0.0 |
| values.append(val) |
| |
| |
| values_closed = values + [values[0]] |
| categories_closed = categories + [categories[0]] |
| |
| fig = go.Figure() |
| |
| |
| fig.add_trace(go.Scatterpolar( |
| r=values_closed, |
| theta=categories_closed, |
| fill='toself', |
| fillcolor='rgba(59, 130, 246, 0.2)', |
| line=dict(color='#3b82f6', width=2), |
| name=f'{country} ({year})', |
| hoverinfo='skip' |
| )) |
| |
| |
| for i, (r, cat) in enumerate(zip(values, categories)): |
| goal_id = i + 1 |
| fig.add_trace(go.Scatterpolar( |
| r=[r], |
| theta=[cat], |
| mode='markers', |
| marker=dict( |
| color=SDG_COLORS.get(goal_id, '#888'), |
| size=12, |
| line=dict(color='white', width=1) |
| ), |
| name=f"SDG {goal_id}", |
| hovertemplate=f"<b>{SDG_NAMES[goal_id]}</b><br>Score: {r:.1f}<extra></extra>" |
| )) |
| |
| fig.update_layout( |
| polar=dict( |
| radialaxis=dict( |
| visible=True, |
| range=[0, 100], |
| tickfont=dict(size=9), |
| gridcolor='#e2e8f0', |
| angle=0, |
| tickangle=0 |
| ), |
| angularaxis=dict( |
| tickfont=dict(size=10, color='#64748b'), |
| gridcolor='#e2e8f0', |
| rotation=90, |
| direction='clockwise' |
| ), |
| bgcolor='rgba(255, 255, 255, 0)' |
| ), |
| showlegend=False, |
| title=dict( |
| text=f"π― {country} SDG Performance ({year})", |
| font=dict(size=18, color='#1e293b'), |
| x=0.5, |
| y=0.95 |
| ), |
| margin=dict(t=80, b=40, l=80, r=80), |
| height=450, |
| paper_bgcolor='rgba(0,0,0,0)' |
| ) |
| return fig |
|
|
| def create_trend_chart(df_filtered): |
| """ |
| Create a multi-line chart for SDG trends with 2025 styling. |
| """ |
| fig = px.line( |
| df_filtered, |
| x="year", |
| y="sdg_index_score", |
| title="π Overall SDG Index Score Trend (2000-2025)", |
| markers=True, |
| line_shape="spline" |
| ) |
| |
| fig.update_traces( |
| line=dict(color='#3b82f6', width=4), |
| marker=dict(size=10, symbol='circle', line=dict(width=2, color='white')), |
| hovertemplate='<b>Year: %{x}</b><br>SDG Index: %{y:.2f}<extra></extra>' |
| ) |
| |
| fig.update_layout( |
| xaxis=dict( |
| title=dict(text="Year", font=dict(size=14)), |
| gridcolor='#f1f5f9', |
| dtick=2 |
| ), |
| yaxis=dict( |
| title=dict(text="Overall Score", font=dict(size=14)), |
| gridcolor='#f1f5f9', |
| range=[ |
| df_filtered['sdg_index_score'].min() * 0.95 if not df_filtered['sdg_index_score'].dropna().empty else 0, |
| df_filtered['sdg_index_score'].max() * 1.05 if not df_filtered['sdg_index_score'].dropna().empty else 100 |
| ] |
| ), |
| annotations=[ |
| dict( |
| text="Data: SDSN 2025 | Unit: Score (0-100)", |
| showarrow=False, |
| xref="paper", yref="paper", |
| x=1, y=-0.2, |
| font=dict(size=10, color="gray") |
| ) |
| ], |
| paper_bgcolor='rgba(0,0,0,0)', |
| plot_bgcolor='rgba(255, 255, 255, 0.5)', |
| hovermode='x unified', |
| height=450 |
| ) |
| |
| return fig |
|
|
| def create_detailed_trend_chart(df_filtered): |
| """ |
| Create a detailed multi-line chart for individual goals. |
| """ |
| cols = [f"goal_{i}_score" for i in range(1, 18)] |
| |
| |
| df_melted = df_filtered.melt( |
| id_vars=['year'], |
| value_vars=cols, |
| var_name='Goal', |
| value_name='Score' |
| ) |
| |
| |
| df_melted['Goal_Num'] = df_melted['Goal'].str.extract(r'goal_(\d+)_score').astype(int) |
| df_melted['Goal_Name'] = df_melted['Goal_Num'].map(lambda x: f"SDG {x}: {SDG_NAMES[x]}") |
| df_melted['Color'] = df_melted['Goal_Num'].map(SDG_COLORS) |
| |
| fig = go.Figure() |
| |
| for goal_num in range(1, 18): |
| goal_data = df_melted[df_melted['Goal_Num'] == goal_num] |
| fig.add_trace(go.Scatter( |
| x=goal_data['year'], |
| y=goal_data['Score'], |
| mode='lines+markers', |
| name=f"SDG {goal_num}", |
| line=dict(color=SDG_COLORS[goal_num], width=2), |
| marker=dict(size=6), |
| hovertemplate=f'{SDG_NAMES[goal_num]}<br>Year: %{{x}}<br>Score: %{{y:.1f}}<extra></extra>' |
| )) |
| |
| fig.update_layout( |
| title=dict( |
| text="π Individual SDG Goals Trends", |
| font=dict(size=16, color='#0d4f6c') |
| ), |
| xaxis=dict( |
| title=dict(text="Year", font=dict(size=13)), |
| gridcolor='#eee' |
| ), |
| yaxis=dict( |
| title=dict(text="Score", font=dict(size=13)), |
| range=[0, 100], |
| gridcolor='#eee' |
| ), |
| legend=dict( |
| orientation='h', |
| yanchor='bottom', |
| y=-0.4, |
| xanchor='center', |
| x=0.5, |
| font=dict(size=10) |
| ), |
| paper_bgcolor='rgba(0,0,0,0)', |
| plot_bgcolor='rgba(248, 250, 252, 0.5)', |
| height=500, |
| margin=dict(b=120) |
| ) |
| |
| return fig |
|
|
|
|