diff --git a/src/CreateGraphPath.ts b/src/CreateGraphPath.ts index 0c7f1c0..eaf5219 100644 --- a/src/CreateGraphPath.ts +++ b/src/CreateGraphPath.ts @@ -87,6 +87,10 @@ export const getXPositionInRange = ( const diff = xRange.max.getTime() - xRange.min.getTime(); const x = date.getTime(); + if (diff === 0) { + return x === xRange.min.getTime() ? 0.5 : Number.NaN; + } + return (x - xRange.min.getTime()) / diff; }; @@ -174,41 +178,54 @@ function createGraphPathBase({ return endX; }; - for ( - let pixel = startX; - startX <= pixel && pixel <= endX; - pixel = getNextPixelValue(pixel) - ) { - const index = getGraphDataIndex(pixel); - - // Draw first point only on the very first pixel - if (index === 0 && pixel !== startX) continue; - // Draw last point only on the very last pixel - - if (index === graphData.length - 1 && pixel !== endX) continue; - - if (index !== 0 && index !== graphData.length - 1) { - // Only draw point, when the point is exact - const exactPointX = - getXInRange(drawingWidth, graphData[index]!.date, range.x) + - horizontalPadding; - - const isExactPointInsidePixelRatio = Array(PIXEL_RATIO) - .fill(0) - .some((_value, additionalPixel) => { - return pixel + additionalPixel === exactPointX; - }); - - if (!isExactPointInsidePixelRatio) continue; - } - + const addPoint = (index: number, x: number) => { const value = graphData[index]!.value; const y = drawingHeight - getYInRange(drawingHeight, value, range.y) + verticalPadding; - points.push({ x: pixel, y: y }); + points.push({ x, y }); + }; + + const firstPointTime = graphData[0]!.date.getTime(); + const allPointsShareDate = graphData.every( + (point) => point.date.getTime() === firstPointTime + ); + + if (endX === startX && allPointsShareDate) { + graphData.forEach((_point, index) => addPoint(index, startX)); + } else { + for ( + let pixel = startX; + startX <= pixel && pixel <= endX; + pixel = getNextPixelValue(pixel) + ) { + const index = getGraphDataIndex(pixel); + + // Draw first point only on the very first pixel + if (index === 0 && pixel !== startX) continue; + // Draw last point only on the very last pixel + + if (index === graphData.length - 1 && pixel !== endX) continue; + + if (index !== 0 && index !== graphData.length - 1) { + // Only draw point, when the point is exact + const exactPointX = + getXInRange(drawingWidth, graphData[index]!.date, range.x) + + horizontalPadding; + + const isExactPointInsidePixelRatio = Array(PIXEL_RATIO) + .fill(0) + .some((_value, additionalPixel) => { + return pixel + additionalPixel === exactPointX; + }); + + if (!isExactPointInsidePixelRatio) continue; + } + + addPoint(index, pixel); + } } for (let i = 0; i < points.length; i++) { diff --git a/src/__tests__/CreateGraphPath.test.ts b/src/__tests__/CreateGraphPath.test.ts index dc015dd..0eb3f45 100644 --- a/src/__tests__/CreateGraphPath.test.ts +++ b/src/__tests__/CreateGraphPath.test.ts @@ -13,7 +13,11 @@ jest.mock('@shopify/react-native-skia', () => ({ }, })); -import { createGraphPath } from '../CreateGraphPath'; +import { + createGraphPath, + getGraphPathRange, + getPointsInRange, +} from '../CreateGraphPath'; beforeEach(() => jest.clearAllMocks()); @@ -43,3 +47,47 @@ it('creates a finite path when every graph point maps to the same pixel', () => expect(mockPath.moveTo).toHaveBeenCalledTimes(1); expect(mockPath.moveTo.mock.calls[0]?.every(Number.isFinite)).toBe(true); }); + +it('creates a visible path when graph points share the same date', () => { + const date = new Date('2023-01-01'); + const points = [ + { date, value: 1 }, + { date, value: 2 }, + ]; + const range = getGraphPathRange(points); + const pointsInRange = getPointsInRange(points, range); + + createGraphPath({ + pointsInRange, + range, + horizontalPadding: 0, + verticalPadding: 0, + canvasHeight: 200, + canvasWidth: 300, + }); + + expect(pointsInRange).toEqual(points); + expect(mockPath.moveTo).toHaveBeenCalledTimes(1); + expect(mockPath.cubicTo).toHaveBeenCalled(); + expect( + [...mockPath.moveTo.mock.calls, ...mockPath.cubicTo.mock.calls] + .flat() + .every(Number.isFinite) + ).toBe(true); +}); + +it('filters different dates from a zero-duration range', () => { + const date = new Date('2023-01-01'); + const points = [ + { date: new Date('2022-12-31'), value: 1 }, + { date, value: 2 }, + { date: new Date('2023-01-02'), value: 3 }, + ]; + + expect( + getPointsInRange(points, { + x: { min: date, max: date }, + y: { min: 1, max: 3 }, + }) + ).toEqual([points[1]]); +});