Skip to content
1 change: 1 addition & 0 deletions draftlogs/7581_fix.md
Original file line number Diff line number Diff line change
@@ -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))
6 changes: 4 additions & 2 deletions src/traces/violin/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Binary file modified test/image/baselines/violin_box_overlay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions test/jasmine/tests/violin_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down