feat: add shared styling constants and _base_layout() helper (Task A.1)
Add module-level constants (CHART_FONT_FAMILY, CHART_TITLE_SIZE, CHART_TITLE_COLOR, GRID_COLOR, ANNOTATION_COLOR, TRUST_PALETTE, DRUG_PALETTE) and _base_layout() helper to DRY shared layout properties across all chart functions. Apply to create_icicle_from_nodes as proof-of-concept.
This commit is contained in:
@@ -29,7 +29,7 @@ Comprehensive review and improvement of all Plotly charts in the Dash dashboard.
|
|||||||
## Phase A: Core Fixes + Shared Constants
|
## Phase A: Core Fixes + Shared Constants
|
||||||
|
|
||||||
### A.1 Extract shared styling constants + `_base_layout()` helper
|
### A.1 Extract shared styling constants + `_base_layout()` helper
|
||||||
- [ ] Add module-level constants to top of `src/visualization/plotly_generator.py`:
|
- [x] Add module-level constants to top of `src/visualization/plotly_generator.py`:
|
||||||
```python
|
```python
|
||||||
CHART_FONT_FAMILY = "Source Sans 3, system-ui, sans-serif"
|
CHART_FONT_FAMILY = "Source Sans 3, system-ui, sans-serif"
|
||||||
CHART_TITLE_SIZE = 18
|
CHART_TITLE_SIZE = 18
|
||||||
@@ -48,8 +48,8 @@ Comprehensive review and improvement of all Plotly charts in the Dash dashboard.
|
|||||||
"#F6B26B", "#8E7CC3", "#C27BA0", "#76A5AF", "#FFD966",
|
"#F6B26B", "#8E7CC3", "#C27BA0", "#76A5AF", "#FFD966",
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
- [ ] Create `_base_layout(title, **overrides)` helper returning a dict with shared layout properties (title font, hoverlabel, paper/plot bgcolor, autosize, font family)
|
- [x] Create `_base_layout(title, **overrides)` helper returning a dict with shared layout properties (title font, hoverlabel, paper/plot bgcolor, autosize, font family)
|
||||||
- [ ] Apply `_base_layout()` to `create_icicle_from_nodes()` as a proof-of-concept (keep all existing behavior, just DRY the layout dict)
|
- [x] Apply `_base_layout()` to `create_icicle_from_nodes()` as a proof-of-concept (keep all existing behavior, just DRY the layout dict)
|
||||||
- **Checkpoint**: `python run_dash.py` starts, icicle chart unchanged visually
|
- **Checkpoint**: `python run_dash.py` starts, icicle chart unchanged visually
|
||||||
|
|
||||||
### A.2 Fix heatmap colorscale + cell annotations (Patient Pathways)
|
### A.2 Fix heatmap colorscale + cell annotations (Patient Pathways)
|
||||||
|
|||||||
@@ -74,3 +74,32 @@ Working Dash application with 2 views (Patient Pathways + Trust Comparison), 13
|
|||||||
- _dosing_by_drug interpolates from one blue to another blue
|
- _dosing_by_drug interpolates from one blue to another blue
|
||||||
|
|
||||||
## Iteration Log
|
## Iteration Log
|
||||||
|
|
||||||
|
## Iteration 1 — 2026-02-07
|
||||||
|
### Task: A.1 — Extract shared styling constants + `_base_layout()` helper
|
||||||
|
### Why this task:
|
||||||
|
- A.1 is the foundation for all subsequent Phase A tasks (A.2-A.4 all reference `_base_layout()` and the palette constants). Must be done first.
|
||||||
|
### Status: COMPLETE
|
||||||
|
### What was done:
|
||||||
|
- Added 7 module-level constants after `logger` line: `CHART_FONT_FAMILY`, `CHART_TITLE_SIZE`, `CHART_TITLE_COLOR`, `GRID_COLOR`, `ANNOTATION_COLOR`, `TRUST_PALETTE` (7 colors), `DRUG_PALETTE` (15 colors)
|
||||||
|
- Created `_base_layout(title, **overrides)` helper returning dict with: title (font family/size/color, centered), hoverlabel (white bg, #CBD5E1 border, font), paper/plot bgcolor transparent, autosize=True, base font family
|
||||||
|
- Applied `_base_layout()` to `create_icicle_from_nodes()` — replaced 20-line explicit layout block with `_base_layout()` call + 3 overrides (margin, hoverlabel size=14, clickmode)
|
||||||
|
- Also replaced hardcoded `"Source Sans 3, system-ui, sans-serif"` in icicle textfont with `CHART_FONT_FAMILY` constant
|
||||||
|
### Validation results:
|
||||||
|
- Tier 1 (Code): all imports pass, `python run_dash.py` starts cleanly
|
||||||
|
- Tier 2 (Visual): icicle figure title correct ("Patient Pathways — By Directory"), font family/size/color all match expected values
|
||||||
|
### Files changed:
|
||||||
|
- `src/visualization/plotly_generator.py` — added constants + `_base_layout()` + refactored icicle layout
|
||||||
|
- `IMPLEMENTATION_PLAN.md` — marked A.1 subtasks [x]
|
||||||
|
### Committed: 63c1801 "feat: add shared styling constants and _base_layout() helper (Task A.1)"
|
||||||
|
### Patterns discovered:
|
||||||
|
- The `_base_layout()` returns a plain dict that gets unpacked via `fig.update_layout(**layout)`. Callers pass chart-specific overrides as kwargs.
|
||||||
|
- Icicle hoverlabel uses `size=14` (slightly larger than base `13`) — preserved as override.
|
||||||
|
- Constants are at module level, so all functions in the file can reference them directly.
|
||||||
|
- Line numbers in IMPLEMENTATION_PLAN.md are now stale (shifted ~70 lines due to constants/helper insertion). Future iterations should search by function name.
|
||||||
|
### Next iteration should:
|
||||||
|
- Start with Task A.2: Fix heatmap colorscale + cell annotations. Read `create_heatmap_figure()` and `create_trust_heatmap_figure()` functions by searching for their names (line numbers have shifted).
|
||||||
|
- The `_base_layout()` and palette constants are now available — A.2 should use `_base_layout()` for both heatmap functions.
|
||||||
|
- Key heatmap fixes: linear 5-stop colorscale, cell text annotations, zmin=0, autosize, automargin, subtitle when >25 drugs.
|
||||||
|
### Blocked items:
|
||||||
|
- None
|
||||||
|
|||||||
@@ -17,6 +17,78 @@ from core.logging_config import get_logger
|
|||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Shared styling constants
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
CHART_FONT_FAMILY = "Source Sans 3, system-ui, sans-serif"
|
||||||
|
CHART_TITLE_SIZE = 18
|
||||||
|
CHART_TITLE_COLOR = "#1E293B"
|
||||||
|
GRID_COLOR = "#E2E8F0"
|
||||||
|
ANNOTATION_COLOR = "#768692"
|
||||||
|
|
||||||
|
# 7 maximally-distinct colours for trust-comparison charts
|
||||||
|
TRUST_PALETTE = [
|
||||||
|
"#005EB8", # NHS Blue
|
||||||
|
"#DA291C", # Red
|
||||||
|
"#009639", # Green
|
||||||
|
"#ED8B00", # Orange
|
||||||
|
"#7C2855", # Plum
|
||||||
|
"#00A499", # Teal
|
||||||
|
"#330072", # Purple
|
||||||
|
]
|
||||||
|
|
||||||
|
# 15 distinct colours for drug-level charts
|
||||||
|
DRUG_PALETTE = [
|
||||||
|
"#005EB8", "#DA291C", "#009639", "#ED8B00", "#7C2855",
|
||||||
|
"#00A499", "#330072", "#E06666", "#6FA8DC", "#93C47D",
|
||||||
|
"#F6B26B", "#8E7CC3", "#C27BA0", "#76A5AF", "#FFD966",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _base_layout(title: str, **overrides) -> dict:
|
||||||
|
"""Return a dict of shared Plotly layout properties.
|
||||||
|
|
||||||
|
All chart functions should call this to get consistent styling, then
|
||||||
|
update the result with chart-specific overrides.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: Display title for the chart.
|
||||||
|
**overrides: Any key accepted by ``fig.update_layout()``; these are
|
||||||
|
merged on top of the base dict so callers can override margins,
|
||||||
|
height, etc.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict ready to be unpacked into ``fig.update_layout(**layout)``.
|
||||||
|
"""
|
||||||
|
layout = dict(
|
||||||
|
title=dict(
|
||||||
|
text=title,
|
||||||
|
font=dict(
|
||||||
|
family=CHART_FONT_FAMILY,
|
||||||
|
size=CHART_TITLE_SIZE,
|
||||||
|
color=CHART_TITLE_COLOR,
|
||||||
|
),
|
||||||
|
x=0.5,
|
||||||
|
xanchor="center",
|
||||||
|
),
|
||||||
|
hoverlabel=dict(
|
||||||
|
bgcolor="#FFFFFF",
|
||||||
|
bordercolor="#CBD5E1",
|
||||||
|
font=dict(
|
||||||
|
family=CHART_FONT_FAMILY,
|
||||||
|
size=13,
|
||||||
|
color=CHART_TITLE_COLOR,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
paper_bgcolor="rgba(0,0,0,0)",
|
||||||
|
plot_bgcolor="rgba(0,0,0,0)",
|
||||||
|
autosize=True,
|
||||||
|
font=dict(family=CHART_FONT_FAMILY),
|
||||||
|
)
|
||||||
|
layout.update(overrides)
|
||||||
|
return layout
|
||||||
|
|
||||||
|
|
||||||
def create_icicle_figure(ice_df: pd.DataFrame, title: str) -> go.Figure:
|
def create_icicle_figure(ice_df: pd.DataFrame, title: str) -> go.Figure:
|
||||||
"""
|
"""
|
||||||
@@ -204,7 +276,7 @@ def create_icicle_from_nodes(nodes: list[dict], title: str = "") -> go.Figure:
|
|||||||
"<extra></extra>"
|
"<extra></extra>"
|
||||||
),
|
),
|
||||||
textfont=dict(
|
textfont=dict(
|
||||||
family="Source Sans 3, system-ui, sans-serif",
|
family=CHART_FONT_FAMILY,
|
||||||
size=12,
|
size=12,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -212,32 +284,21 @@ def create_icicle_from_nodes(nodes: list[dict], title: str = "") -> go.Figure:
|
|||||||
|
|
||||||
display_title = f"Patient Pathways \u2014 {title}" if title else "Patient Pathways"
|
display_title = f"Patient Pathways \u2014 {title}" if title else "Patient Pathways"
|
||||||
|
|
||||||
fig.update_layout(
|
layout = _base_layout(
|
||||||
title=dict(
|
display_title,
|
||||||
text=display_title,
|
|
||||||
font=dict(
|
|
||||||
family="Source Sans 3, system-ui, sans-serif",
|
|
||||||
size=18,
|
|
||||||
color="#1E293B",
|
|
||||||
),
|
|
||||||
x=0.5,
|
|
||||||
xanchor="center",
|
|
||||||
),
|
|
||||||
margin=dict(t=40, l=8, r=8, b=24),
|
margin=dict(t=40, l=8, r=8, b=24),
|
||||||
hoverlabel=dict(
|
hoverlabel=dict(
|
||||||
bgcolor="#FFFFFF",
|
bgcolor="#FFFFFF",
|
||||||
bordercolor="#CBD5E1",
|
bordercolor="#CBD5E1",
|
||||||
font=dict(
|
font=dict(
|
||||||
family="Source Sans 3, system-ui, sans-serif",
|
family=CHART_FONT_FAMILY,
|
||||||
size=14,
|
size=14,
|
||||||
color="#1E293B",
|
color=CHART_TITLE_COLOR,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
paper_bgcolor="rgba(0,0,0,0)",
|
|
||||||
plot_bgcolor="rgba(0,0,0,0)",
|
|
||||||
autosize=True,
|
|
||||||
clickmode="event+select",
|
clickmode="event+select",
|
||||||
)
|
)
|
||||||
|
fig.update_layout(**layout)
|
||||||
|
|
||||||
fig.update_traces(sort=False)
|
fig.update_traces(sort=False)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user