journal-elevation-profile: elevation chart + map↔chart sync
Implements the journal-elevation-profile change (specs/journal-elevation-profile):
- gpx: `elevationSeries(tracks)` → { d, e, lat, lng }[] with cumulative distance,
downsampled (keeps first/last), empty when <2 points carry elevation.
- ElevationProfile: read-only SVG area chart (vertical gradient fill), highest/
lowest summary + a hover readout. Reports hovered index (onActive) and clicked
index (onSeek); draws a marker at the active index. Renders nothing for an
empty series.
- RouteMapThumbnail: ActiveMarker (CircleMarker at the chart's active point),
HoverTracker (route hover → nearest sample → onHoverIndex), Recenter (panTo on
chart click). Props forwarded through ClientMap.
- Wired into the activity + route detail pages via a shared activeIndex/centerOn
state; loaders expose the series (activity reuses the moving-time parse).
- i18n journal.elevation.{highest,lowest} in en + de.
Ascent/descent stay in the stat row (#532); the chart summary shows highest/
lowest to avoid duplication.
Tests: elevationSeries unit; ElevationProfile component (jsdom); e2e creates an
activity from an elevation GPX and asserts the chart renders. typecheck + lint +
unit (gpx 67, journal 315) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5e6fcde281
commit
536a8f98b9
16 changed files with 509 additions and 27 deletions
|
|
@ -1,11 +1,11 @@
|
|||
## ADDED Requirements
|
||||
|
||||
### Requirement: Elevation profile chart on detail pages
|
||||
The route and activity detail pages SHALL render an elevation profile chart (distance on the x-axis, elevation on the y-axis) with a gradient fill and a summary strip showing ascent, descent, highest point, and lowest point, whenever the activity or route has a usable elevation series.
|
||||
The route and activity detail pages SHALL render an elevation profile chart (distance on the x-axis, elevation on the y-axis) with a gradient area fill and a summary showing the highest and lowest points, whenever the activity or route has a usable elevation series. (Ascent and descent are shown in the headline stat row above the chart.)
|
||||
|
||||
#### Scenario: Chart shown for a route with elevation
|
||||
- **WHEN** a user views a route whose GPX contains an elevation series
|
||||
- **THEN** an elevation profile chart is shown with a summary strip (ascent, descent, highest, lowest)
|
||||
- **THEN** an elevation profile chart is shown with a highest/lowest summary
|
||||
|
||||
#### Scenario: Chart omitted without elevation
|
||||
- **WHEN** an activity's GPX has no usable elevation data
|
||||
|
|
@ -38,4 +38,4 @@ The elevation summary strip SHALL render its labels and units through localized
|
|||
|
||||
#### Scenario: German summary labels
|
||||
- **WHEN** the UI locale is German
|
||||
- **THEN** the ascent, descent, highest, and lowest labels render in German
|
||||
- **THEN** the highest and lowest labels render in German
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
## 1. Series helper
|
||||
|
||||
- [ ] 1.1 Add a helper (in `@trails-cool/gpx` or the journal lib) that turns GPX into `{ distance, elevation, lat, lng }[]`, downsampled to ~500 points; unit-test it (incl. the no-elevation case → empty/invalid series).
|
||||
- [ ] 1.2 Expose the series + summary (ascent/descent/high/low) from the route and activity detail loaders.
|
||||
- [x] 1.1 `elevationSeries(tracks, maxPoints=400)` in `@trails-cool/gpx` → `{ d, e, lat, lng }[]`, downsampled (keeps first/last), empty when <2 points carry elevation; unit-tested.
|
||||
- [x] 1.2 Expose the series from the route + activity detail loaders (one parse; activity reuses the moving-time parse). Highest/lowest are computed in the chart; ascent/descent stay in the stat row.
|
||||
|
||||
## 2. Chart component
|
||||
|
||||
- [ ] 2.1 Build a presentational `ElevationProfile` SVG area chart (gradient fill via the `map-core` elevation/gradient scale) with the ascent/descent/high/low summary strip.
|
||||
- [ ] 2.2 Render nothing when the series has no usable elevation.
|
||||
- [x] 2.1 Presentational `ElevationProfile` SVG area chart (vertical gradient fill) with a highest/lowest summary + a hover readout (distance · elevation).
|
||||
- [x] 2.2 Renders nothing when the series has <2 points.
|
||||
|
||||
## 3. Active-distance interaction
|
||||
|
||||
- [ ] 3.1 Lift an `activeDistance | null` state above the detail page's map + chart.
|
||||
- [ ] 3.2 Chart hover → set `activeDistance`; map shows a marker at the interpolated lat/lng.
|
||||
- [ ] 3.3 Map route-line hover → set `activeDistance` (nearest sample); chart shows a crosshair/marker.
|
||||
- [ ] 3.4 Chart click → center the map on that location (read-only). Throttle pointer updates; memoize interpolation.
|
||||
- [x] 3.1 Lift a shared `activeIndex | null` (+ `centerOn`) above the detail page's map + chart.
|
||||
- [x] 3.2 Chart hover → `onActive(index)`; map draws a `CircleMarker` at that sample's lat/lng.
|
||||
- [x] 3.3 Map route hover → `HoverTracker` finds the nearest sample → `onHoverIndex`; chart draws a crosshair + dot.
|
||||
- [x] 3.4 Chart pointer-down → `onSeek` bumps `centerOn`; map `panTo`s that point (read-only, no data change).
|
||||
|
||||
## 4. Wire-in & i18n
|
||||
|
||||
- [ ] 4.1 Mount `ElevationProfile` + shared state in `routes.$id.tsx` and `activities.$id.tsx`.
|
||||
- [ ] 4.2 Add `journal.elevation.*` keys (ascent, descent, highest, lowest) to en + de.
|
||||
- [x] 4.1 Mounted in `routes.$id.tsx` and `activities.$id.tsx` (props forwarded through `ClientMap`).
|
||||
- [x] 4.2 Add `journal.elevation.{highest,lowest}` to en + de.
|
||||
|
||||
## 5. Tests & checks
|
||||
|
||||
- [ ] 5.1 Unit: series helper (sampling, summary numbers, no-elevation → omitted).
|
||||
- [ ] 5.2 Component: chart renders for a series; renders nothing for an empty series.
|
||||
- [ ] 5.3 E2E: open a route detail with elevation, assert the chart + summary appear; hovering the chart shows a map marker.
|
||||
- [ ] 5.4 `pnpm typecheck && pnpm lint && pnpm test && pnpm test:e2e` green.
|
||||
- [x] 5.1 Unit: `elevationSeries` (cumulative distance, flatten segments, downsample first/last, no-elevation → empty).
|
||||
- [x] 5.2 Component (jsdom): chart renders for a series + highest/lowest summary; nothing for a too-short series; marker on active; `onSeek` on pointer-down.
|
||||
- [x] 5.3 E2E: create an activity from an elevation GPX, assert the profile chart + summary render (`e2e/elevation-profile.test.ts`, registered in `playwright.config.ts`). Passes locally.
|
||||
- [x] 5.4 typecheck + lint + unit (gpx 67, journal 315) green; new e2e passes locally. Full `pnpm test:e2e` runs in CI.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue