diff --git a/src/traces/box/plot.js b/src/traces/box/plot.js index 81b47d715c3..5d251686f9c 100644 --- a/src/traces/box/plot.js +++ b/src/traces/box/plot.js @@ -132,7 +132,10 @@ module.exports = function plot(gd, plotinfo, cdbox) { .data(function(d) { var pts = (trace.boxpoints === 'all') ? d.val : d.val.filter(function(v) { return (v < d.lf || v > d.uf); }), - spreadLimit = (d.q3 - d.q1) * JITTERSPREAD, + // normally use IQR, but if this is 0 or too small, use max-min + typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1), + minSpread = typicalSpread * 1e-9, + spreadLimit = typicalSpread * JITTERSPREAD, jitterFactors = [], maxJitterFactor = 0, i, @@ -144,22 +147,32 @@ module.exports = function plot(gd, plotinfo, cdbox) { // dynamic jitter if(trace.jitter) { - for(i = 0; i < pts.length; i++) { - i0 = Math.max(0, i - JITTERCOUNT); - pmin = pts[i0]; - i1 = Math.min(pts.length - 1, i + JITTERCOUNT); - pmax = pts[i1]; - - if(trace.boxpoints !== 'all') { - if(pts[i] < d.lf) pmax = Math.min(pmax, d.lf); - else pmin = Math.max(pmin, d.uf); + if(typicalSpread === 0) { + // edge case of no spread at all: fall back to max jitter + maxJitterFactor = 1; + jitterFactors = new Array(pts.length); + for(i = 0; i < pts.length; i++) { + jitterFactors[i] = 1; + } + } + else { + for(i = 0; i < pts.length; i++) { + i0 = Math.max(0, i - JITTERCOUNT); + pmin = pts[i0]; + i1 = Math.min(pts.length - 1, i + JITTERCOUNT); + pmax = pts[i1]; + + if(trace.boxpoints !== 'all') { + if(pts[i] < d.lf) pmax = Math.min(pmax, d.lf); + else pmin = Math.max(pmin, d.uf); + } + + jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0; + jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1); + + jitterFactors.push(jitterFactor); + maxJitterFactor = Math.max(jitterFactor, maxJitterFactor); } - - jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin)) || 0; - jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1); - - jitterFactors.push(jitterFactor); - maxJitterFactor = Math.max(jitterFactor, maxJitterFactor); } newJitter = trace.jitter * 2 / maxJitterFactor; } diff --git a/test/image/baselines/box_plot_jitter_edge_cases.png b/test/image/baselines/box_plot_jitter_edge_cases.png new file mode 100644 index 00000000000..1853a20a16c Binary files /dev/null and b/test/image/baselines/box_plot_jitter_edge_cases.png differ diff --git a/test/image/mocks/box_plot_jitter_edge_cases.json b/test/image/mocks/box_plot_jitter_edge_cases.json new file mode 100644 index 00000000000..5478454503b --- /dev/null +++ b/test/image/mocks/box_plot_jitter_edge_cases.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "y": [0,0,2.4,0,0,2,0,0,0,0,0,0,62,0,0,0,13,0,0,0,0,38,0,0,0,0,0,0,0,0], + "type": "box", + "boxpoints": "all", + "jitter": 0, + "name": "No whiskers, no jitter" + }, + { + "y": [0,0,2.4,0,0,2,0,0,0,0,0,0,62,0,0,0,13,0,0,0,0,38,0,0,0,0,0,0,0,0], + "type": "box", + "boxpoints": "all", + "jitter": 0.5, + "name": "No whiskers, jitter 0.5" + }, + { + "y": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + "type": "box", + "boxpoints": "all", + "jitter": 0, + "name": "No range, no jitter" + }, + { + "y": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + "type": "box", + "boxpoints": "all", + "jitter": 0.5, + "name": "No range, jitter 0.5" + } + ] +}