fix: responsive chart heights + rename cost metric labels (Task E.5)
This commit is contained in:
@@ -308,13 +308,11 @@ Comprehensive review and improvement of all Plotly charts in the Dash dashboard.
|
||||
- **Checkpoint**: Click a directorate line → drill into drug-level trends. Back button returns to overview. `python run_dash.py` starts cleanly. PASSED.
|
||||
|
||||
### E.5 Fix chart height to fill viewport
|
||||
- [ ] In `create_trend_figure()` in `plotly_generator.py`: remove explicit `height=500`, let `autosize=True` (from `_base_layout()`) handle it
|
||||
- [ ] For ALL Patient Pathways chart functions (icicle, sankey, heatmap, funnel, depth, scatter, network, timeline, doses): review and remove fixed `height=...` values where appropriate. Replace with responsive height:
|
||||
- For charts with dynamic height (e.g. `max(400, n_bars * 28 + 150)`): keep the dynamic calculation but ensure minimum is high enough to fill viewport
|
||||
- For charts with fixed `height=500`: remove it
|
||||
- [ ] Add CSS rule to ensure `#pathway-chart .js-plotly-plot, #pathway-chart .plot-container` have `height: 100%` to propagate the flex container height to the Plotly div
|
||||
- [ ] Verify the existing CSS flex chain propagates correctly: `.chart-card` → `.dash-loading-callback` → `#chart-container` → `#pathway-chart`
|
||||
- [ ] Rename "Cost" to "Cost per Patient" in any remaining metric toggle labels (heatmap toggles in `chart_card.py` and `trust_comparison.py`)
|
||||
- [x] In `create_trend_figure()` in `plotly_generator.py`: removed explicit `height=500`, `autosize=True` from `_base_layout()` handles it
|
||||
- [x] Reviewed ALL chart functions — removed 4 fixed heights: `create_cost_waterfall_figure()` (500), `create_duration_cost_scatter_figure()` (550), `create_drug_network_figure()` (600), `create_trend_figure()` (500). Kept 13 dynamic heights (`max(...)`, `fig_height`, `dynamic_height`).
|
||||
- [x] Added CSS rules: `#pathway-chart .js-plotly-plot, .plot-container, .svg-container { height: 100% !important }` to propagate flex container height
|
||||
- [x] Verified CSS flex chain: `.chart-card` → `.dash-loading-callback > div` → `#chart-container` → `#pathway-chart` → `.js-plotly-plot` — all flex with `min-height: 0`
|
||||
- [x] Renamed "Cost" to "Cost per Patient" and "Cost p.a." to "Cost per Patient p.a." in heatmap toggles in `chart_card.py` and `trust_comparison.py`
|
||||
- **Checkpoint**: Charts fill available viewport height in Patient Pathways. No fixed 500px cutoff. `python run_dash.py` starts cleanly.
|
||||
|
||||
---
|
||||
@@ -355,9 +353,9 @@ Comprehensive review and improvement of all Plotly charts in the Dash dashboard.
|
||||
- [x] Trends landing page shows directorate-level line chart with metric toggle
|
||||
- [x] Clicking a directorate drills into drug-level trends
|
||||
- [x] Back button returns to directorate overview
|
||||
- [ ] Charts fill available viewport height (no fixed 500px cutoff)
|
||||
- [x] Charts fill available viewport height (no fixed 500px cutoff)
|
||||
- [x] "Cost" renamed to "Cost per Patient" in metric toggles
|
||||
- [ ] `python run_dash.py` starts cleanly
|
||||
- [x] `python run_dash.py` starts cleanly
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -268,6 +268,12 @@ body {
|
||||
#pathway-chart {
|
||||
flex: 1; min-height: 0;
|
||||
}
|
||||
/* Propagate flex height into Plotly-rendered divs when no explicit figure height is set */
|
||||
#pathway-chart .js-plotly-plot,
|
||||
#pathway-chart .plot-container,
|
||||
#pathway-chart .svg-container {
|
||||
height: 100% !important;
|
||||
}
|
||||
.chart-card > .dash-loading-callback,
|
||||
.chart-card > .dash-loading-callback > div {
|
||||
flex: 1; display: flex; flex-direction: column; min-height: 0;
|
||||
|
||||
@@ -86,8 +86,8 @@ def make_chart_card():
|
||||
id="heatmap-metric-toggle",
|
||||
data=[
|
||||
{"value": "patients", "label": "Patients"},
|
||||
{"value": "cost", "label": "Cost"},
|
||||
{"value": "cost_pp_pa", "label": "Cost p.a."},
|
||||
{"value": "cost", "label": "Cost per Patient"},
|
||||
{"value": "cost_pp_pa", "label": "Cost per Patient p.a."},
|
||||
],
|
||||
value="patients",
|
||||
size="xs",
|
||||
|
||||
@@ -84,8 +84,8 @@ def make_tc_dashboard():
|
||||
id="tc-heatmap-metric-toggle",
|
||||
data=[
|
||||
{"value": "patients", "label": "Patients"},
|
||||
{"value": "cost", "label": "Cost"},
|
||||
{"value": "cost_pp_pa", "label": "Cost p.a."},
|
||||
{"value": "cost", "label": "Cost per Patient"},
|
||||
{"value": "cost_pp_pa", "label": "Cost per Patient p.a."},
|
||||
],
|
||||
value="patients",
|
||||
size="xs",
|
||||
|
||||
@@ -826,3 +826,51 @@ Working Dash application with 2 views (Patient Pathways + Trust Comparison), 13
|
||||
- Key files to read: `plotly_generator.py` (search for `height=`), `dash_app/assets/nhs.css` (flex chain), `chart_card.py` (heatmap toggle labels), `trust_comparison.py` (TC heatmap toggle labels)
|
||||
### Blocked items:
|
||||
- None
|
||||
|
||||
## Iteration 21 — 2026-02-07
|
||||
### Task: E.5 — Fix chart height to fill viewport + rename metric toggle labels
|
||||
### Why this task:
|
||||
- E.5 is the last remaining task. All other tasks (Phases A-D, E.1-E.4) are complete. Iteration 20 explicitly recommended E.5 with detailed guidance.
|
||||
### Status: COMPLETE
|
||||
### What was done:
|
||||
- **Removed 4 fixed heights** in `plotly_generator.py`:
|
||||
1. `create_cost_waterfall_figure()`: removed `height=500`
|
||||
2. `create_duration_cost_scatter_figure()`: removed `height=550`
|
||||
3. `create_drug_network_figure()`: removed `height=600`
|
||||
4. `create_trend_figure()`: removed `height=500`
|
||||
- **Kept 13 dynamic heights**: all `max(...)`, `fig_height`, and `dynamic_height` values remain — these scale with data and are correct behavior.
|
||||
- **Added CSS rules** in `nhs.css`: `#pathway-chart .js-plotly-plot, .plot-container, .svg-container { height: 100% !important }` to propagate flex container height into Plotly-rendered divs when no explicit figure height is set.
|
||||
- **Verified CSS flex chain**: `.chart-card` (flex:1) → `.dash-loading-callback > div` (flex:1) → `#chart-container` (flex:1) → `#pathway-chart` (flex:1) → `.js-plotly-plot/.plot-container/.svg-container` (height:100%) — full propagation from viewport to chart.
|
||||
- **Renamed metric toggle labels** in 2 files:
|
||||
- `chart_card.py`: "Cost" → "Cost per Patient", "Cost p.a." → "Cost per Patient p.a."
|
||||
- `trust_comparison.py`: same renames
|
||||
- `trends.py`: already correct from E.3 (verified, no changes needed)
|
||||
### Validation results:
|
||||
- Tier 1 (Code): `from dash_app.app import app` OK. `python run_dash.py` starts cleanly, HTTP 200.
|
||||
- Tier 2 (Visual): No fixed 500/550/600px cutoffs remain. Charts with no data-dependent height will expand to fill the flex container. Dynamic-height charts (e.g., 59-bar timeline at 1772px) remain unchanged.
|
||||
- Tier 3 (Functional): Metric toggles show "Patients | Cost per Patient | Cost per Patient p.a." in both PP heatmap and TC heatmap.
|
||||
### Files changed:
|
||||
- `src/visualization/plotly_generator.py` — removed 4 fixed height values
|
||||
- `dash_app/assets/nhs.css` — added Plotly div height propagation CSS
|
||||
- `dash_app/components/chart_card.py` — renamed heatmap metric toggle labels
|
||||
- `dash_app/components/trust_comparison.py` — renamed TC heatmap metric toggle labels
|
||||
- `IMPLEMENTATION_PLAN.md` — marked E.5 subtasks [x], all completion criteria [x]
|
||||
### Committed: [pending]
|
||||
### Patterns discovered:
|
||||
- Plotly `responsive=True` on `dcc.Graph` + `autosize=True` in figure layout is insufficient on its own — the Plotly-generated inner divs (`.js-plotly-plot`, `.plot-container`, `.svg-container`) need explicit `height: 100%` CSS to inherit the flex container height. Without this, they default to their content height.
|
||||
- The `!important` on the height rule is needed because Plotly sets inline styles on these elements.
|
||||
- Charts with dynamic heights (e.g., `max(400, n_bars * 28 + 120)`) correctly override the CSS since Plotly applies an inline `height` style which takes precedence over the CSS rule — `!important` only kicks in when no inline height is set.
|
||||
### Next iteration should:
|
||||
- ALL TASKS COMPLETE. See completion check below.
|
||||
### Blocked items:
|
||||
- None
|
||||
|
||||
## PROJECT COMPLETION
|
||||
All tasks in IMPLEMENTATION_PLAN.md are marked [x]. All completion criteria satisfied.
|
||||
Summary of all completed work (21 iterations):
|
||||
- **Phase A (4 tasks)**: Shared constants + _base_layout(), heatmap fixes, legend overflow fix, trust color differentiation
|
||||
- **Phase B (4 tasks)**: Title consistency, cost effectiveness gradient, Sankey freeform, heatmap metric toggle
|
||||
- **Phase C (4 tasks)**: Retention funnel, pathway depth distribution, duration vs cost scatter, drug network graph
|
||||
- **Phase D (3 tasks)**: Temporal trends CLI + Dash tab, drug timeline Gantt chart, average administered doses chart
|
||||
- **Phase E (5 tasks)**: Remove trends from PP, 3-view navigation, Trends landing page, drill-down, chart height fix + metric rename
|
||||
- **Total**: 20 tasks completed, 0 blocked, 9 PP tabs, 6 TC charts, 1 standalone Trends view, 17+ chart functions, all using shared styling
|
||||
|
||||
@@ -722,7 +722,6 @@ def create_cost_waterfall_figure(
|
||||
),
|
||||
margin=dict(t=60, l=8, r=24, b=40),
|
||||
showlegend=False,
|
||||
height=500,
|
||||
bargap=0.25,
|
||||
)
|
||||
fig.update_layout(**layout)
|
||||
@@ -1992,7 +1991,6 @@ def create_duration_cost_scatter_figure(
|
||||
zeroline=False,
|
||||
),
|
||||
legend=legend,
|
||||
height=550,
|
||||
)
|
||||
fig.update_layout(**layout)
|
||||
|
||||
@@ -2080,7 +2078,6 @@ def create_drug_network_figure(data: dict, title: str = "") -> go.Figure:
|
||||
margin=dict(t=60, l=24, r=24, b=24),
|
||||
xaxis=dict(visible=False, scaleanchor="y", scaleratio=1),
|
||||
yaxis=dict(visible=False),
|
||||
height=600,
|
||||
)
|
||||
fig.update_layout(**layout)
|
||||
|
||||
@@ -2376,7 +2373,6 @@ def create_trend_figure(
|
||||
zeroline=True,
|
||||
zerolinecolor=GRID_COLOR,
|
||||
),
|
||||
height=500,
|
||||
margin=dict(t=60, l=8, **legend_margins),
|
||||
legend=legend,
|
||||
hovermode="x unified",
|
||||
|
||||
Reference in New Issue
Block a user