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 if not in config default_colors = { "1": '#E5243B', # No Poverty "2": '#DDA63A', # Zero Hunger "3": '#4C9F38', # Good Health and Well-being "4": '#C5192D', # Quality Education "5": '#FF3A21', # Gender Equality "6": '#26BDE2', # Clean Water and Sanitation "7": '#FCC30B', # Affordable and Clean Energy "8": '#A21942', # Decent Work and Economic Growth "9": '#FD6925', # Industry, Innovation and Infrastructure "10": '#DD1367', # Reduced Inequalities "11": '#FD9D24', # Sustainable Cities and Communities "12": '#BF8B2E', # Responsible Consumption and Production "13": '#3F7E44', # Climate Action "14": '#0A97D9', # Life Below Water "15": '#56C02B', # Life on Land "16": '#00689D', # Peace, Justice and Strong Institutions "17": '#19486A' # Partnerships for the Goals } # Use config colors or fallback to defaults colors = sdg_colors_config or default_colors # Convert string keys to integers return {int(k): v for k, v in colors.items()} # Get colors from configuration 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 # Use goal names as labels for better clarity categories = [f"SDG {i}: {SDG_NAMES[i]}" for i in range(1, 18)] # Robustly extract values as scalars 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) # Close the radar loop values_closed = values + [values[0]] categories_closed = categories + [categories[0]] fig = go.Figure() # Add the radar area 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' # Disable hover for area to avoid interference with markers )) # Add individual markers for each goal with their official colors 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"{SDG_NAMES[goal_id]}
Score: {r:.1f}" )) 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='Year: %{x}
SDG Index: %{y:.2f}' ) 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)] # Melt the dataframe for plotting df_melted = df_filtered.melt( id_vars=['year'], value_vars=cols, var_name='Goal', value_name='Score' ) # Map goal numbers to SDG names and colors 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]}
Year: %{{x}}
Score: %{{y:.1f}}' )) 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