Invert function does not work correctly on scaleTime
nilsofue opened this issue · 3 comments
After an update of d3 I noticed that since version 5.15.0 of d3 the invert() function of scaleTime() sometimes does not return an exact date. It is then missing 1ms.
I have created an example in StackBlitz.
I noticed the error when I converted the value in pixels to a date and then converted this value back to a date. Then the two dates are different. I hope the problem can be solved soon.
This change seems to have been introduced between version 0.8 and 0.9 of d3-scale
v0.8.0...v0.9.0
So probably somewhere here? d3/d3-time@v0.2.0...v0.3.0
It’s easier to see what’s happening with a linear scale (and the only difference between a time scale and a linear scale is that scale.invert calls new Date(time) to convert milliseconds since UNIX epoch back into a Date instance):
The domain is [2022-08-31T22:00Z
, 2022-09-30T21:59:59.999Z
] which is [1661983200000, 1664575199999]. The range is [0, 946].
The input is 2022-09-04T22:00Z
which is 1662328800000.
The scaled output is 126.13333338199588 which is a * (1 - t) + b * t given t = (1662328800000 - 1661983200000) / (1664575199999 - 1661983200000), a = 0, b = 946.
Inverting the scaled output back to the input is 1662328799999.9998, which is a * (1 - t) + b * t given t = (126.13333338199588 - 0) / (946 - 0), a = 1661983200000, b = 1664575199999. This is less than the input by 0.000244140625ms, but within the expected floating point precision (0.9999999999999999×).
The issue arises that new Date(number) floors the input. And hence new Date(1662328799999.9998) is equivalent to new Date(1662328799999), which is off by 1ms, for an error of 0.9999999999993985×. This is a ~4,000× increase in error due to millisecond rounding.
To improve this, we need to call new Date(Math.round(number)) instead.