Skip to content

Commit d539ac7

Browse files
committed
Merge branch 'master' into route_stats
2 parents 1802748 + b684818 commit d539ac7

File tree

9 files changed

+74
-21
lines changed

9 files changed

+74
-21
lines changed

src/pathDetails/elevationWidget/ChartRenderer.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ChartConfig, ChartData, ChartHoverResult, ChartPathDetail, ElevationPoint } from './types'
2-
import { calculateNiceTicks, formatDistanceLabel, formatElevationLabel } from './axisUtils'
2+
import { calculateNiceTicks, formatDistanceLabel, formatElevationLabel, computeDetailYRange, formatDetailTick } from './axisUtils'
33
import { getSlopeColor } from './colors'
44

55
const DEFAULT_MARGIN = { top: 10, right: 15, bottom: 26, left: 48 }
@@ -497,19 +497,7 @@ export default class ChartRenderer {
497497
plotTop: number,
498498
) {
499499
if (detail.minValue === undefined || detail.maxValue === undefined) return
500-
let min = detail.minValue
501-
let max = detail.maxValue
502-
// Ensure minimum span
503-
if (max - min < 10) {
504-
const mid = (min + max) / 2
505-
min = mid - 25
506-
max = mid + 25
507-
}
508-
const pad = (max - min) * 0.1
509-
min -= pad
510-
max += pad
511-
// Don't push min below 0 when all original values are non-negative
512-
if (detail.minValue >= 0) min = Math.max(0, min)
500+
const { min, max } = computeDetailYRange(detail.minValue, detail.maxValue)
513501

514502
const lineY = (v: number) => plotBottom - detailBarH - ((v - min) / (max - min)) * plotHeight
515503

@@ -552,7 +540,7 @@ export default class ChartRenderer {
552540
for (const t of ticks) {
553541
const y = lineY(t)
554542
if (y < plotTop || y > plotBottom - detailBarH) continue
555-
ctx.fillText(String(Math.round(t)), rightX + 4, y)
543+
ctx.fillText(formatDetailTick(t, ticks), rightX + 4, y)
556544
}
557545

558546
// Draw unit label at top of axis

src/pathDetails/elevationWidget/ElevationWidget.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export default function ElevationWidget({
175175
}, [alternativeCount])
176176

177177
return (
178-
<div className={`${styles.container}${isExpanded ? ' ' + styles.containerExpanded : ''}`} style={{ display: hasData ? undefined : 'none' }}>
178+
<div className={`${styles.container}${isExpanded ? ' ' + styles.containerExpanded : ''}`}>
179179
<div className={styles.controls}>
180180
<DetailSelector
181181
details={data?.pathDetails || []}

src/pathDetails/elevationWidget/axisUtils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,27 @@ function formatNumber(n: number): string {
6767
if (n >= 10) return (Math.round(n * 10) / 10).toString()
6868
return (Math.round(n * 100) / 100).toString()
6969
}
70+
71+
/**
72+
* Compute padded y-axis range for a detail line chart.
73+
* Adds 10% padding on each side and clamps min to 0 when all values are non-negative.
74+
*/
75+
export function computeDetailYRange(dataMin: number, dataMax: number): { min: number; max: number } {
76+
const range = dataMax - dataMin
77+
const pad = (range > 0 ? range : Math.abs(dataMax) || 1) * 0.1
78+
let min = dataMin - pad
79+
let max = dataMax + pad
80+
if (dataMin >= 0) min = Math.max(0, min)
81+
return { min, max }
82+
}
83+
84+
/**
85+
* Format a detail y-axis tick value. Uses enough decimal places to avoid duplicate labels.
86+
*/
87+
export function formatDetailTick(value: number, ticks: number[]): string {
88+
if (ticks.length < 2) return String(value)
89+
const step = Math.abs(ticks[1] - ticks[0])
90+
if (step >= 1) return String(Math.round(value))
91+
const decimals = Math.min(4, Math.ceil(-Math.log10(step)))
92+
return value.toFixed(decimals)
93+
}

src/pathDetails/elevationWidget/pathDetailData.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,14 @@ export function transformPathDetail(
159159
coordinates: coordinates.slice(from, to + 1).map(c => [c[0], c[1]] as [number, number]),
160160
}
161161
})
162-
const mid = Math.round((sanitizedInfo.minVal + sanitizedInfo.maxVal) / 2)
162+
const range = sanitizedInfo.maxVal - sanitizedInfo.minVal
163+
const precision = range >= 10 ? 1 : range >= 1 ? 100 : 10000
164+
const round = (v: number) => Math.round(v * precision) / precision
165+
const mid = round((sanitizedInfo.minVal + sanitizedInfo.maxVal) / 2)
163166
legend = [
164-
{ label: String(sanitizedInfo.minVal), color: getNumericGradientColor(0) },
167+
{ label: String(round(sanitizedInfo.minVal)), color: getNumericGradientColor(0) },
165168
{ label: String(mid), color: getNumericGradientColor(0.5) },
166-
{ label: String(sanitizedInfo.maxVal), color: getNumericGradientColor(1) },
169+
{ label: String(round(sanitizedInfo.maxVal)), color: getNumericGradientColor(1) },
167170
]
168171
} else {
169172
// Discrete values

src/stores/ErrorStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default class ErrorStore extends Store<ErrorStoreState> {
2626
isDismissed: false,
2727
}
2828
} else if (action instanceof DismissLastError || action instanceof RouteRequestSuccess) {
29+
if (state.isDismissed) return state
2930
return {
3031
...state,
3132
isDismissed: true,

src/stores/MapOptionsStore.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,16 +199,19 @@ export default class MapOptionsStore extends Store<MapOptionsStoreState> {
199199
selectedStyle: styleOption,
200200
}
201201
} else if (action instanceof ToggleRoutingGraph) {
202+
if (state.routingGraphEnabled === action.routingGraphEnabled) return state
202203
return {
203204
...state,
204205
routingGraphEnabled: action.routingGraphEnabled,
205206
}
206207
} else if (action instanceof ToggleUrbanDensityLayer) {
208+
if (state.urbanDensityEnabled === action.urbanDensityEnabled) return state
207209
return {
208210
...state,
209211
urbanDensityEnabled: action.urbanDensityEnabled,
210212
}
211213
} else if (action instanceof ToggleExternalMVTLayer) {
214+
if (state.externalMVTEnabled === action.externalMVTLayerEnabled) return state
212215
return {
213216
...state,
214217
externalMVTEnabled: action.externalMVTLayerEnabled,

src/stores/RouteStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Store from '@/stores/Store'
22
import { Action } from '@/stores/Dispatcher'
3-
import { ClearPoints, ClearRoute, RemovePoint, RouteRequestSuccess, SetPoint, SetSelectedPath } from '@/actions/Actions'
3+
import { AddPoint, ClearPoints, ClearRoute, RemovePoint, RouteRequestSuccess, SetPoint, SetSelectedPath } from '@/actions/Actions'
44
import { Path, RoutingResult } from '@/api/graphhopper'
55

66
export interface RouteStoreState {
@@ -66,6 +66,7 @@ export default class RouteStore extends Store<RouteStoreState> {
6666
}
6767
} else if (
6868
action instanceof SetPoint ||
69+
action instanceof AddPoint ||
6970
action instanceof ClearRoute ||
7071
action instanceof ClearPoints ||
7172
action instanceof RemovePoint

test/pathDetails/elevationWidget/axisUtils.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { calculateNiceTicks, formatDistanceLabel, formatElevationLabel } from '@/pathDetails/elevationWidget/axisUtils'
1+
import { calculateNiceTicks, formatDistanceLabel, formatElevationLabel, computeDetailYRange, formatDetailTick } from '@/pathDetails/elevationWidget/axisUtils'
22

33
describe('axisUtils', () => {
44
describe('calculateNiceTicks', () => {
@@ -107,4 +107,26 @@ describe('axisUtils', () => {
107107
expect(formatElevationLabel(100.7, false)).toBe('101 m')
108108
})
109109
})
110+
111+
describe('computeDetailYRange', () => {
112+
it('adds 10% padding and clamps non-negative min to 0', () => {
113+
// curvature-like: 0.5–1.0, range=0.5, pad=0.05
114+
expect(computeDetailYRange(0.5, 1.0)).toEqual({ min: expect.closeTo(0.45), max: expect.closeTo(1.05) })
115+
// speed-like: non-negative min clamped to 0
116+
expect(computeDetailYRange(10, 120).min).toBe(0)
117+
// negative data: no clamping
118+
expect(computeDetailYRange(-10, 10).min).toBeCloseTo(-12)
119+
// equal min/max: uses fallback range
120+
expect(computeDetailYRange(5, 5).min).toBeLessThan(5)
121+
})
122+
})
123+
124+
describe('formatDetailTick', () => {
125+
it('uses enough decimals to keep ticks distinct', () => {
126+
expect(formatDetailTick(10, [0, 10, 20])).toBe('10')
127+
expect(formatDetailTick(0.6, [0.4, 0.6, 0.8, 1.0])).toBe('0.6')
128+
expect(formatDetailTick(1.0, [0.4, 0.6, 0.8, 1.0])).toBe('1.0')
129+
expect(formatDetailTick(0.01, [0.01, 0.02, 0.03])).toBe('0.01')
130+
})
131+
})
110132
})

test/pathDetails/elevationWidget/pathDetailData.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ describe('pathDetailData', () => {
151151
expect(result.type).toBe('bars')
152152
})
153153

154+
it('produces distinct legend labels for small numeric ranges', () => {
155+
const entries: [number, number, any][] = [
156+
[0, 1, 0.5],
157+
[1, 2, 1.0],
158+
]
159+
const result = transformPathDetail('curvature', 'Curvature', entries, coords, cumDist)
160+
expect(result.type).toBe('line')
161+
const labels = result.legend.map(l => l.label)
162+
expect(new Set(labels).size).toBe(labels.length)
163+
})
164+
154165
it('assigns missing color for special values', () => {
155166
const entries: [number, number, any][] = [
156167
[0, 1, 'missing'],

0 commit comments

Comments
 (0)