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.
|
- **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
|
### 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
|
- [x] In `create_trend_figure()` in `plotly_generator.py`: removed explicit `height=500`, `autosize=True` from `_base_layout()` handles 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:
|
- [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`).
|
||||||
- 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
|
- [x] Added CSS rules: `#pathway-chart .js-plotly-plot, .plot-container, .svg-container { height: 100% !important }` to propagate flex container height
|
||||||
- For charts with fixed `height=500`: remove it
|
- [x] Verified CSS flex chain: `.chart-card` → `.dash-loading-callback > div` → `#chart-container` → `#pathway-chart` → `.js-plotly-plot` — all flex with `min-height: 0`
|
||||||
- [ ] 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
|
- [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`
|
||||||
- [ ] 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`)
|
|
||||||
- **Checkpoint**: Charts fill available viewport height in Patient Pathways. No fixed 500px cutoff. `python run_dash.py` starts cleanly.
|
- **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] Trends landing page shows directorate-level line chart with metric toggle
|
||||||
- [x] Clicking a directorate drills into drug-level trends
|
- [x] Clicking a directorate drills into drug-level trends
|
||||||
- [x] Back button returns to directorate overview
|
- [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
|
- [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 {
|
#pathway-chart {
|
||||||
flex: 1; min-height: 0;
|
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,
|
||||||
.chart-card > .dash-loading-callback > div {
|
.chart-card > .dash-loading-callback > div {
|
||||||
flex: 1; display: flex; flex-direction: column; min-height: 0;
|
flex: 1; display: flex; flex-direction: column; min-height: 0;
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ def make_chart_card():
|
|||||||
id="heatmap-metric-toggle",
|
id="heatmap-metric-toggle",
|
||||||
data=[
|
data=[
|
||||||
{"value": "patients", "label": "Patients"},
|
{"value": "patients", "label": "Patients"},
|
||||||
{"value": "cost", "label": "Cost"},
|
{"value": "cost", "label": "Cost per Patient"},
|
||||||
{"value": "cost_pp_pa", "label": "Cost p.a."},
|
{"value": "cost_pp_pa", "label": "Cost per Patient p.a."},
|
||||||
],
|
],
|
||||||
value="patients",
|
value="patients",
|
||||||
size="xs",
|
size="xs",
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ def make_tc_dashboard():
|
|||||||
id="tc-heatmap-metric-toggle",
|
id="tc-heatmap-metric-toggle",
|
||||||
data=[
|
data=[
|
||||||
{"value": "patients", "label": "Patients"},
|
{"value": "patients", "label": "Patients"},
|
||||||
{"value": "cost", "label": "Cost"},
|
{"value": "cost", "label": "Cost per Patient"},
|
||||||
{"value": "cost_pp_pa", "label": "Cost p.a."},
|
{"value": "cost_pp_pa", "label": "Cost per Patient p.a."},
|
||||||
],
|
],
|
||||||
value="patients",
|
value="patients",
|
||||||
size="xs",
|
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)
|
- 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:
|
### Blocked items:
|
||||||
- None
|
- 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),
|
margin=dict(t=60, l=8, r=24, b=40),
|
||||||
showlegend=False,
|
showlegend=False,
|
||||||
height=500,
|
|
||||||
bargap=0.25,
|
bargap=0.25,
|
||||||
)
|
)
|
||||||
fig.update_layout(**layout)
|
fig.update_layout(**layout)
|
||||||
@@ -1992,7 +1991,6 @@ def create_duration_cost_scatter_figure(
|
|||||||
zeroline=False,
|
zeroline=False,
|
||||||
),
|
),
|
||||||
legend=legend,
|
legend=legend,
|
||||||
height=550,
|
|
||||||
)
|
)
|
||||||
fig.update_layout(**layout)
|
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),
|
margin=dict(t=60, l=24, r=24, b=24),
|
||||||
xaxis=dict(visible=False, scaleanchor="y", scaleratio=1),
|
xaxis=dict(visible=False, scaleanchor="y", scaleratio=1),
|
||||||
yaxis=dict(visible=False),
|
yaxis=dict(visible=False),
|
||||||
height=600,
|
|
||||||
)
|
)
|
||||||
fig.update_layout(**layout)
|
fig.update_layout(**layout)
|
||||||
|
|
||||||
@@ -2376,7 +2373,6 @@ def create_trend_figure(
|
|||||||
zeroline=True,
|
zeroline=True,
|
||||||
zerolinecolor=GRID_COLOR,
|
zerolinecolor=GRID_COLOR,
|
||||||
),
|
),
|
||||||
height=500,
|
|
||||||
margin=dict(t=60, l=8, **legend_margins),
|
margin=dict(t=60, l=8, **legend_margins),
|
||||||
legend=legend,
|
legend=legend,
|
||||||
hovermode="x unified",
|
hovermode="x unified",
|
||||||
|
|||||||
Reference in New Issue
Block a user