diff --git a/src/math/__tests__/math.test.ts b/src/math/__tests__/math.test.ts index 2bce73a..9e36b73 100644 --- a/src/math/__tests__/math.test.ts +++ b/src/math/__tests__/math.test.ts @@ -69,6 +69,21 @@ describe('computeRange', () => { const mid = (min + max) / 2 expect(mid).toBeCloseTo(50, 1) }) + + it('uses absolute floor only for zero-span data', () => { + const { min, max } = computeRange(pts([10, 10]), 10) + expect(max - min).toBeCloseTo(0.4) + }) + + it('does not force absolute floor for tiny nonzero spans', () => { + // Forcing 0.4 for every tiny span over-pads micro volatility into a + // flat-looking chart. Keep relative domain + margin; grid guards (#19) + // prevent hangs when the step size is denormal. + const rawRange = 1e-8 + const { min, max } = computeRange(pts([9, 9 + rawRange]), 9) + expect(max - min).toBeCloseTo(rawRange * (1 + 2 * 0.12), 12) + expect(max - min).toBeLessThan(0.4) + }) }) // -- detectMomentum -- diff --git a/src/math/range.ts b/src/math/range.ts index d987513..6ca7a63 100644 --- a/src/math/range.ts +++ b/src/math/range.ts @@ -29,7 +29,14 @@ export function computeRange( const rawRange = targetMax - targetMin const marginFactor = exaggerate ? 0.01 : 0.12 - const minRange = rawRange * (exaggerate ? 0.02 : 0.1) || (exaggerate ? 0.04 : 0.4) + // `rawRange * k || floor` skipped the absolute floor for any tiny nonzero + // span (JS || only falls through on 0/falsy), which produced denormal Y + // domains and could freeze the Y-grid loop. Apply the floor only when the + // span is zero / non-finite; keep relative min for real nonzero ranges. + const relativeMin = rawRange * (exaggerate ? 0.02 : 0.1) + const absoluteFloor = exaggerate ? 0.04 : 0.4 + const minRange = + rawRange > 0 && Number.isFinite(rawRange) ? relativeMin : absoluteFloor if (rawRange < minRange) { const mid = (targetMin + targetMax) / 2 diff --git a/src/useLivelineEngine.ts b/src/useLivelineEngine.ts index 7c29276..2c687cf 100644 --- a/src/useLivelineEngine.ts +++ b/src/useLivelineEngine.ts @@ -424,7 +424,10 @@ function computeCandleRange( if (!isFinite(min) || !isFinite(max)) return { min: 99, max: 101 } const range = max - min const margin = range * 0.12 - const minRange = range * 0.1 || 0.4 + // Same floor semantics as computeRange: do not use `||` (skips floor for + // tiny nonzero spans). Absolute floor only when span is zero / non-finite. + const relativeMin = range * 0.1 + const minRange = range > 0 && Number.isFinite(range) ? relativeMin : 0.4 if (range < minRange) { const mid = (min + max) / 2 return { min: mid - minRange / 2, max: mid + minRange / 2 }