diff --git a/weave-js/src/common/components/elements/SliderInput.test.tsx b/weave-js/src/common/components/elements/SliderInput.test.tsx new file mode 100644 index 00000000000..a712fefc354 --- /dev/null +++ b/weave-js/src/common/components/elements/SliderInput.test.tsx @@ -0,0 +1,68 @@ +import {getClosestTick} from './SliderInput'; + +describe('getClosestTick', () => { + test('lower previous value returns next greater', () => { + const ticks = [2, 4, 6, 8, 10]; + const previous = 4; + const val = 5; + expect(getClosestTick(ticks, val, previous)).toBe(6); + }); + test('lower previous value returns next greater, large step', () => { + const ticks = [2, 4, 60, 80, 10]; + const previous = 4; + const val = 5; + expect(getClosestTick(ticks, val, previous)).toBe(60); + }); + test('greater previous value returns next lesser', () => { + const ticks = [2, 4, 6, 8, 10]; + const previous = 4; + const val = 3; + const actual = getClosestTick(ticks, val, previous); + expect(actual).toBe(2); + }); + test('greater previous value returns next lesser, consecutive', () => { + const ticks = [1, 2, 3, 4, 5, 6]; + const previous = 4; + const val = 3; + const actual = getClosestTick(ticks, val, previous); + expect(actual).toBe(3); + }); + test('lower previous value returns next greater, consecutive', () => { + const ticks = [1, 2, 3, 4, 5, 6]; + const previous = 3; + const val = 4; + const actual = getClosestTick(ticks, val, previous); + expect(actual).toBe(4); + }); + test('lower previous value returns next greater, erratic', () => { + const ticks = [1, 4, 5, 7, 9, 12]; + const previous = 9; + const val = 10; + const actual = getClosestTick(ticks, val, previous); + expect(actual).toBe(12); + }); + test('greater previous value returns next lower, erratic', () => { + const ticks = [1, 2, 5, 7, 9, 12]; + const previous = 5; + const val = 4; + const actual = getClosestTick(ticks, val, previous); + expect(actual).toBe(2); + }); + + // Modified logic, retaining for expected performance + test('large number of ticks', () => { + const ticks = []; + for (let i = 0; i < 10000000; i += 2) { + ticks.push(i); + } + + const previous = 500000; + const val = 500001; + const start = Date.now(); + const actual = getClosestTick(ticks, val, previous); + const end = Date.now(); + const duration = end - start; + expect(actual).toBe(500002); + expect(duration).toBeLessThanOrEqual(1); + }); +}); diff --git a/weave-js/src/common/components/elements/SliderInput.tsx b/weave-js/src/common/components/elements/SliderInput.tsx index dce75615c93..1afbb8f064b 100644 --- a/weave-js/src/common/components/elements/SliderInput.tsx +++ b/weave-js/src/common/components/elements/SliderInput.tsx @@ -78,12 +78,12 @@ const SliderInput: React.FC = React.memo( newVal = min; } if (ticks != null) { - newVal = getClosestTick(ticks, newVal); + newVal = getClosestTick(ticks, newVal, sliderValue); } setSliderValue(newVal); onChangeDebounced(newVal); }, - [ticks, min, max, allowGreaterThanMax, onChangeDebounced] + [ticks, min, max, allowGreaterThanMax, sliderValue, onChangeDebounced] ); React.useEffect(() => { @@ -172,17 +172,38 @@ const SliderInput: React.FC = React.memo( export default SliderInput; -function getClosestTick(ticks: number[], val: number): number { +export function getClosestTick( + ticks: number[], + val: number, + prev: number +): number { let closest = val; + const increasing = val > prev; let minDiff = Number.MAX_VALUE; - for (const tick of ticks) { + // Binary search for the closest tick + let left = 0; + let right = ticks.length - 1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const tick = ticks[mid]; const diff = Math.abs(tick - val); - if (diff >= minDiff) { - break; + + // Only update closest if the tick is in the right direction + if ( + diff < minDiff && + ((increasing && tick >= val) || (!increasing && tick <= val)) + ) { + closest = tick; + minDiff = diff; + } + + if (tick < val) { + left = mid + 1; + } else { + right = mid - 1; } - closest = tick; - minDiff = diff; } return closest;