diff --git a/eu_fact_force/dash-app/data/PGP x D4G- Exported Vaccine Data.xlsx b/eu_fact_force/dash-app/data/PGP x D4G- Exported Vaccine Data.xlsx new file mode 100644 index 0000000..c9cbf9c Binary files /dev/null and b/eu_fact_force/dash-app/data/PGP x D4G- Exported Vaccine Data.xlsx differ diff --git a/eu_fact_force/dash-app/pages/pgp.py b/eu_fact_force/dash-app/pages/pgp.py index 94acc39..8db5071 100644 --- a/eu_fact_force/dash-app/pages/pgp.py +++ b/eu_fact_force/dash-app/pages/pgp.py @@ -1,59 +1,63 @@ -from dash import html +# pages/pgp.py + +from dash import html, dcc +from utils.pgpxd4g_graphs import get_figures + +def make_layout(): + + trend, lang, source, themes = get_figures() -def create_layout(): return html.Div( style={ - "backgroundColor": "#FFFFFF", + "backgroundColor": "#F4F6F8", + "padding": "20px", "fontFamily": "Arial", - "padding": "20px" }, - children=[ + # HEADER html.Div( - children=[ - html.H1( - "PGP Dashboard", - style={ - "color": "white", - "textAlign": "center", - "margin": "0" - } - ) - ], + "PGP Dashboard", style={ - "backgroundColor": "#00669B", + "backgroundColor": "#0B5FA5", + "color": "white", "padding": "15px", - "borderRadius": "8px" - } + "borderRadius": "10px", + "textAlign": "center", + "fontSize": "22px", + "fontWeight": "bold", + }, ), - html.Br(), - + html.Div( - children=[ - html.H3("Filters"), - html.P("Add dropdowns / filters here") - ], + "This dashboard summarizes posting dynamics, linguistic distribution, platform sources, and key thematic patterns extracted from the dataset.", style={ - "backgroundColor": "#E6F4F9", - "padding": "15px", - "borderRadius": "8px" - } + "textAlign": "center", + "marginTop": "6px", + "marginBottom": "16px", + "color": "#6B7280", + "fontSize": "12.5px", + "fontStyle": "italic", + }, ), - - html.Br(), - + + + # GRAPHS GRID PROPRE html.Div( + style={ + "display": "grid", + "gridTemplateColumns": "1fr 1fr", + "gap": "15px", + }, children=[ - html.H3("Visualizations"), - html.P("Graphs will be added here") + + dcc.Graph(figure=trend), + dcc.Graph(figure=lang), + + dcc.Graph(figure=source), + dcc.Graph(figure=themes), ], - style={ - "backgroundColor": "#F5F5F5", - "padding": "20px", - "borderRadius": "8px" - } - ) - ] + ), + ], ) \ No newline at end of file diff --git a/eu_fact_force/dash-app/utils/pgpxd4g_graphs.py b/eu_fact_force/dash-app/utils/pgpxd4g_graphs.py new file mode 100644 index 0000000..98cb2f8 --- /dev/null +++ b/eu_fact_force/dash-app/utils/pgpxd4g_graphs.py @@ -0,0 +1,125 @@ +import pandas as pd +import plotly.graph_objects as go + +DATA_PATH = "data/PGP x D4G- Exported Vaccine Data.xlsx" # Correct to the actual Excel file + +# ── Load all sheets ──────────────────────────────────────────────── +xl = pd.ExcelFile(DATA_PATH) + +df_trend = pd.read_excel(xl, "Trendline") +df_trend.columns = ["date", "posts"] +df_trend["date"] = pd.to_datetime(df_trend["date"]) + +df_lang = pd.read_excel(xl, "Language") +df_lang.columns = ["language", "posts", "share"] +df_lang = df_lang.dropna(subset=["language", "posts"]) +df_lang = df_lang[df_lang["posts"].apply(lambda x: str(x).isdigit() or isinstance(x, (int, float)))] +df_lang["posts"] = pd.to_numeric(df_lang["posts"], errors="coerce") +df_lang = df_lang.dropna(subset=["posts"]).head(8) + +df_source = pd.read_excel(xl, "Source") +df_source.columns = ["source", "mentions"] +df_source["mentions"] = pd.to_numeric(df_source["mentions"], errors="coerce").fillna(0) +df_source = df_source.dropna(subset=["source", "mentions"]) + +df_themes_raw = pd.read_excel(xl, "Crosstab- themes across all vac") + +# Corrected processing for df_th +df_th = df_themes_raw.drop(columns=['Topics \\ Themes']).T.reset_index() +df_th.columns = ['theme', 'mentions'] +df_th['mentions'] = pd.to_numeric(df_th['mentions'], errors='coerce') +df_th = df_th.dropna(subset=["mentions"]) +df_th = df_th.sort_values("mentions", ascending=False) + +PALETTE = ["#1E5AA8", "#2F6FB6", "#5C8FD6", "#E6EEF8"] +BG = "#FFFFFF" +PAPER = "#FFFFFF" +FONT_COLOR = "#333333" +GRID_COLOR = "#E0E0E0" + + +def base_layout(title): + return dict( + title=dict(text=title, font=dict(size=16, color=FONT_COLOR), x=0.02), + paper_bgcolor=PAPER, + plot_bgcolor=BG, + font=dict(family="Inter, sans-serif", color=FONT_COLOR), + margin=dict(l=50, r=30, t=60, b=50), + ) + + +# ── 1. Daily post volume trendline ───────────────────────────── +fig_trend = go.Figure() +fig_trend.add_trace(go.Scatter( + x=df_trend["date"], y=df_trend["posts"], + mode="lines+markers", + line=dict(color=PALETTE[0], width=2), + marker=dict(size=5, color=PALETTE[0]), + fill="tozeroy", + fillcolor="rgba(30,90,168,0.15)", + name="Posts", + hovertemplate="%{x|%b %d}
%{y:,} posts", +)) +fig_trend.update_layout( + **base_layout("Daily Post Volume — March–April 2026"), + xaxis=dict(showgrid=False, tickformat="%b %d", tickcolor=GRID_COLOR), + yaxis=dict(showgrid=True, gridcolor=GRID_COLOR, tickformat=","), +) + +# ── 2. Language donut ─────────────────────────────────── +fig_lang = go.Figure(go.Pie( + labels=df_lang["language"], + values=df_lang["posts"], + hole=0.55, + marker=dict(colors=PALETTE), + textinfo="label+percent", + hovertemplate="%{label}
%{value:,} posts (%{percent})", +)) +fig_lang.update_layout( + **base_layout("Post Distribution by Language"), + showlegend=False, +) + +# Create a color list for df_source that matches its length +source_colors = [] +for i in range(len(df_source)): + source_colors.append(PALETTE[i % len(PALETTE)]) + +# ── 3. Source donut chart ─────────────────────────────────── +fig_source = go.Figure(go.Pie( + labels=df_source["source"].tolist(), + values=df_source["mentions"].tolist(), + hole=0.55, + marker=dict(colors=source_colors), # Use the explicit color list + textinfo="label+percent", # Reverted to include percentage + hovertemplate="%{label}
%{value:,} mentions (%{percent})", # Reverted to include percentage +)) +fig_source.update_layout( + **base_layout("Mentions by Platform"), + showlegend=False, +) + +# ── 4. Anti-vaccine themes treemap ───────────────────── +# Define a colorscale using the PALETTE in reverse order to map higher mentions to darker blues +gradient_colors = [[0, PALETTE[3]], [0.33, PALETTE[2]], [0.66, PALETTE[1]], [1, PALETTE[0]]] +fig_themes = go.Figure(go.Treemap( + labels=df_th["theme"], + parents=[""] * len(df_th), + values=df_th["mentions"], + # Assign the 'mentions' column to marker.colors for the gradient effect + marker=dict(colors=df_th["mentions"], colorscale=gradient_colors, showscale=False), + hovertemplate="%{label}
%{value:,} mentions (%{percent parent})", + textinfo="label+percent parent" +)) +fig_themes.update_layout(**base_layout("Anti-vaccine Themes — All Vaccines")) +fig_themes.update_layout(margin=dict(l=10, r=10, t=60, b=10)) + +__all__ = ["get_figures"] + +def get_figures(): + return ( + fig_trend, + fig_lang, + fig_source, + fig_themes + ) \ No newline at end of file