{"action":"created","issue":{"url":"https://api.github.com/repos/mbostock/d3/issues/2513","labels_url":"https://api.github.com/repos/mbostock/d3/issues/2513/labels{/name}","comments_url":"https://api.github.com/repos/mbostock/d3/issues/2513/comments","events_url":"https://api.github.com/repos/mbostock/d3/issues/2513/events","html_url":"https://github.com/mbostock/d3/issues/2513","id":99271266,"number":2513,"title":"Quantize scale produces incorrect result on qunatized range thresholds","user":{"login":"barrybecker4","id":3020163,"avatar_url":"https://avatars.githubusercontent.com/u/3020163?v=3","gravatar_id":"","url":"https://api.github.com/users/barrybecker4","html_url":"https://github.com/barrybecker4","followers_url":"https://api.github.com/users/barrybecker4/followers","following_url":"https://api.github.com/users/barrybecker4/following{/other_user}","gists_url":"https://api.github.com/users/barrybecker4/gists{/gist_id}","starred_url":"https://api.github.com/users/barrybecker4/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/barrybecker4/subscriptions","organizations_url":"https://api.github.com/users/barrybecker4/orgs","repos_url":"https://api.github.com/users/barrybecker4/repos","events_url":"https://api.github.com/users/barrybecker4/events{/privacy}","received_events_url":"https://api.github.com/users/barrybecker4/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/mbostock/d3/labels/bug","name":"bug","color":"d62728"}],"state":"closed","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/mbostock/d3/milestones/32","html_url":"https://github.com/mbostock/d3/milestones/3.5.x","labels_url":"https://api.github.com/repos/mbostock/d3/milestones/32/labels","id":860338,"number":32,"title":"3.5.x","description":"","creator":{"login":"mbostock","id":230541,"avatar_url":"https://avatars.githubusercontent.com/u/230541?v=3","gravatar_id":"","url":"https://api.github.com/users/mbostock","html_url":"https://github.com/mbostock","followers_url":"https://api.github.com/users/mbostock/followers","following_url":"https://api.github.com/users/mbostock/following{/other_user}","gists_url":"https://api.github.com/users/mbostock/gists{/gist_id}","starred_url":"https://api.github.com/users/mbostock/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mbostock/subscriptions","organizations_url":"https://api.github.com/users/mbostock/orgs","repos_url":"https://api.github.com/users/mbostock/repos","events_url":"https://api.github.com/users/mbostock/events{/privacy}","received_events_url":"https://api.github.com/users/mbostock/received_events","type":"User","site_admin":false},"open_issues":16,"closed_issues":41,"state":"open","created_at":"2014-11-10T16:45:16Z","updated_at":"2015-12-16T18:42:22Z","due_on":null,"closed_at":null},"comments":4,"created_at":"2015-08-05T18:45:19Z","updated_at":"2015-12-17T00:47:15Z","closed_at":"2015-10-22T06:15:00Z","body":"Suppose I create a quantize scale like this:\r\n\r\n```js\r\nvar quantizeScale = d3.scale.quantize()\r\n    .domain([4.2, 6.2])\r\n    .range(d3.range(10));\r\n```\r\n\r\nThen the qunatized bins should be [4.2, 4.4], [4.4, 4.6], [4.6, 4.8], [4.8, 5], [5, 5.2], ... [6, 6.2]\r\n\r\nThere is a problem getting the correct range value when scaling some of the threshold values.\r\nMy expectation is that the low value of each bin (shown above) should be included in the bin, and the high value should be excluded.\r\nThis is the case most of the time, but not always. Because of floating point precision issues (like the fact that .3 - .1 is not .2), we sometimes get unexpected results.\r\n\r\nFor example,\r\n quantizeScale(4.9999) should be 3 (and is)\r\n quantizeScale(5) should be 4 (but is 3)\r\n quantizeScale(5.001) should be 4 (and is)\r\n \r\n The inconsistent behavior causes lots of subtle problems.\r\n I have a proposed fix, but I'm not sure if it is the best fix, or if it works in all cases.\r\n The fix is in d3's quantize.js\r\n \r\n I altered the scale function to be\r\n\r\n```js\r\nfunction scale(x) {\r\n  return range[Math.max(0, Math.min(i, Math.floor(Number(kx * (x - x0)).toFixed(12))))];\r\n}\r\n```\r\n  \r\n and the invertExtent function to be\r\n \r\n```js\r\nscale.invertExtent = function(y) {\r\n  y = range.indexOf(y);\r\n  y = y < 0 ? NaN : y / kx + x0;\r\n  return [+Number(y).toFixed(12), +Number(y + 1 / kx).toFixed(12)];\r\n};\r\n```\r\n\r\nThis makes sure that numbers like 5.999999999999 or 4.0000000000001 get rounded to something reasonable so that the correct bin is selected when we are on the boundary.\r\n"},"comment":{"url":"https://api.github.com/repos/mbostock/d3/issues/comments/165302460","html_url":"https://github.com/mbostock/d3/issues/2513#issuecomment-165302460","issue_url":"https://api.github.com/repos/mbostock/d3/issues/2513","id":165302460,"user":{"login":"mbostock","id":230541,"avatar_url":"https://avatars.githubusercontent.com/u/230541?v=3","gravatar_id":"","url":"https://api.github.com/users/mbostock","html_url":"https://github.com/mbostock","followers_url":"https://api.github.com/users/mbostock/followers","following_url":"https://api.github.com/users/mbostock/following{/other_user}","gists_url":"https://api.github.com/users/mbostock/gists{/gist_id}","starred_url":"https://api.github.com/users/mbostock/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mbostock/subscriptions","organizations_url":"https://api.github.com/users/mbostock/orgs","repos_url":"https://api.github.com/users/mbostock/repos","events_url":"https://api.github.com/users/mbostock/events{/privacy}","received_events_url":"https://api.github.com/users/mbostock/received_events","type":"User","site_admin":false},"created_at":"2015-12-17T00:47:15Z","updated_at":"2015-12-17T00:47:15Z","body":"This has been fixed in d3-scale for 4.0; see d3/d3-scale@1b07e7abc38253df4beb35f1d785f42f7e0a8c55. My solution was to compute explicit quantization thresholds and then bisect, like a threshold scale, so that *quantize*.invertExtent(*y*) is guaranteed to be consistent with *quantize*(*x*). I also made the threshold computation slightly more accurate, but it’s still floating point."}}