diff --git a/draftlogs/7581_fix.md b/draftlogs/7581_fix.md new file mode 100644 index 00000000000..c3f1a5950dc --- /dev/null +++ b/draftlogs/7581_fix.md @@ -0,0 +1 @@ +- Fix KDE sampling precision in violin trace to eliminate floating-point drift and prevent density underrun/overrun ([#7581](https://github.com/plotly/plotly.js/pull/7581)) diff --git a/src/traces/violin/calc.js b/src/traces/violin/calc.js index 39fa549097b..4baad80e0d0 100644 --- a/src/traces/violin/calc.js +++ b/src/traces/violin/calc.js @@ -48,9 +48,11 @@ module.exports = function calc(gd, trace) { } var kde = helpers.makeKDE(cdi, trace, vals); - cdi.density = new Array(n); + // n intervals means n + 1 sample points to include both endpoints + cdi.density = new Array(n + 1); - for(var k = 0, t = span[0]; t < (span[1] + step / 2); k++, t += step) { + for(var k = 0; k <= n; k++) { + var t = span[0] + k * step; var v = kde(t); cdi.density[k] = {v: v, t: t}; maxKDE = Math.max(maxKDE, v); diff --git a/test/image/baselines/violin_box_overlay.png b/test/image/baselines/violin_box_overlay.png index 7150d01fb0b..d03af33ebb6 100644 Binary files a/test/image/baselines/violin_box_overlay.png and b/test/image/baselines/violin_box_overlay.png differ diff --git a/test/jasmine/tests/violin_test.js b/test/jasmine/tests/violin_test.js index c96a8dedd06..375aa92aa39 100644 --- a/test/jasmine/tests/violin_test.js +++ b/test/jasmine/tests/violin_test.js @@ -401,6 +401,20 @@ describe('Test violin calc:', function() { expect(cd.length).toBe(1, '# of violins'); expect(cd[0].bandwidth).toBe(0, 'bandwidth'); }); + + it('should produce exactly n + 1 density samples for tiny or near-equal spans', function() { + var cd = _calc({ + type: 'violin', + x: [0, 0], + y: [0.5006312999999999, 0.5006313] + }); + var cdi = cd[0]; + + var dist = cdi.span[1] - cdi.span[0]; + var n = Math.ceil(dist / (cdi.bandwidth / 3)); + + expect(cdi.density.length).toBe(n + 1); + }); }); describe('Test violin hover:', function() {