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