On mobile the compare chart was unreadable when schools score closely: the y-axis was pinned 0-100 so clustered values squashed into a thin band, and the in-chart title plus a legend row per school consumed ~40% of a 300px card.
Changes
Y-axis auto-fits the data on all viewports (computeYBounds in lib/utils): percentages get padding with a minimum span (never widened past 0-100 for non-negative data; pct-named trend metrics with negative deltas are not zero-clamped), progress scores stay symmetric around 0, scores (Attainment 8 etc.) fit the range.
Each school gets a distinct point style (circle/triangle/rect/rectRot/star) as secondary encoding — converging lines stay tellable apart, and it helps colour-blind readers. Desktop's built-in legend shows the shapes via usePointStyle.
Mobile (max 640px): in-chart title/legend/axis titles removed; a chip row above the canvas (colour dot + truncated name) doubles as the legend and a tap-to-focus control — tapping highlights that school's line and dims the rest. Chart card grows 300px to 340px, nearly all of it now plot area.
Fixes a latent colour mismatch: datasets were built from Object.entries(comparisonData), whose integer-like URN keys enumerate in ascending numeric order — line colours desynced from the school-card colours whenever the basket wasn't in ascending-URN order. The chart now receives the ordered school list. (Line colours may visibly change for some existing share URLs — that's this fix, not a regression.)
Years are unioned across schools (previously only the first school's years were plotted).
PerformanceChart's matchMedia pattern extracted into shared hooks/useIsMobile.
Tests: unit tests for metricKind/computeYBounds; new e2e journey asserts the mobile chip legend renders per school and the focus toggle works. After staging deploy, screenshot check: /compare?urns=101038,101022,101036 at 390x844 (three schools all at 86% in 2024/25 — must show three separable lines).
On mobile the compare chart was unreadable when schools score closely: the y-axis was pinned 0-100 so clustered values squashed into a thin band, and the in-chart title plus a legend row per school consumed ~40% of a 300px card.
**Changes**
- Y-axis auto-fits the data on all viewports (`computeYBounds` in `lib/utils`): percentages get padding with a minimum span (never widened past 0-100 for non-negative data; pct-named trend metrics with negative deltas are not zero-clamped), progress scores stay symmetric around 0, scores (Attainment 8 etc.) fit the range.
- Each school gets a distinct point style (circle/triangle/rect/rectRot/star) as secondary encoding — converging lines stay tellable apart, and it helps colour-blind readers. Desktop's built-in legend shows the shapes via `usePointStyle`.
- Mobile (max 640px): in-chart title/legend/axis titles removed; a chip row above the canvas (colour dot + truncated name) doubles as the legend and a tap-to-focus control — tapping highlights that school's line and dims the rest. Chart card grows 300px to 340px, nearly all of it now plot area.
- Fixes a latent colour mismatch: datasets were built from `Object.entries(comparisonData)`, whose integer-like URN keys enumerate in ascending numeric order — line colours desynced from the school-card colours whenever the basket wasn't in ascending-URN order. The chart now receives the ordered school list. (Line colours may visibly change for some existing share URLs — that's this fix, not a regression.)
- Years are unioned across schools (previously only the first school's years were plotted).
- `PerformanceChart`'s matchMedia pattern extracted into shared `hooks/useIsMobile`.
**Tests**: unit tests for `metricKind`/`computeYBounds`; new e2e journey asserts the mobile chip legend renders per school and the focus toggle works. After staging deploy, screenshot check: `/compare?urns=101038,101022,101036` at 390x844 (three schools all at 86% in 2024/25 — must show three separable lines).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The compare chart squashed clustered schools into a thin band (y pinned
0-100) under an in-chart title + per-school legend that ate ~40% of a
300px card, leaving converging lines indistinguishable on phones.
- Auto-fit the y-axis to the data on all viewports (computeYBounds in
lib/utils: padded + min-span for percentages, symmetric around 0 for
progress, fitted for scores; negative pct-named trend metrics are not
zero-clamped).
- Distinct point style per school (circle/triangle/rect/rectRot/star)
as secondary encoding for convergence and colour-blindness.
- Mobile: drop in-chart title/legend/axis titles; add a chip row (colour
dot + name) that doubles as tap-to-focus — highlights one school's
line and dims the rest. Chart card 300px -> 340px, nearly all plot.
- Fix a latent colour mismatch: datasets were built from Object.entries
whose integer-like URN keys enumerate in ascending numeric order,
desyncing line colours from card colours; the chart now receives the
ordered school list.
- Union years across schools instead of taking the first school's.
- Extract PerformanceChart's matchMedia pattern into hooks/useIsMobile.
Unit tests for metricKind/computeYBounds; e2e journey covers the mobile
chips and focus toggle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This PR reworks the compare-page chart to auto-fit its y-axis to the data instead of pinning 0-100, adds distinct point shapes and a mobile tap-to-focus chip legend, fixes a real color/label desync bug (datasets now driven by the ordered schools array instead of Object.entries(comparisonData)), and adds solid unit/e2e test coverage. The change is well-tested and the core logic (computeYBounds, metricKind, useIsMobile) checks out correctly against its own test cases.
🟡 Minor
nextjs-app/components/ComparisonView.module.css: The taller mobile chartContainer height (340px) is scoped to the existing @media (max-width: 768px) block, but the JS mobile behavior it's sized for (hiding the in-chart legend/axis titles, showing chips) only kicks in at 640px via useIsMobile and the chip CSS. For viewports between 641px and 768px, the chart gets the extra height intended for the stripped-down mobile layout while still rendering the full desktop legend and axis titles, leaving unused vertical space.
## 🤖 AI Code Review (Claude Code)
This PR reworks the compare-page chart to auto-fit its y-axis to the data instead of pinning 0-100, adds distinct point shapes and a mobile tap-to-focus chip legend, fixes a real color/label desync bug (datasets now driven by the ordered schools array instead of Object.entries(comparisonData)), and adds solid unit/e2e test coverage. The change is well-tested and the core logic (computeYBounds, metricKind, useIsMobile) checks out correctly against its own test cases.
### 🟡 Minor
- **nextjs-app/components/ComparisonView.module.css**: The taller mobile chartContainer height (340px) is scoped to the existing @media (max-width: 768px) block, but the JS mobile behavior it's sized for (hiding the in-chart legend/axis titles, showing chips) only kicks in at 640px via useIsMobile and the chip CSS. For viewports between 641px and 768px, the chart gets the extra height intended for the stripped-down mobile layout while still rendering the full desktop legend and axis titles, leaving unused vertical space.
tudor
merged commit 18cd805c6c into main2026-07-05 21:26:01 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
On mobile the compare chart was unreadable when schools score closely: the y-axis was pinned 0-100 so clustered values squashed into a thin band, and the in-chart title plus a legend row per school consumed ~40% of a 300px card.
Changes
computeYBoundsinlib/utils): percentages get padding with a minimum span (never widened past 0-100 for non-negative data; pct-named trend metrics with negative deltas are not zero-clamped), progress scores stay symmetric around 0, scores (Attainment 8 etc.) fit the range.usePointStyle.Object.entries(comparisonData), whose integer-like URN keys enumerate in ascending numeric order — line colours desynced from the school-card colours whenever the basket wasn't in ascending-URN order. The chart now receives the ordered school list. (Line colours may visibly change for some existing share URLs — that's this fix, not a regression.)PerformanceChart's matchMedia pattern extracted into sharedhooks/useIsMobile.Tests: unit tests for
metricKind/computeYBounds; new e2e journey asserts the mobile chip legend renders per school and the focus toggle works. After staging deploy, screenshot check:/compare?urns=101038,101022,101036at 390x844 (three schools all at 86% in 2024/25 — must show three separable lines).🤖 Generated with Claude Code
🤖 AI Code Review (Claude Code)
This PR reworks the compare-page chart to auto-fit its y-axis to the data instead of pinning 0-100, adds distinct point shapes and a mobile tap-to-focus chip legend, fixes a real color/label desync bug (datasets now driven by the ordered schools array instead of Object.entries(comparisonData)), and adds solid unit/e2e test coverage. The change is well-tested and the core logic (computeYBounds, metricKind, useIsMobile) checks out correctly against its own test cases.
🟡 Minor