Skip to content

Commit 9532528

Browse files
Merge pull request #16 from ga4gh/dc-iss-15-persona-funder-view
Persona: Funder view design, Home page redesign
2 parents 991cdcb + f85ace9 commit 9532528

9 files changed

Lines changed: 655 additions & 119 deletions

File tree

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copy this file to .env and fill in your values.
2+
# .env is gitignored — never commit credentials.
3+
4+
# URL of the analytics-dashboard backend API
5+
# Local backend: http://localhost:8000
6+
# Staging backend: https://<staging-alb-url>
7+
GA4GH_ANALYTICS_DASHBOARD_API_BASE_URL=http://localhost:8000
8+
9+
# Port the UI runs on locally
10+
GA4GH_ANALYTICS_DASHBOARD_UI_PORT=8050

app/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from app.callbacks.github_callbacks import register_github_callbacks
66
from app.callbacks.home_callbacks import register_home_callbacks
77
from app.callbacks.epmc_callbacks import register_epmc_callbacks
8+
from app.callbacks.persona_callbacks import register_persona_callbacks
89

910
def create_app():
1011
app = Dash(__name__,
@@ -24,7 +25,8 @@ def create_app():
2425
register_pypi_callbacks(app)
2526
register_github_callbacks(app)
2627
register_epmc_callbacks(app)
27-
28+
register_persona_callbacks(app)
29+
2830
return app
2931

3032
app = create_app()

app/assets/style.css

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -182,29 +182,6 @@ text {
182182
color: var(--border-grey);
183183
}
184184

185-
.info-badge {
186-
padding: 8px 12px;
187-
background-color: var(--dark);
188-
color: var(--white);
189-
border-radius: 6px;
190-
font-size: 14px;
191-
}
192-
193-
.info-badges-row {
194-
display: flex;
195-
justify-content: center; /* horizontal centering */
196-
align-items: center; /* vertical alignment */
197-
gap: 1em; /* spacing between badges */
198-
flex-wrap: wrap; /* prevents overflow */
199-
margin:clamp(2em, 4vw, 4em);
200-
}
201-
202-
@media (max-width: 768px) {
203-
.info-badges-row {
204-
flex-direction: column;
205-
align-items: center; /* keep centered */
206-
}
207-
}
208185

209186
.dashboard-text {
210187
font-size: clamp(0.9rem, 1vw, 1.1rem);
@@ -249,13 +226,14 @@ text {
249226
}
250227

251228
.hero-section {
252-
position: relative; /* 👈 REQUIRED */
229+
position: relative;
253230
height: 700px;
254231
background: var(--gradient-green-darkgreen);
255232
display: flex;
256233
align-items: flex-end;
234+
justify-content: space-between;
257235
padding-top: 120px;
258-
overflow: hidden; /* 👈 prevents bleed */
236+
overflow: hidden;
259237
}
260238

261239
.hero-section::before {
@@ -282,20 +260,56 @@ text {
282260
z-index: 1;
283261
}
284262

263+
.hero-right-panel {
264+
display: flex;
265+
flex-direction: column;
266+
align-items: flex-end;
267+
justify-content: flex-end;
268+
gap: 12px;
269+
padding: 0 40px 40px 20px;
270+
z-index: 1;
271+
}
272+
273+
.hero-badge {
274+
display: inline-block;
275+
padding: 8px 14px;
276+
background-color: rgba(255, 255, 255, 0.15);
277+
color: var(--white);
278+
font-size: 13px;
279+
font-weight: 500;
280+
border-radius: 6px;
281+
border: 1px solid rgba(255, 255, 255, 0.3);
282+
text-align: right;
283+
white-space: nowrap;
284+
}
285+
285286
@media (max-width: 768px) {
286287

287288
.hero-section {
288-
height: auto; /* 👈 let it grow naturally on small screens */
289+
height: auto;
290+
flex-direction: column;
291+
align-items: center;
289292
padding-bottom: 40px;
290-
padding-top: 350px;
293+
padding-top: 350px;
294+
}
295+
296+
.hero-right-panel {
297+
align-items: center;
298+
padding: 20px 20px 0 20px;
299+
width: 90%;
300+
}
301+
302+
.hero-badge {
303+
text-align: center;
304+
max-width: 100%;
291305
}
292306

293307
.hero-content-box {
294308
width: 90%;
295-
margin: 0 auto; /* 👈 center horizontally */
296-
margin-bottom: -60px; /* 👈 softer overlap */
309+
margin: 0 auto;
310+
margin-bottom: -60px;
297311
padding: 30px 20px;
298-
text-align: center; /* 👈 center all text */
312+
text-align: center;
299313
margin-top: 2em;
300314
}
301315

@@ -409,4 +423,56 @@ text {
409423

410424
#epmc-authors-card-body {
411425
min-height: auto;
426+
}
427+
428+
/* ---------- Persona Selector ---------- */
429+
430+
.persona-selector-row {
431+
display: flex;
432+
align-items: center;
433+
justify-content: space-between;
434+
gap: 24px;
435+
padding: 20px 0 16px 0;
436+
margin-bottom: 4px;
437+
border-bottom: 2px solid var(--grey);
438+
}
439+
440+
.persona-selector-label {
441+
font-size: 15px;
442+
font-weight: 700;
443+
color: var(--border-grey);
444+
white-space: nowrap;
445+
text-transform: uppercase;
446+
letter-spacing: 0.06em;
447+
}
448+
449+
.persona-btn-group {
450+
display: flex;
451+
flex: 1;
452+
gap: 10px;
453+
}
454+
455+
.persona-btn {
456+
flex: 1 !important;
457+
font-size: 15px !important;
458+
font-weight: 600 !important;
459+
padding: 10px 20px !important;
460+
border-radius: 8px !important;
461+
border: 2px solid var(--darkblue) !important;
462+
color: var(--darkblue) !important;
463+
background-color: transparent !important;
464+
transition: background-color 0.18s, color 0.18s, box-shadow 0.18s;
465+
letter-spacing: 0.02em;
466+
}
467+
468+
.persona-btn:hover {
469+
background-color: var(--darkblue) !important;
470+
color: var(--white) !important;
471+
box-shadow: 0 4px 12px rgba(2, 38, 106, 0.18) !important;
472+
}
473+
474+
.active-persona {
475+
background-color: var(--darkblue) !important;
476+
color: var(--white) !important;
477+
box-shadow: 0 4px 12px rgba(2, 38, 106, 0.22) !important;
412478
}

app/callbacks/persona_callbacks.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from dash import Input, Output, ctx
2+
3+
PERSONA_BUTTONS = [
4+
"persona-btn-default",
5+
"persona-btn-funder",
6+
"persona-btn-researcher",
7+
"persona-btn-developer",
8+
"persona-btn-internal",
9+
]
10+
11+
BUTTON_TO_PERSONA = {
12+
"persona-btn-default": "default",
13+
"persona-btn-funder": "funder",
14+
"persona-btn-researcher": "researcher",
15+
"persona-btn-developer": "developer",
16+
"persona-btn-internal": "internal",
17+
}
18+
19+
# ---------------------------------------------------------------------------
20+
# Master registry of every element the persona system controls.
21+
#
22+
# To add a new graph/KPI:
23+
# 1. Add its layout ID to ALL_SECTION_IDS (html.Div) or ALL_COL_IDS (dbc.Col).
24+
# 2. Add that ID to whichever persona(s) should show it in PERSONA_SHOW below.
25+
# Everything not listed for a persona is hidden automatically.
26+
# ---------------------------------------------------------------------------
27+
28+
# html.Div sections — shown with display:block
29+
ALL_SECTION_IDS = [
30+
"servicemap",
31+
"metrics",
32+
"epmc",
33+
"github",
34+
"pypi",
35+
"tables",
36+
# funder-specific chart sections
37+
"funder-charts",
38+
]
39+
40+
# dbc.Col KPI cards — shown with {} (let Bootstrap flex handle sizing)
41+
ALL_COL_IDS = [
42+
"funder-kpi-yoy",
43+
"funder-kpi-avg-citations",
44+
"funder-kpi-funding-bodies",
45+
]
46+
47+
ALL_CONTROLLED_IDS = ALL_SECTION_IDS + ALL_COL_IDS
48+
49+
# ---------------------------------------------------------------------------
50+
# Per-persona whitelist — only list what IS shown.
51+
# Anything omitted is hidden automatically.
52+
# ---------------------------------------------------------------------------
53+
PERSONA_SHOW = {
54+
"default": {
55+
"sections": ["servicemap", "metrics", "epmc", "github", "pypi", "tables"],
56+
"cols": [],
57+
},
58+
"funder": {
59+
"sections": ["metrics", "epmc", "funder-charts"],
60+
"cols": ["funder-kpi-yoy", "funder-kpi-avg-citations", "funder-kpi-funding-bodies"],
61+
},
62+
}
63+
64+
65+
def register_persona_callbacks(app):
66+
67+
# ------------------------------------------------------------------
68+
# 1. Update the active-persona store when any button is clicked
69+
# ------------------------------------------------------------------
70+
@app.callback(
71+
Output("active-persona", "data"),
72+
[Input(btn_id, "n_clicks") for btn_id in PERSONA_BUTTONS],
73+
prevent_initial_call=True,
74+
)
75+
def update_active_persona(*_):
76+
return BUTTON_TO_PERSONA.get(ctx.triggered_id, "default")
77+
78+
# ------------------------------------------------------------------
79+
# 2. Highlight the active button; reset all others
80+
# ------------------------------------------------------------------
81+
@app.callback(
82+
[Output(btn_id, "className") for btn_id in PERSONA_BUTTONS]
83+
+ [Output(btn_id, "outline") for btn_id in PERSONA_BUTTONS],
84+
Input("active-persona", "data"),
85+
)
86+
def update_button_styles(active_persona):
87+
active = active_persona or "default"
88+
classes = [
89+
"persona-btn active-persona" if BUTTON_TO_PERSONA[b] == active else "persona-btn"
90+
for b in PERSONA_BUTTONS
91+
]
92+
outlines = [BUTTON_TO_PERSONA[b] != active for b in PERSONA_BUTTONS]
93+
return classes + outlines
94+
95+
# ------------------------------------------------------------------
96+
# 3. Show / hide sections based on active persona
97+
# ------------------------------------------------------------------
98+
@app.callback(
99+
[Output(eid, "style") for eid in ALL_CONTROLLED_IDS],
100+
Input("active-persona", "data"),
101+
)
102+
def toggle_persona_sections(active_persona):
103+
persona = active_persona or "default"
104+
config = PERSONA_SHOW.get(persona, PERSONA_SHOW["default"])
105+
shown_sections = set(config["sections"])
106+
shown_cols = set(config["cols"])
107+
return [
108+
{"display": "block"} if eid in shown_sections
109+
else {} if eid in shown_cols
110+
else {"display": "none"}
111+
for eid in ALL_CONTROLLED_IDS
112+
]

app/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import os
2+
from dotenv import load_dotenv
3+
4+
load_dotenv() # reads .env from project root if present; real env vars always win
25

36
class Config:
47

app/constants/api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@
3434
EPMC_GET_AUTHORS_BY_ARTICLE = BASE_API + "/epmc/get-authors-by-article-id/"
3535
EPMC_CITATION_OVER_YEARS = BASE_API + "/epmc/citations-over-years"
3636
EPMC_AFFILIATION_BY_ARTICLE = BASE_API + "/epmc/get-affiliations-by-article-id/"
37+
EPMC_FUNDING_AGENCIES = BASE_API + "/epmc/funding-agencies"

0 commit comments

Comments
 (0)