diff --git a/src/lib/search.js b/src/lib/search.js index de1d634160a..6b24c77cf3c 100644 --- a/src/lib/search.js +++ b/src/lib/search.js @@ -71,7 +71,9 @@ exports.sorterDes = function(a, b) { return b - a; }; * just be off by a rounding error * return the distinct values and the minimum difference between any two */ -exports.distinctVals = function(valsIn) { +exports.distinctVals = function(valsIn, opts) { + var unitMinDiff = (opts || {}).unitMinDiff; + var vals = valsIn.slice(); // otherwise we sort the original array... vals.sort(exports.sorterAsc); // undefined listed in the end - also works on IE11 @@ -80,7 +82,9 @@ exports.distinctVals = function(valsIn) { if(vals[last] !== BADNUM) break; } - var minDiff = (vals[last] - vals[0]) || 1; + var minDiff = 1; + if(!unitMinDiff) minDiff = (vals[last] - vals[0]) || 1; + var errDiff = minDiff / (last || 1) / 10000; var newVals = []; var preV; diff --git a/src/traces/bar/cross_trace_calc.js b/src/traces/bar/cross_trace_calc.js index 628bfb1396d..1bb5dda2f81 100644 --- a/src/traces/bar/cross_trace_calc.js +++ b/src/traces/bar/cross_trace_calc.js @@ -59,6 +59,9 @@ function crossTraceCalc(gd, plotinfo) { } var opts = { + xCat: xa.type === 'category' || xa.type === 'multicategory', + yCat: ya.type === 'category' || ya.type === 'multicategory', + mode: fullLayout.barmode, norm: fullLayout.barnorm, gap: fullLayout.bargap, @@ -177,6 +180,7 @@ function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) { var calcTrace = calcTraces[i]; var sieve = new Sieve([calcTrace], { + unitMinDiff: opts.xCat || opts.yCat, sepNegVal: false, overlapNoMerge: !opts.norm }); diff --git a/src/traces/bar/sieve.js b/src/traces/bar/sieve.js index 15052f6f1a9..7cc69f6e81e 100644 --- a/src/traces/bar/sieve.js +++ b/src/traces/bar/sieve.js @@ -48,7 +48,10 @@ function Sieve(traces, opts) { } this.positions = positions; - var dv = distinctVals(positions); + var dv = distinctVals(positions, { + unitMinDiff: opts.unitMinDiff + }); + this.distinctPositions = dv.vals; if(dv.vals.length === 1 && width1 !== Infinity) this.minDiff = width1; else this.minDiff = Math.min(dv.minDiff, width1); diff --git a/src/traces/box/cross_trace_calc.js b/src/traces/box/cross_trace_calc.js index 478dea16abc..72b57856cfb 100644 --- a/src/traces/box/cross_trace_calc.js +++ b/src/traces/box/cross_trace_calc.js @@ -68,7 +68,10 @@ function setPositionOffset(traceType, gd, boxList, posAxis) { if(!pointList.length) return; // box plots - update dPos based on multiple traces - var boxdv = Lib.distinctVals(pointList); + var boxdv = Lib.distinctVals(pointList, { + unitMinDiff: posAxis.type === 'category' || posAxis.type === 'multicategory' + }); + var dPos0 = boxdv.minDiff / 2; // check for forced minimum dtick diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 5b162e897af..8b835f36907 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -969,6 +969,23 @@ describe('Bar.crossTraceCalc (formerly known as setPositions)', function() { assertPointField(gd.calcdata, 'b', [[0, 0, 0, 0]]); }); + it('should set unit width for categories in overlay mode', function() { + var gd = mockBarPlot([{ + type: 'bar', + x: ['a', 'b', 'c'], + y: [2, 2, 2] + }, + { + type: 'bar', + x: ['a', 'c'], + y: [1, 1] + }], { + barmode: 'overlay' + }); + + expect(gd.calcdata[1][0].t.bardelta).toBe(1); + }); + describe('should relative-stack bar within the same trace that overlap under barmode=group', function() { it('- base case', function() { var gd = mockBarPlot([{ diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js index 58ea50c048d..9ea11d6b406 100644 --- a/test/jasmine/tests/box_test.js +++ b/test/jasmine/tests/box_test.js @@ -1534,3 +1534,51 @@ describe('Test box calc', function() { }); }); }); + + +describe('Box crossTraceCalc', function() { + 'use strict'; + + function mockBoxPlot(dataWithoutTraceType, layout) { + var traceTemplate = { type: 'box' }; + + var dataWithTraceType = dataWithoutTraceType.map(function(trace) { + return Lib.extendFlat({}, traceTemplate, trace); + }); + + var gd = { + data: dataWithTraceType, + layout: layout || {}, + calcdata: [], + _context: {locale: 'en', locales: {}} + }; + + supplyAllDefaults(gd); + Plots.doCalcdata(gd); + + return gd; + } + + it('should set unit width for categories in overlay mode', function() { + var gd = mockBoxPlot([{ + y: [1, 2, 3] + }, + { + y: [null, null, null] + }, + { + y: [null, null, null] + }, + { + y: [4, 5, 6] + }], { + boxgap: 0, + xaxis: { + range: [-0.5, 3.5], + type: 'category' + } + }); + + expect(gd.calcdata[0][0].t.dPos).toBe(0.5); + }); +});