From 03e5072f72359da8e51e04aeea24c326bd536d7b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 6 Oct 2016 15:27:31 +0100 Subject: [PATCH 01/13] bar: use descriptive variable names * Simplified algorithm to identify overlapping bars. * Removed the closure setBarCenter. * Checked that jasmine tests still pass. --- src/traces/bar/set_positions.js | 87 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 3fea9a1d58c..2c9059b5dea 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -53,60 +53,63 @@ module.exports = function setPositions(gd, plotinfo) { // for stacked or grouped bars, this is all vertical or horizontal // bars for overlaid bars, call this individually on each trace. function barposition(bl1) { - // find the min. difference between any points - // in any traces in bl1 - var pvals = []; - bl1.forEach(function(i) { - gd.calcdata[i].forEach(function(v) { pvals.push(v.p); }); - }); - var dv = Lib.distinctVals(pvals), - pv2 = dv.vals, - barDiff = dv.minDiff; - - // check if all the traces have only independent positions - // if so, let them have full width even if mode is group - var overlap = false, - comparelist = []; + var traces = bl1.map(function(i) { return gd.calcdata[i]; }); + + var positions = [], + i, trace, + j, bar; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + positions.push(bar.p); + } + } + + var dv = Lib.distinctVals(positions), + distinctPositions = dv.vals, + minDiff = dv.minDiff; + // check if there are any overlapping positions; + // if there aren't, let them have full width even if mode is group + var overlap = false; if(fullLayout.barmode === 'group') { - bl1.forEach(function(i) { - if(overlap) return; - gd.calcdata[i].forEach(function(v) { - if(overlap) return; - comparelist.forEach(function(cp) { - if(Math.abs(v.p - cp) < barDiff) overlap = true; - }); - }); - if(overlap) return; - gd.calcdata[i].forEach(function(v) { - comparelist.push(v.p); - }); - }); + overlap = (positions.length !== distinctPositions.length); } // check forced minimum dtick - Axes.minDtick(pa, barDiff, pv2[0], overlap); + Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); // position axis autorange - always tight fitting - Axes.expand(pa, pv2, {vpad: barDiff / 2}); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // bar widths and position offsets - barDiff *= 1 - fullLayout.bargap; - if(overlap) barDiff /= bl.length; + // computer bar widths and position offsets + var barWidth = minDiff * (1 - fullLayout.bargap); + if(overlap) barWidth /= bl.length; - var barCenter; - function setBarCenter(v) { v[pLetter] = v.p + barCenter; } + var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); - for(var i = 0; i < bl1.length; i++) { - var t = gd.calcdata[bl1[i]][0].t; - t.barwidth = barDiff * (1 - fullLayout.bargroupgap); - t.poffset = ((overlap ? (2 * i + 1 - bl1.length) * barDiff : 0) - - t.barwidth) / 2; - t.dbar = dv.minDiff; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + + // computer bar group center and bar offset + var offsetFromCenter = ( + (overlap ? (2 * i + 1 - bl1.length) * barWidth : 0) - + barWidthMinusGroupGap + ) / 2, + barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; + + // store bar width and offset for this trace + var t = trace[0].t; + t.barwidth = barWidthMinusGroupGap; + t.poffset = offsetFromCenter; + t.dbar = minDiff; // store the bar center in each calcdata item - barCenter = t.poffset + t.barwidth / 2; - gd.calcdata[bl1[i]].forEach(setBarCenter); + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + bar[pLetter] = bar.p + barCenter; + } } } From bbdc38558ced13fc415230f809cac0989d9967ed Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 7 Oct 2016 13:12:41 +0100 Subject: [PATCH 02/13] bar: convert closure into function * Converted the closure that groups vertical and horizontal bars into a function. * This change will help split setPositions into functions for each barmode. --- src/traces/bar/set_positions.js | 344 ++++++++++++++++---------------- 1 file changed, 173 insertions(+), 171 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 2c9059b5dea..f15a15a91fa 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -23,210 +23,212 @@ var Lib = require('../../lib'); */ module.exports = function setPositions(gd, plotinfo) { + setGroupPositions(gd, plotinfo, 'v'); + setGroupPositions(gd, plotinfo, 'h'); +}; + +function setGroupPositions(gd, plotinfo, dir) { var fullLayout = gd._fullLayout, xa = plotinfo.xaxis, ya = plotinfo.yaxis, i, j; - ['v', 'h'].forEach(function(dir) { - var bl = [], - pLetter = {v: 'x', h: 'y'}[dir], - sLetter = {v: 'y', h: 'x'}[dir], - pa = plotinfo[pLetter + 'axis'], - sa = plotinfo[sLetter + 'axis']; - - gd._fullData.forEach(function(trace, i) { - if(trace.visible === true && - Registry.traceIs(trace, 'bar') && - trace.orientation === dir && - trace.xaxis === xa._id && - trace.yaxis === ya._id) { - bl.push(i); - } - }); - - if(!bl.length) return; - - // bar position offset and width calculation - // bl1 is a list of traces (in calcdata) to look at together - // to find the maximum size bars that won't overlap - // for stacked or grouped bars, this is all vertical or horizontal - // bars for overlaid bars, call this individually on each trace. - function barposition(bl1) { - var traces = bl1.map(function(i) { return gd.calcdata[i]; }); - - var positions = [], - i, trace, - j, bar; - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - positions.push(bar.p); - } + var bl = [], + pLetter = {v: 'x', h: 'y'}[dir], + sLetter = {v: 'y', h: 'x'}[dir], + pa = plotinfo[pLetter + 'axis'], + sa = plotinfo[sLetter + 'axis']; + + gd._fullData.forEach(function(trace, i) { + if(trace.visible === true && + Registry.traceIs(trace, 'bar') && + trace.orientation === dir && + trace.xaxis === xa._id && + trace.yaxis === ya._id) { + bl.push(i); + } + }); + + if(!bl.length) return; + + // bar position offset and width calculation + // bl1 is a list of traces (in calcdata) to look at together + // to find the maximum size bars that won't overlap + // for stacked or grouped bars, this is all vertical or horizontal + // bars for overlaid bars, call this individually on each trace. + function barposition(bl1) { + var traces = bl1.map(function(i) { return gd.calcdata[i]; }); + + var positions = [], + i, trace, + j, bar; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + for(j = 0; j < trace.length; j++) { + bar = trace[j]; } + } - var dv = Lib.distinctVals(positions), - distinctPositions = dv.vals, - minDiff = dv.minDiff; + var dv = Lib.distinctVals(positions), + distinctPositions = dv.vals, + minDiff = dv.minDiff; - // check if there are any overlapping positions; - // if there aren't, let them have full width even if mode is group - var overlap = false; - if(fullLayout.barmode === 'group') { - overlap = (positions.length !== distinctPositions.length); - } + // check if there are any overlapping positions; + // if there aren't, let them have full width even if mode is group + var overlap = false; + if(fullLayout.barmode === 'group') { + overlap = (positions.length !== distinctPositions.length); + } - // check forced minimum dtick - Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); + // check forced minimum dtick + Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); - // position axis autorange - always tight fitting - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); + // position axis autorange - always tight fitting + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // computer bar widths and position offsets - var barWidth = minDiff * (1 - fullLayout.bargap); - if(overlap) barWidth /= bl.length; + // computer bar widths and position offsets + var barWidth = minDiff * (1 - fullLayout.bargap); + if(overlap) barWidth /= bl.length; - var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); + var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); - for(i = 0; i < traces.length; i++) { - trace = traces[i]; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; - // computer bar group center and bar offset - var offsetFromCenter = ( - (overlap ? (2 * i + 1 - bl1.length) * barWidth : 0) - - barWidthMinusGroupGap - ) / 2, - barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; + // computer bar group center and bar offset + var offsetFromCenter = ( + (overlap ? (2 * i + 1 - bl1.length) * barWidth : 0) - + barWidthMinusGroupGap + ) / 2, + barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; - // store bar width and offset for this trace - var t = trace[0].t; - t.barwidth = barWidthMinusGroupGap; - t.poffset = offsetFromCenter; - t.dbar = minDiff; + // store bar width and offset for this trace + var t = trace[0].t; + t.barwidth = barWidthMinusGroupGap; + t.poffset = offsetFromCenter; + t.dbar = minDiff; - // store the bar center in each calcdata item - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - bar[pLetter] = bar.p + barCenter; - } + // store the bar center in each calcdata item + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + bar[pLetter] = bar.p + barCenter; } } - - if(fullLayout.barmode === 'overlay') { - bl.forEach(function(bli) { barposition([bli]); }); - } - else barposition(bl); - - var stack = (fullLayout.barmode === 'stack'), - relative = (fullLayout.barmode === 'relative'), - norm = fullLayout.barnorm; - - // bar size range and stacking calculation - if(stack || relative || norm) { - // for stacked bars, we need to evaluate every step in every - // stack, because negative bars mean the extremes could be - // anywhere - // also stores the base (b) of each bar in calcdata - // so we don't have to redo this later - var sMax = sa.l2c(sa.c2l(0)), - sMin = sMax, - sums = {}, - - // make sure if p is different only by rounding, - // we still stack - sumround = gd.calcdata[bl[0]][0].t.barwidth / 100, - sv = 0, - padded = true, - barEnd, - ti, - scale; - - for(i = 0; i < bl.length; i++) { // trace index - ti = gd.calcdata[bl[i]]; - for(j = 0; j < ti.length; j++) { - - // skip over bars with no size, - // so that we don't try to stack them - if(!isNumeric(ti[j].s)) continue; - - sv = Math.round(ti[j].p / sumround); - - // store the negative sum value for p at the same key, - // with sign flipped using string to ensure -0 !== 0. - if(relative && ti[j].s < 0) sv = '-' + sv; - - var previousSum = sums[sv] || 0; - if(stack || relative) ti[j].b = previousSum; - barEnd = ti[j].b + ti[j].s; - sums[sv] = previousSum + ti[j].s; - - // store the bar top in each calcdata item - if(stack || relative) { - ti[j][sLetter] = barEnd; - if(!norm && isNumeric(sa.c2l(barEnd))) { - sMax = Math.max(sMax, barEnd); - sMin = Math.min(sMin, barEnd); - } + } + + if(fullLayout.barmode === 'overlay') { + bl.forEach(function(bli) { barposition([bli]); }); + } + else barposition(bl); + + var stack = (fullLayout.barmode === 'stack'), + relative = (fullLayout.barmode === 'relative'), + norm = fullLayout.barnorm; + + // bar size range and stacking calculation + if(stack || relative || norm) { + // for stacked bars, we need to evaluate every step in every + // stack, because negative bars mean the extremes could be + // anywhere + // also stores the base (b) of each bar in calcdata + // so we don't have to redo this later + var sMax = sa.l2c(sa.c2l(0)), + sMin = sMax, + sums = {}, + + // make sure if p is different only by rounding, + // we still stack + sumround = gd.calcdata[bl[0]][0].t.barwidth / 100, + sv = 0, + padded = true, + barEnd, + ti, + scale; + + for(i = 0; i < bl.length; i++) { // trace index + ti = gd.calcdata[bl[i]]; + for(j = 0; j < ti.length; j++) { + + // skip over bars with no size, + // so that we don't try to stack them + if(!isNumeric(ti[j].s)) continue; + + sv = Math.round(ti[j].p / sumround); + + // store the negative sum value for p at the same key, + // with sign flipped using string to ensure -0 !== 0. + if(relative && ti[j].s < 0) sv = '-' + sv; + + var previousSum = sums[sv] || 0; + if(stack || relative) ti[j].b = previousSum; + barEnd = ti[j].b + ti[j].s; + sums[sv] = previousSum + ti[j].s; + + // store the bar top in each calcdata item + if(stack || relative) { + ti[j][sLetter] = barEnd; + if(!norm && isNumeric(sa.c2l(barEnd))) { + sMax = Math.max(sMax, barEnd); + sMin = Math.min(sMin, barEnd); } } } + } - if(norm) { - var top = norm === 'fraction' ? 1 : 100, - relAndNegative = false, - tiny = top / 1e9; // in case of rounding error in sum + if(norm) { + var top = norm === 'fraction' ? 1 : 100, + relAndNegative = false, + tiny = top / 1e9; // in case of rounding error in sum - padded = false; - sMin = 0; - sMax = stack ? top : 0; + padded = false; + sMin = 0; + sMax = stack ? top : 0; - for(i = 0; i < bl.length; i++) { // trace index - ti = gd.calcdata[bl[i]]; + for(i = 0; i < bl.length; i++) { // trace index + ti = gd.calcdata[bl[i]]; - for(j = 0; j < ti.length; j++) { - relAndNegative = (relative && ti[j].s < 0); + for(j = 0; j < ti.length; j++) { + relAndNegative = (relative && ti[j].s < 0); - sv = Math.round(ti[j].p / sumround); + sv = Math.round(ti[j].p / sumround); - // locate negative sum amount for this p val - if(relAndNegative) sv = '-' + sv; + // locate negative sum amount for this p val + if(relAndNegative) sv = '-' + sv; - scale = top / sums[sv]; + scale = top / sums[sv]; - // preserve sign if negative - if(relAndNegative) scale *= -1; - ti[j].b *= scale; - ti[j].s *= scale; - barEnd = ti[j].b + ti[j].s; - ti[j][sLetter] = barEnd; + // preserve sign if negative + if(relAndNegative) scale *= -1; + ti[j].b *= scale; + ti[j].s *= scale; + barEnd = ti[j].b + ti[j].s; + ti[j][sLetter] = barEnd; - if(isNumeric(sa.c2l(barEnd))) { - if(barEnd < sMin - tiny) { - padded = true; - sMin = barEnd; - } - if(barEnd > sMax + tiny) { - padded = true; - sMax = barEnd; - } + if(isNumeric(sa.c2l(barEnd))) { + if(barEnd < sMin - tiny) { + padded = true; + sMin = barEnd; + } + if(barEnd > sMax + tiny) { + padded = true; + sMax = barEnd; } } } } - - Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}); } - else { - // for grouped or overlaid bars, just make sure zero is - // included, along with the tops of each bar, and store - // these bar tops in calcdata - var fs = function(v) { v[sLetter] = v.s; return v.s; }; - - for(i = 0; i < bl.length; i++) { - Axes.expand(sa, gd.calcdata[bl[i]].map(fs), - {tozero: true, padded: true}); - } + + Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}); + } + else { + // for grouped or overlaid bars, just make sure zero is + // included, along with the tops of each bar, and store + // these bar tops in calcdata + var fs = function(v) { v[sLetter] = v.s; return v.s; }; + + for(i = 0; i < bl.length; i++) { + Axes.expand(sa, gd.calcdata[bl[i]].map(fs), + {tozero: true, padded: true}); } - }); -}; + } +} From 7b14fcd1383777a8000fd85fa9b2064945d93c1b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 7 Oct 2016 14:51:52 +0100 Subject: [PATCH 03/13] bar: refactor code to group bars * Moved code to group bars from setGroupPositions to setPositions. --- src/traces/bar/set_positions.js | 81 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index f15a15a91fa..e5611ea5c6e 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -23,42 +23,45 @@ var Lib = require('../../lib'); */ module.exports = function setPositions(gd, plotinfo) { - setGroupPositions(gd, plotinfo, 'v'); - setGroupPositions(gd, plotinfo, 'h'); + var xa = plotinfo.xaxis, + ya = plotinfo.yaxis; + + var traces = gd._fullData, + tracesCalc = gd.calcdata, + tracesHorizontal = [], + tracesVertical = [], + i; + for(i = 0; i < traces.length; i++) { + var trace = traces[i]; + if( + trace.visible === true && + Registry.traceIs(trace, 'bar') && + trace.xaxis === xa._id && + trace.yaxis === ya._id + ) { + if(trace.orientation === 'h') tracesHorizontal.push(tracesCalc[i]); + else tracesVertical.push(tracesCalc[i]); + } + } + + setGroupPositions(gd, xa, ya, tracesVertical); + setGroupPositions(gd, ya, xa, tracesHorizontal); }; -function setGroupPositions(gd, plotinfo, dir) { +function setGroupPositions(gd, pa, sa, traces) { + if(!traces.length) return; + var fullLayout = gd._fullLayout, - xa = plotinfo.xaxis, - ya = plotinfo.yaxis, + pLetter = pa._id.charAt(0), + sLetter = sa._id.charAt(0), i, j; - var bl = [], - pLetter = {v: 'x', h: 'y'}[dir], - sLetter = {v: 'y', h: 'x'}[dir], - pa = plotinfo[pLetter + 'axis'], - sa = plotinfo[sLetter + 'axis']; - - gd._fullData.forEach(function(trace, i) { - if(trace.visible === true && - Registry.traceIs(trace, 'bar') && - trace.orientation === dir && - trace.xaxis === xa._id && - trace.yaxis === ya._id) { - bl.push(i); - } - }); - - if(!bl.length) return; - // bar position offset and width calculation - // bl1 is a list of traces (in calcdata) to look at together + // traces is a list of traces (in calcdata) to look at together // to find the maximum size bars that won't overlap // for stacked or grouped bars, this is all vertical or horizontal // bars for overlaid bars, call this individually on each trace. - function barposition(bl1) { - var traces = bl1.map(function(i) { return gd.calcdata[i]; }); - + function barposition(traces) { var positions = [], i, trace, j, bar; @@ -88,7 +91,7 @@ function setGroupPositions(gd, plotinfo, dir) { // computer bar widths and position offsets var barWidth = minDiff * (1 - fullLayout.bargap); - if(overlap) barWidth /= bl.length; + if(overlap) barWidth /= traces.length; var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); @@ -97,7 +100,7 @@ function setGroupPositions(gd, plotinfo, dir) { // computer bar group center and bar offset var offsetFromCenter = ( - (overlap ? (2 * i + 1 - bl1.length) * barWidth : 0) - + (overlap ? (2 * i + 1 - traces.length) * barWidth : 0) - barWidthMinusGroupGap ) / 2, barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; @@ -117,9 +120,9 @@ function setGroupPositions(gd, plotinfo, dir) { } if(fullLayout.barmode === 'overlay') { - bl.forEach(function(bli) { barposition([bli]); }); + traces.forEach(function(trace) { barposition([trace]); }); } - else barposition(bl); + else barposition(traces); var stack = (fullLayout.barmode === 'stack'), relative = (fullLayout.barmode === 'relative'), @@ -138,15 +141,16 @@ function setGroupPositions(gd, plotinfo, dir) { // make sure if p is different only by rounding, // we still stack - sumround = gd.calcdata[bl[0]][0].t.barwidth / 100, + sumround = traces[0][0].t.barwidth / 100, sv = 0, padded = true, barEnd, ti, scale; - for(i = 0; i < bl.length; i++) { // trace index - ti = gd.calcdata[bl[i]]; + for(i = 0; i < traces.length; i++) { // trace index + ti = traces[i]; + for(j = 0; j < ti.length; j++) { // skip over bars with no size, @@ -184,8 +188,8 @@ function setGroupPositions(gd, plotinfo, dir) { sMin = 0; sMax = stack ? top : 0; - for(i = 0; i < bl.length; i++) { // trace index - ti = gd.calcdata[bl[i]]; + for(i = 0; i < traces.length; i++) { // trace index + ti = traces[i]; for(j = 0; j < ti.length; j++) { relAndNegative = (relative && ti[j].s < 0); @@ -226,9 +230,8 @@ function setGroupPositions(gd, plotinfo, dir) { // these bar tops in calcdata var fs = function(v) { v[sLetter] = v.s; return v.s; }; - for(i = 0; i < bl.length; i++) { - Axes.expand(sa, gd.calcdata[bl[i]].map(fs), - {tozero: true, padded: true}); + for(i = 0; i < traces.length; i++) { + Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); } } } From fe66e3cc17d83dc20071bbfba155670fea4d3531 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 10 Oct 2016 14:17:01 +0100 Subject: [PATCH 04/13] bar: rename `barpositions` to `setOffsetAndWidth` * Converted closure `barpositions` into function `setOffsetAndWidth`. * This change will help split setPositions into functions for each barmode. --- src/traces/bar/set_positions.js | 138 +++++++++++++++++--------------- 1 file changed, 73 insertions(+), 65 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index e5611ea5c6e..26456630161 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -48,6 +48,7 @@ module.exports = function setPositions(gd, plotinfo) { setGroupPositions(gd, ya, xa, tracesHorizontal); }; + function setGroupPositions(gd, pa, sa, traces) { if(!traces.length) return; @@ -56,73 +57,12 @@ function setGroupPositions(gd, pa, sa, traces) { sLetter = sa._id.charAt(0), i, j; - // bar position offset and width calculation - // traces is a list of traces (in calcdata) to look at together - // to find the maximum size bars that won't overlap - // for stacked or grouped bars, this is all vertical or horizontal - // bars for overlaid bars, call this individually on each trace. - function barposition(traces) { - var positions = [], - i, trace, - j, bar; - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - } - } - - var dv = Lib.distinctVals(positions), - distinctPositions = dv.vals, - minDiff = dv.minDiff; - - // check if there are any overlapping positions; - // if there aren't, let them have full width even if mode is group - var overlap = false; - if(fullLayout.barmode === 'group') { - overlap = (positions.length !== distinctPositions.length); - } - - // check forced minimum dtick - Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); - - // position axis autorange - always tight fitting - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - - // computer bar widths and position offsets - var barWidth = minDiff * (1 - fullLayout.bargap); - if(overlap) barWidth /= traces.length; - - var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); - - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - - // computer bar group center and bar offset - var offsetFromCenter = ( - (overlap ? (2 * i + 1 - traces.length) * barWidth : 0) - - barWidthMinusGroupGap - ) / 2, - barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; - - // store bar width and offset for this trace - var t = trace[0].t; - t.barwidth = barWidthMinusGroupGap; - t.poffset = offsetFromCenter; - t.dbar = minDiff; - - // store the bar center in each calcdata item - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - bar[pLetter] = bar.p + barCenter; - } - } - } - if(fullLayout.barmode === 'overlay') { - traces.forEach(function(trace) { barposition([trace]); }); + traces.forEach(function(trace) { + setOffsetAndWidth(gd, pa, pLetter, [trace]); + }); } - else barposition(traces); + else setOffsetAndWidth(gd, pa, pLetter, traces); var stack = (fullLayout.barmode === 'stack'), relative = (fullLayout.barmode === 'relative'), @@ -235,3 +175,71 @@ function setGroupPositions(gd, pa, sa, traces) { } } } + + +// bar position offset and width calculation +// traces is a list of traces (in calcdata) to look at together +// to find the maximum width bars that won't overlap +// for stacked or grouped bars, this is all vertical or horizontal +// bars for overlaid bars, call this individually on each trace. +function setOffsetAndWidth(gd, pa, pLetter, traces) { + var fullLayout = gd._fullLayout, + i, trace, + j, bar; + + // make list of bar positions + var positions = []; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + positions.push(bar.p); + } + } + + var dv = Lib.distinctVals(positions), + distinctPositions = dv.vals, + minDiff = dv.minDiff; + + // check if there are any overlapping positions; + // if there aren't, let them have full width even if mode is group + var overlap = false; + if(fullLayout.barmode === 'group') { + overlap = (positions.length !== distinctPositions.length); + } + + // check forced minimum dtick + Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); + + // position axis autorange - always tight fitting + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); + + // computer bar widths and position offsets + var barWidth = minDiff * (1 - fullLayout.bargap); + if(overlap) barWidth /= traces.length; + + var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); + + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + + // computer bar group center and bar offset + var offsetFromCenter = ( + (overlap ? (2 * i + 1 - traces.length) * barWidth : 0) - + barWidthMinusGroupGap + ) / 2, + barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; + + // store bar width and offset for this trace + var t = trace[0].t; + t.barwidth = barWidthMinusGroupGap; + t.poffset = offsetFromCenter; + t.dbar = minDiff; + + // store the bar center in each calcdata item + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + bar[pLetter] = bar.p + barCenter; + } + } +} From d422f675721e27ca9f9a70592fc9e0f55146687b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 10 Oct 2016 15:04:39 +0100 Subject: [PATCH 05/13] bar: refactor code into function `setBaseAndSize` --- src/traces/bar/set_positions.js | 186 +++++++++++++++++--------------- 1 file changed, 98 insertions(+), 88 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 26456630161..cd8aa905c5b 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -54,8 +54,7 @@ function setGroupPositions(gd, pa, sa, traces) { var fullLayout = gd._fullLayout, pLetter = pa._id.charAt(0), - sLetter = sa._id.charAt(0), - i, j; + sLetter = sa._id.charAt(0); if(fullLayout.barmode === 'overlay') { traces.forEach(function(trace) { @@ -64,6 +63,83 @@ function setGroupPositions(gd, pa, sa, traces) { } else setOffsetAndWidth(gd, pa, pLetter, traces); + setBaseAndSize(gd, sa, sLetter, traces); +} + + +// bar position offset and width calculation +// traces is a list of traces (in calcdata) to look at together +// to find the maximum width bars that won't overlap +// for stacked or grouped bars, this is all vertical or horizontal +// bars for overlaid bars, call this individually on each trace. +function setOffsetAndWidth(gd, pa, pLetter, traces) { + var fullLayout = gd._fullLayout, + i, trace, + j, bar; + + // make list of bar positions + var positions = []; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + positions.push(bar.p); + } + } + + var dv = Lib.distinctVals(positions), + distinctPositions = dv.vals, + minDiff = dv.minDiff; + + // check if there are any overlapping positions; + // if there aren't, let them have full width even if mode is group + var overlap = false; + if(fullLayout.barmode === 'group') { + overlap = (positions.length !== distinctPositions.length); + } + + // check forced minimum dtick + Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); + + // position axis autorange - always tight fitting + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); + + // computer bar widths and position offsets + var barWidth = minDiff * (1 - fullLayout.bargap); + if(overlap) barWidth /= traces.length; + + var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); + + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + + // computer bar group center and bar offset + var offsetFromCenter = ( + (overlap ? (2 * i + 1 - traces.length) * barWidth : 0) - + barWidthMinusGroupGap + ) / 2, + barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; + + // store bar width and offset for this trace + var t = trace[0].t; + t.barwidth = barWidthMinusGroupGap; + t.poffset = offsetFromCenter; + t.dbar = minDiff; + + // store the bar center in each calcdata item + for(j = 0; j < trace.length; j++) { + bar = trace[j]; + bar[pLetter] = bar.p + barCenter; + } + } +} + + +function setBaseAndSize(gd, sa, sLetter, traces) { + var fullLayout = gd._fullLayout, + i, trace, + j, bar; + var stack = (fullLayout.barmode === 'stack'), relative = (fullLayout.barmode === 'relative'), norm = fullLayout.barnorm; @@ -85,32 +161,32 @@ function setGroupPositions(gd, pa, sa, traces) { sv = 0, padded = true, barEnd, - ti, scale; for(i = 0; i < traces.length; i++) { // trace index - ti = traces[i]; + trace = traces[i]; - for(j = 0; j < ti.length; j++) { + for(j = 0; j < trace.length; j++) { + bar = trace[j]; // skip over bars with no size, // so that we don't try to stack them - if(!isNumeric(ti[j].s)) continue; + if(!isNumeric(bar.s)) continue; - sv = Math.round(ti[j].p / sumround); + sv = Math.round(bar.p / sumround); // store the negative sum value for p at the same key, // with sign flipped using string to ensure -0 !== 0. - if(relative && ti[j].s < 0) sv = '-' + sv; + if(relative && bar.s < 0) sv = '-' + sv; var previousSum = sums[sv] || 0; - if(stack || relative) ti[j].b = previousSum; - barEnd = ti[j].b + ti[j].s; - sums[sv] = previousSum + ti[j].s; + if(stack || relative) bar.b = previousSum; + barEnd = bar.b + bar.s; + sums[sv] = previousSum + bar.s; // store the bar top in each calcdata item if(stack || relative) { - ti[j][sLetter] = barEnd; + bar[sLetter] = barEnd; if(!norm && isNumeric(sa.c2l(barEnd))) { sMax = Math.max(sMax, barEnd); sMin = Math.min(sMin, barEnd); @@ -129,12 +205,14 @@ function setGroupPositions(gd, pa, sa, traces) { sMax = stack ? top : 0; for(i = 0; i < traces.length; i++) { // trace index - ti = traces[i]; + trace = traces[i]; - for(j = 0; j < ti.length; j++) { - relAndNegative = (relative && ti[j].s < 0); + for(j = 0; j < trace.length; j++) { + bar = trace[j]; - sv = Math.round(ti[j].p / sumround); + relAndNegative = (relative && bar.s < 0); + + sv = Math.round(bar.p / sumround); // locate negative sum amount for this p val if(relAndNegative) sv = '-' + sv; @@ -143,10 +221,10 @@ function setGroupPositions(gd, pa, sa, traces) { // preserve sign if negative if(relAndNegative) scale *= -1; - ti[j].b *= scale; - ti[j].s *= scale; - barEnd = ti[j].b + ti[j].s; - ti[j][sLetter] = barEnd; + bar.b *= scale; + bar.s *= scale; + barEnd = bar.b + bar.s; + bar[sLetter] = barEnd; if(isNumeric(sa.c2l(barEnd))) { if(barEnd < sMin - tiny) { @@ -175,71 +253,3 @@ function setGroupPositions(gd, pa, sa, traces) { } } } - - -// bar position offset and width calculation -// traces is a list of traces (in calcdata) to look at together -// to find the maximum width bars that won't overlap -// for stacked or grouped bars, this is all vertical or horizontal -// bars for overlaid bars, call this individually on each trace. -function setOffsetAndWidth(gd, pa, pLetter, traces) { - var fullLayout = gd._fullLayout, - i, trace, - j, bar; - - // make list of bar positions - var positions = []; - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - positions.push(bar.p); - } - } - - var dv = Lib.distinctVals(positions), - distinctPositions = dv.vals, - minDiff = dv.minDiff; - - // check if there are any overlapping positions; - // if there aren't, let them have full width even if mode is group - var overlap = false; - if(fullLayout.barmode === 'group') { - overlap = (positions.length !== distinctPositions.length); - } - - // check forced minimum dtick - Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); - - // position axis autorange - always tight fitting - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - - // computer bar widths and position offsets - var barWidth = minDiff * (1 - fullLayout.bargap); - if(overlap) barWidth /= traces.length; - - var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); - - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - - // computer bar group center and bar offset - var offsetFromCenter = ( - (overlap ? (2 * i + 1 - traces.length) * barWidth : 0) - - barWidthMinusGroupGap - ) / 2, - barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; - - // store bar width and offset for this trace - var t = trace[0].t; - t.barwidth = barWidthMinusGroupGap; - t.poffset = offsetFromCenter; - t.dbar = minDiff; - - // store the bar center in each calcdata item - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - bar[pLetter] = bar.p + barCenter; - } - } -} From 97da55b526261c13d240f981994a6d3800be36d7 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 12 Oct 2016 13:49:07 +0100 Subject: [PATCH 06/13] bar: replace t.dbar with t.bargroupwidth * Don't assume the position axis is `xa` in the calculation of `barDelta` in `bar/hover.js`. * Updated `tests/bar_test.js` to account for the replacement of `t.bar` with `t.bargroupwidth`. * Updated the group case in `tests/bar_test.js` to test the use of `layout.bargroupgap`. --- src/traces/bar/hover.js | 3 ++- src/traces/bar/set_positions.js | 22 +++++++++++----------- test/jasmine/tests/bar_test.js | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index f93aedff94a..2573452209f 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -21,7 +21,8 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { xa = pointData.xa, ya = pointData.ya, barDelta = (hovermode === 'closest') ? - t.barwidth / 2 : t.dbar * (1 - xa._gd._fullLayout.bargap) / 2, + t.barwidth / 2 : + t.bargroupwidth, barPos; if(hovermode !== 'closest') barPos = function(di) { return di.p; }; diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index cd8aa905c5b..9e2b91e9022 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -105,26 +105,26 @@ function setOffsetAndWidth(gd, pa, pLetter, traces) { Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); // computer bar widths and position offsets - var barWidth = minDiff * (1 - fullLayout.bargap); - if(overlap) barWidth /= traces.length; - - var barWidthMinusGroupGap = barWidth * (1 - fullLayout.bargroupgap); + var barGroupWidth = minDiff * (1 - fullLayout.bargap), + barWidthPlusGap = (overlap) ? + barGroupWidth / traces.length : + barGroupWidth, + barWidth = barWidthPlusGap * (1 - fullLayout.bargroupgap); for(i = 0; i < traces.length; i++) { trace = traces[i]; // computer bar group center and bar offset - var offsetFromCenter = ( - (overlap ? (2 * i + 1 - traces.length) * barWidth : 0) - - barWidthMinusGroupGap - ) / 2, - barCenter = offsetFromCenter + barWidthMinusGroupGap / 2; + var offsetFromCenter = (overlap) ? + ((2 * i + 1 - traces.length) * barWidthPlusGap - barWidth) / 2 : + -barWidth / 2, + barCenter = offsetFromCenter + barWidth / 2; // store bar width and offset for this trace var t = trace[0].t; - t.barwidth = barWidthMinusGroupGap; + t.barwidth = barWidth; t.poffset = offsetFromCenter; - t.dbar = minDiff; + t.bargroupwidth = barGroupWidth; // store the bar center in each calcdata item for(j = 0; j < trace.length; j++) { diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index b1363b111ad..d4197bad8ea 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -142,7 +142,7 @@ describe('heatmap calc / setPositions', function() { assertPtField(out, 'p', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); assertTraceField(out, 't.barwidth', [0.8, 0.8, 0.8]); assertTraceField(out, 't.poffset', [-0.4, -0.4, -0.4]); - assertTraceField(out, 't.dbar', [1, 1, 1]); + assertTraceField(out, 't.bargroupwidth', [0.8, 0.8, 0.8]); }); it('should fill in calc pt fields (overlay case)', function() { @@ -161,7 +161,7 @@ describe('heatmap calc / setPositions', function() { assertPtField(out, 'p', [[0, 1, 2], [0, 1, 2]]); assertTraceField(out, 't.barwidth', [0.8, 0.8]); assertTraceField(out, 't.poffset', [-0.4, -0.4]); - assertTraceField(out, 't.dbar', [1, 1]); + assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); }); it('should fill in calc pt fields (group case)', function() { @@ -170,7 +170,9 @@ describe('heatmap calc / setPositions', function() { }, { y: [3, 1, 2] }], { - barmode: 'group' + barmode: 'group', + // asumming default bargap is 0.2 + bargroupgap: 0.1 }); assertPtField(out, 'x', [[-0.2, 0.8, 1.8], [0.2, 1.2, 2.2]]); @@ -178,9 +180,9 @@ describe('heatmap calc / setPositions', function() { assertPtField(out, 'b', [[0, 0, 0], [0, 0, 0]]); assertPtField(out, 's', [[2, 1, 2], [3, 1, 2]]); assertPtField(out, 'p', [[0, 1, 2], [0, 1, 2]]); - assertTraceField(out, 't.barwidth', [0.4, 0.4]); - assertTraceField(out, 't.poffset', [-0.4, 0]); - assertTraceField(out, 't.dbar', [1, 1]); + assertTraceField(out, 't.barwidth', [0.36, 0.36]); + assertTraceField(out, 't.poffset', [-0.38, 0.02]); + assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); }); it('should fill in calc pt fields (relative case)', function() { @@ -199,7 +201,7 @@ describe('heatmap calc / setPositions', function() { assertPtField(out, 'p', [[0, 1, 2], [0, 1, 2]]); assertTraceField(out, 't.barwidth', [0.8, 0.8]); assertTraceField(out, 't.poffset', [-0.4, -0.4]); - assertTraceField(out, 't.dbar', [1, 1]); + assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); }); it('should fill in calc pt fields (relative / percent case)', function() { @@ -221,6 +223,6 @@ describe('heatmap calc / setPositions', function() { assertPtField(out, 'p', [[0, 1, 2, 3], [0, 1, 2, 3]]); assertTraceField(out, 't.barwidth', [0.8, 0.8]); assertTraceField(out, 't.poffset', [-0.4, -0.4]); - assertTraceField(out, 't.dbar', [1, 1]); + assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); }); }); From 93ca66ed498437980d401aa11bc3f81c44576744 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 12 Oct 2016 18:28:45 +0100 Subject: [PATCH 07/13] bar: Move code for `overlay` mode into a function * Refactored the code to set bar positions in overlay mode into function `setGroupPositionsInOverlayMode`. --- src/traces/bar/set_positions.js | 45 ++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 9e2b91e9022..3f1b063eb62 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -52,18 +52,32 @@ module.exports = function setPositions(gd, plotinfo) { function setGroupPositions(gd, pa, sa, traces) { if(!traces.length) return; - var fullLayout = gd._fullLayout, - pLetter = pa._id.charAt(0), - sLetter = sa._id.charAt(0); - - if(fullLayout.barmode === 'overlay') { - traces.forEach(function(trace) { - setOffsetAndWidth(gd, pa, pLetter, [trace]); - }); + if(gd._fullLayout.barmode === 'overlay') { + setGroupPositionsInOverlayMode(gd, pa, sa, traces); + } + else { + setOffsetAndWidth(gd, pa, traces); + setBaseAndSize(gd, sa, traces); } - else setOffsetAndWidth(gd, pa, pLetter, traces); +} + - setBaseAndSize(gd, sa, sLetter, traces); +function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { + // set bar offsets and widths + traces.forEach(function(trace) { + setOffsetAndWidth(gd, pa, [trace]); + }); + + // for overlaid bars, + // just make sure the size axis includes zero, + // along with the tops of each bar, + // and store these bar tops in calcdata + var sLetter = getAxisLetter(sa), + fs = function(v) { v[sLetter] = v.s; return v.s; }; + + for(var i = 0; i < traces.length; i++) { + Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); + } } @@ -72,8 +86,9 @@ function setGroupPositions(gd, pa, sa, traces) { // to find the maximum width bars that won't overlap // for stacked or grouped bars, this is all vertical or horizontal // bars for overlaid bars, call this individually on each trace. -function setOffsetAndWidth(gd, pa, pLetter, traces) { +function setOffsetAndWidth(gd, pa, traces) { var fullLayout = gd._fullLayout, + pLetter = getAxisLetter(pa), i, trace, j, bar; @@ -135,8 +150,9 @@ function setOffsetAndWidth(gd, pa, pLetter, traces) { } -function setBaseAndSize(gd, sa, sLetter, traces) { +function setBaseAndSize(gd, sa, traces) { var fullLayout = gd._fullLayout, + sLetter = getAxisLetter(sa), i, trace, j, bar; @@ -253,3 +269,8 @@ function setBaseAndSize(gd, sa, sLetter, traces) { } } } + + +function getAxisLetter(ax) { + return ax._id.charAt(0); +} From 80fdfbdb12ced51aa1dafc45f34233d52264b6ef Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 13 Oct 2016 16:54:46 +0100 Subject: [PATCH 08/13] bar: refactor code into functions for each barmode * Refactored the code for setting bar positions into setGroupPositionsInOverlayMode, setGroupPositionsInGroupMode and setGroupPositionsInStackOrRelativeMode. * Refactored code for stacking bars and computing minDiff into helper class Sieve. --- src/traces/bar/set_positions.js | 172 +++++++++++++++++++++++--------- src/traces/bar/sieve.js | 99 ++++++++++++++++++ 2 files changed, 225 insertions(+), 46 deletions(-) create mode 100644 src/traces/bar/sieve.js diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 3f1b063eb62..89799deae5d 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -13,7 +13,7 @@ var isNumeric = require('fast-isnumeric'); var Registry = require('../../registry'); var Axes = require('../../plots/cartesian/axes'); -var Lib = require('../../lib'); +var Sieve = require('./sieve.js'); /* * Bar chart stacking/grouping positioning and autoscaling calculations @@ -52,24 +52,44 @@ module.exports = function setPositions(gd, plotinfo) { function setGroupPositions(gd, pa, sa, traces) { if(!traces.length) return; - if(gd._fullLayout.barmode === 'overlay') { + var barmode = gd._fullLayout.barmode, + overlay = (barmode === 'overlay'), + group = (barmode === 'group'); + + if(overlay) { setGroupPositionsInOverlayMode(gd, pa, sa, traces); } + else if(group) { + setGroupPositionsInGroupMode(gd, pa, sa, traces); + } else { - setOffsetAndWidth(gd, pa, traces); - setBaseAndSize(gd, sa, traces); + setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces); } } function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { - // set bar offsets and widths + var barnorm = gd._fullLayout.barnorm, + separateNegativeValues = false, + dontMergeOverlappingData = !barnorm; + + // update position axis and set bar offsets and widths traces.forEach(function(trace) { - setOffsetAndWidth(gd, pa, [trace]); + var sieve = new Sieve( + [trace], separateNegativeValues, dontMergeOverlappingData + ), + minDiff = sieve.minDiff, + distinctPositions = sieve.distinctPositions; + + setOffsetAndWidth(gd, pa, sieve); + + Axes.minDtick(pa, minDiff, distinctPositions[0]); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); }); - // for overlaid bars, - // just make sure the size axis includes zero, + // update size axis and set bar bases and sizes + // + // make sure the size axis includes zero, // along with the tops of each bar, // and store these bar tops in calcdata var sLetter = getAxisLetter(sa), @@ -81,53 +101,112 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { } -// bar position offset and width calculation -// traces is a list of traces (in calcdata) to look at together -// to find the maximum width bars that won't overlap -// for stacked or grouped bars, this is all vertical or horizontal -// bars for overlaid bars, call this individually on each trace. -function setOffsetAndWidth(gd, pa, traces) { +function setGroupPositionsInGroupMode(gd, pa, sa, traces) { var fullLayout = gd._fullLayout, - pLetter = getAxisLetter(pa), - i, trace, - j, bar; + barnorm = fullLayout.barnorm, + separateNegativeValues = false, + dontMergeOverlappingData = !barnorm, + sieve = new Sieve( + traces, separateNegativeValues, dontMergeOverlappingData + ), + minDiff = sieve.minDiff, + distinctPositions = sieve.distinctPositions; - // make list of bar positions - var positions = []; - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - positions.push(bar.p); - } - } + // set bar offsets and widths + setOffsetAndWidthInGroupMode(gd, pa, sieve); - var dv = Lib.distinctVals(positions), - distinctPositions = dv.vals, - minDiff = dv.minDiff; + // update position axis + Axes.minDtick(pa, minDiff, distinctPositions[0]); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // check if there are any overlapping positions; - // if there aren't, let them have full width even if mode is group - var overlap = false; - if(fullLayout.barmode === 'group') { - overlap = (positions.length !== distinctPositions.length); - } + // set bar bases and sizes + setBaseAndSize(gd, sa, sieve); +} + + +function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) { + var fullLayout = gd._fullLayout, + barmode = fullLayout.barmode, + stack = (barmode === 'stack'), + relative = (barmode === 'relative'), + barnorm = gd._fullLayout.barnorm, + separateNegativeValues = relative, + dontMergeOverlappingData = !(barnorm || stack || relative), + sieve = new Sieve( + traces, separateNegativeValues, dontMergeOverlappingData + ), + minDiff = sieve.minDiff, + distinctPositions = sieve.distinctPositions; - // check forced minimum dtick - Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); + // set bar offsets and widths + setOffsetAndWidth(gd, pa, sieve); - // position axis autorange - always tight fitting + // update position axis + Axes.minDtick(pa, minDiff, distinctPositions[0]); Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // computer bar widths and position offsets - var barGroupWidth = minDiff * (1 - fullLayout.bargap), + // set bar bases and sizes + setBaseAndSize(gd, sa, sieve); +} + + +function setOffsetAndWidth(gd, pa, sieve) { + var fullLayout = gd._fullLayout, + pLetter = getAxisLetter(pa), + traces = sieve.traces, + bargap = fullLayout.bargap, + bargroupgap = fullLayout.bargroupgap, + minDiff = sieve.minDiff; + + // set bar offsets and widths + var barGroupWidth = minDiff * (1 - bargap), + barWidthPlusGap = barGroupWidth, + barWidth = barWidthPlusGap * (1 - bargroupgap); + + // computer bar group center and bar offset + var offsetFromCenter = -barWidth / 2, + barCenter = 0; + + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + + // store bar width and offset for this trace + var t = trace[0].t; + t.barwidth = barWidth; + t.poffset = offsetFromCenter; + t.bargroupwidth = barGroupWidth; + + // store the bar center in each calcdata item + for(var j = 0; j < trace.length; j++) { + var bar = trace[j]; + bar[pLetter] = bar.p + barCenter; + } + } +} + + +function setOffsetAndWidthInGroupMode(gd, pa, sieve) { + var fullLayout = gd._fullLayout, + pLetter = getAxisLetter(pa), + traces = sieve.traces, + bargap = fullLayout.bargap, + bargroupgap = fullLayout.bargroupgap, + positions = sieve.positions, + distinctPositions = sieve.distinctPositions, + minDiff = sieve.minDiff; + + // if there aren't any overlapping positions, + // let them have full width even if mode is group + var overlap = (positions.length !== distinctPositions.length); + + var barGroupWidth = minDiff * (1 - bargap), barWidthPlusGap = (overlap) ? barGroupWidth / traces.length : barGroupWidth, - barWidth = barWidthPlusGap * (1 - fullLayout.bargroupgap); + barWidth = barWidthPlusGap * (1 - bargroupgap); - for(i = 0; i < traces.length; i++) { - trace = traces[i]; + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; // computer bar group center and bar offset var offsetFromCenter = (overlap) ? @@ -142,17 +221,18 @@ function setOffsetAndWidth(gd, pa, traces) { t.bargroupwidth = barGroupWidth; // store the bar center in each calcdata item - for(j = 0; j < trace.length; j++) { - bar = trace[j]; + for(var j = 0; j < trace.length; j++) { + var bar = trace[j]; bar[pLetter] = bar.p + barCenter; } } } -function setBaseAndSize(gd, sa, traces) { +function setBaseAndSize(gd, sa, sieve) { var fullLayout = gd._fullLayout, sLetter = getAxisLetter(sa), + traces = sieve.traces, i, trace, j, bar; diff --git a/src/traces/bar/sieve.js b/src/traces/bar/sieve.js new file mode 100644 index 00000000000..481b619b521 --- /dev/null +++ b/src/traces/bar/sieve.js @@ -0,0 +1,99 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = Sieve; + +var Lib = require('../../lib'); + +/** + * Helper class to sieve data from traces into bins + * + * @class + * @param {Array} traces + * Array of calculated traces + * @param {boolean} [separateNegativeValues] + * If true, then split data at the same position into a bar + * for positive values and another for negative values + * @param {boolean} [dontMergeOverlappingData] + * If true, then don't merge overlapping bars into a single bar + */ +function Sieve(traces, separateNegativeValues, dontMergeOverlappingData) { + this.traces = traces; + this.separateNegativeValues = separateNegativeValues; + this.dontMergeOverlappingData = dontMergeOverlappingData; + + var positions = []; + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + for(var j = 0; j < trace.length; j++) { + var bar = trace[j]; + positions.push(bar.p); + } + } + this.positions = positions; + + var dv = Lib.distinctVals(this.positions); + this.distinctPositions = dv.vals; + this.minDiff = dv.minDiff; + + this.binWidth = this.minDiff; + + this.bins = {}; +} + +/** + * Sieve datum + * + * @method + * @param {number} position + * @param {number} value + * @returns {number} Previous bin value + */ +Sieve.prototype.put = function put(position, value) { + var label = this.getLabel(position, value), + oldValue = this.bins[label] || 0; + + this.bins[label] = oldValue + value; + + return oldValue; +}; + +/** + * Get current bin value for a given datum + * + * @method + * @param {number} position Position of datum + * @param {number} [value] Value of datum + * (required if this.separateNegativeValues is true) + * @returns {number} Current bin value + */ +Sieve.prototype.get = function put(position, value) { + var label = this.getLabel(position, value); + return this.bins[label] || 0; +}; + +/** + * Get bin label for a given datum + * + * @method + * @param {number} position Position of datum + * @param {number} [value] Value of datum + * (required if this.separateNegativeValues is true) + * @returns {string} Bin label + * (prefixed with a 'v' if value is negative and this.separateNegativeValues is + * true; otherwise prefixed with '^') + */ +Sieve.prototype.getLabel = function getLabel(position, value) { + var prefix = (value < 0 && this.separateNegativeValues) ? 'v' : '^', + label = (this.dontMergeOverlappingData) ? + position : + Math.round(position / this.binWidth); + return prefix + label; +}; From 3b87e85b76f6e07fa5c7fb8641cd040bb09bb6ba Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 14 Oct 2016 18:25:16 +0100 Subject: [PATCH 09/13] bar: use Sieve to stack bars in setBaseAndSize --- src/traces/bar/set_positions.js | 51 +++++++++++---------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 89799deae5d..00b6e26b208 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -249,17 +249,12 @@ function setBaseAndSize(gd, sa, sieve) { // so we don't have to redo this later var sMax = sa.l2c(sa.c2l(0)), sMin = sMax, - sums = {}, + barEnd; - // make sure if p is different only by rounding, - // we still stack - sumround = traces[0][0].t.barwidth / 100, - sv = 0, - padded = true, - barEnd, - scale; + // stack bars that only differ by rounding + sieve.binWidth = traces[0][0].t.barwidth / 100; - for(i = 0; i < traces.length; i++) { // trace index + for(i = 0; i < traces.length; i++) { trace = traces[i]; for(j = 0; j < trace.length; j++) { @@ -269,16 +264,11 @@ function setBaseAndSize(gd, sa, sieve) { // so that we don't try to stack them if(!isNumeric(bar.s)) continue; - sv = Math.round(bar.p / sumround); - - // store the negative sum value for p at the same key, - // with sign flipped using string to ensure -0 !== 0. - if(relative && bar.s < 0) sv = '-' + sv; + // stack current bar and get previous sum + var previousSum = sieve.put(bar.p, bar.s); - var previousSum = sums[sv] || 0; if(stack || relative) bar.b = previousSum; barEnd = bar.b + bar.s; - sums[sv] = previousSum + bar.s; // store the bar top in each calcdata item if(stack || relative) { @@ -291,43 +281,36 @@ function setBaseAndSize(gd, sa, sieve) { } } - if(norm) { - var top = norm === 'fraction' ? 1 : 100, - relAndNegative = false, - tiny = top / 1e9; // in case of rounding error in sum + var padded = true; + if(norm) { padded = false; + + var sTop = (norm === 'fraction') ? 1 : 100, + sTiny = sTop / 1e9; // in case of rounding error in sum + sMin = 0; - sMax = stack ? top : 0; + sMax = (stack) ? sTop : 0; - for(i = 0; i < traces.length; i++) { // trace index + for(i = 0; i < traces.length; i++) { trace = traces[i]; for(j = 0; j < trace.length; j++) { bar = trace[j]; - relAndNegative = (relative && bar.s < 0); - - sv = Math.round(bar.p / sumround); - - // locate negative sum amount for this p val - if(relAndNegative) sv = '-' + sv; - - scale = top / sums[sv]; + var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); - // preserve sign if negative - if(relAndNegative) scale *= -1; bar.b *= scale; bar.s *= scale; barEnd = bar.b + bar.s; bar[sLetter] = barEnd; if(isNumeric(sa.c2l(barEnd))) { - if(barEnd < sMin - tiny) { + if(barEnd < sMin - sTiny) { padded = true; sMin = barEnd; } - if(barEnd > sMax + tiny) { + if(barEnd > sMax + sTiny) { padded = true; sMax = barEnd; } From a7ca755c56700d52e0c2f6fa2f15b7a6505b95d8 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 14 Oct 2016 19:18:08 +0100 Subject: [PATCH 10/13] bar: add functions stackBars and normalizeBars * Renamed setBaseAndSize to stackBars. * Refactor code to normalize bars into function normalizeBars. --- src/traces/bar/set_positions.js | 189 +++++++++++++++++--------------- 1 file changed, 99 insertions(+), 90 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 00b6e26b208..14f1d901956 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -81,23 +81,27 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { minDiff = sieve.minDiff, distinctPositions = sieve.distinctPositions; + // set bar offsets and widths setOffsetAndWidth(gd, pa, sieve); + // update position axis Axes.minDtick(pa, minDiff, distinctPositions[0]); Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - }); - - // update size axis and set bar bases and sizes - // - // make sure the size axis includes zero, - // along with the tops of each bar, - // and store these bar tops in calcdata - var sLetter = getAxisLetter(sa), - fs = function(v) { v[sLetter] = v.s; return v.s; }; - for(var i = 0; i < traces.length; i++) { - Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); - } + // update size axis and set bar bases and sizes + if(barnorm) { + stackBars(gd, sa, sieve); + } + else { + // make sure the size axis includes zero, + // along with the tops of each bar, + // and store these bar tops in calcdata + var sLetter = getAxisLetter(sa), + fs = function(v) { v[sLetter] = v.s; return v.s; }; + + Axes.expand(sa, trace.map(fs), {tozero: true, padded: true}); + } + }); } @@ -119,8 +123,21 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { Axes.minDtick(pa, minDiff, distinctPositions[0]); Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // set bar bases and sizes - setBaseAndSize(gd, sa, sieve); + // update size axis and set bar bases and sizes + if(barnorm) { + stackBars(gd, sa, sieve); + } + else { + // make sure the size axis includes zero, + // along with the tops of each bar, + // and store these bar tops in calcdata + var sLetter = getAxisLetter(sa), + fs = function(v) { v[sLetter] = v.s; return v.s; }; + + for(var i = 0; i < traces.length; i++) { + Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); + } + } } @@ -146,7 +163,7 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) { Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); // set bar bases and sizes - setBaseAndSize(gd, sa, sieve); + stackBars(gd, sa, sieve); } @@ -229,7 +246,7 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { } -function setBaseAndSize(gd, sa, sieve) { +function stackBars(gd, sa, sieve) { var fullLayout = gd._fullLayout, sLetter = getAxisLetter(sa), traces = sieve.traces, @@ -241,96 +258,88 @@ function setBaseAndSize(gd, sa, sieve) { norm = fullLayout.barnorm; // bar size range and stacking calculation - if(stack || relative || norm) { - // for stacked bars, we need to evaluate every step in every - // stack, because negative bars mean the extremes could be - // anywhere - // also stores the base (b) of each bar in calcdata - // so we don't have to redo this later - var sMax = sa.l2c(sa.c2l(0)), - sMin = sMax, - barEnd; - - // stack bars that only differ by rounding - sieve.binWidth = traces[0][0].t.barwidth / 100; - - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - - for(j = 0; j < trace.length; j++) { - bar = trace[j]; - - // skip over bars with no size, - // so that we don't try to stack them - if(!isNumeric(bar.s)) continue; - - // stack current bar and get previous sum - var previousSum = sieve.put(bar.p, bar.s); - - if(stack || relative) bar.b = previousSum; - barEnd = bar.b + bar.s; - - // store the bar top in each calcdata item - if(stack || relative) { - bar[sLetter] = barEnd; - if(!norm && isNumeric(sa.c2l(barEnd))) { - sMax = Math.max(sMax, barEnd); - sMin = Math.min(sMin, barEnd); - } - } - } - } + // for stacked bars, we need to evaluate every step in every + // stack, because negative bars mean the extremes could be + // anywhere + // also stores the base (b) of each bar in calcdata + // so we don't have to redo this later + var sMax = sa.l2c(sa.c2l(0)), + sMin = sMax; - var padded = true; + // stack bars that only differ by rounding + sieve.binWidth = traces[0][0].t.barwidth / 100; - if(norm) { - padded = false; - - var sTop = (norm === 'fraction') ? 1 : 100, - sTiny = sTop / 1e9; // in case of rounding error in sum - - sMin = 0; - sMax = (stack) ? sTop : 0; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; - for(i = 0; i < traces.length; i++) { - trace = traces[i]; + for(j = 0; j < trace.length; j++) { + bar = trace[j]; - for(j = 0; j < trace.length; j++) { - bar = trace[j]; + // skip over bars with no size, + // so that we don't try to stack them + if(!isNumeric(bar.s)) continue; - var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); + // stack current bar and get previous sum + var previousSum = sieve.put(bar.p, bar.s); - bar.b *= scale; - bar.s *= scale; - barEnd = bar.b + bar.s; - bar[sLetter] = barEnd; + if(stack || relative) bar.b = previousSum; - if(isNumeric(sa.c2l(barEnd))) { - if(barEnd < sMin - sTiny) { - padded = true; - sMin = barEnd; - } - if(barEnd > sMax + sTiny) { - padded = true; - sMax = barEnd; - } - } + // store the bar top in each calcdata item + if(stack || relative) { + var barEnd = bar.b + bar.s; + bar[sLetter] = barEnd; + if(!norm && isNumeric(sa.c2l(barEnd))) { + sMax = Math.max(sMax, barEnd); + sMin = Math.min(sMin, barEnd); } } } + } - Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}); + if(norm) { + normalizeBars(gd, sa, sieve); } else { - // for grouped or overlaid bars, just make sure zero is - // included, along with the tops of each bar, and store - // these bar tops in calcdata - var fs = function(v) { v[sLetter] = v.s; return v.s; }; + Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}); + } +} - for(i = 0; i < traces.length; i++) { - Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); + +function normalizeBars(gd, sa, sieve) { + var traces = sieve.traces, + sLetter = getAxisLetter(sa), + sTop = (gd._fullLayout.barnorm === 'fraction') ? 1 : 100, + sTiny = sTop / 1e9, // in case of rounding error in sum + sMin = 0, + sMax = (gd._fullLayout.barmode === 'stack') ? sTop : 0, + padded = false; + + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + + for(var j = 0; j < trace.length; j++) { + var bar = trace[j], + scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); + + bar.b *= scale; + bar.s *= scale; + var barEnd = bar.b + bar.s; + bar[sLetter] = barEnd; + + if(isNumeric(sa.c2l(barEnd))) { + if(barEnd < sMin - sTiny) { + padded = true; + sMin = barEnd; + } + if(barEnd > sMax + sTiny) { + padded = true; + sMax = barEnd; + } + } } } + + Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}); } From b5f5befce017d014ad3b00db00f10911dee77ec5 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 17 Oct 2016 14:12:54 +0100 Subject: [PATCH 11/13] bar: Implement layout.barbase * Fix: do not normalise bars with no size * Fix: update minDtick if barmode is group and bars overlap. Closes #475 --- src/traces/bar/layout_attributes.js | 6 +++ src/traces/bar/layout_defaults.js | 2 + src/traces/bar/set_positions.js | 74 ++++++++++++++++++----------- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/traces/bar/layout_attributes.js b/src/traces/bar/layout_attributes.js index e2cca32a822..827f0ebccf8 100644 --- a/src/traces/bar/layout_attributes.js +++ b/src/traces/bar/layout_attributes.js @@ -10,6 +10,12 @@ module.exports = { + barbase: { + valType: 'number', + dflt: 0, + role: 'style', + description: 'Sets where the bar base is drawn (in axis units).' + }, barmode: { valType: 'enumerated', values: ['stack', 'group', 'overlay', 'relative'], diff --git a/src/traces/bar/layout_defaults.js b/src/traces/bar/layout_defaults.js index 8c6d6c3ff23..dc8f4f05619 100644 --- a/src/traces/bar/layout_defaults.js +++ b/src/traces/bar/layout_defaults.js @@ -48,6 +48,8 @@ module.exports = function(layoutIn, layoutOut, fullData) { if(!hasBars) return; + coerce('barbase'); + var mode = coerce('barmode'); if(mode !== 'overlay') coerce('barnorm'); diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 14f1d901956..4c40e7daebe 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -76,18 +76,12 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { // update position axis and set bar offsets and widths traces.forEach(function(trace) { var sieve = new Sieve( - [trace], separateNegativeValues, dontMergeOverlappingData - ), - minDiff = sieve.minDiff, - distinctPositions = sieve.distinctPositions; + [trace], separateNegativeValues, dontMergeOverlappingData + ); - // set bar offsets and widths + // set bar offsets and widths and update position axis setOffsetAndWidth(gd, pa, sieve); - // update position axis - Axes.minDtick(pa, minDiff, distinctPositions[0]); - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // update size axis and set bar bases and sizes if(barnorm) { stackBars(gd, sa, sieve); @@ -102,6 +96,8 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { Axes.expand(sa, trace.map(fs), {tozero: true, padded: true}); } }); + + applyBarbase(gd, sa, traces); } @@ -112,17 +108,11 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { dontMergeOverlappingData = !barnorm, sieve = new Sieve( traces, separateNegativeValues, dontMergeOverlappingData - ), - minDiff = sieve.minDiff, - distinctPositions = sieve.distinctPositions; + ); - // set bar offsets and widths + // set bar offsets and widths and update position axis setOffsetAndWidthInGroupMode(gd, pa, sieve); - // update position axis - Axes.minDtick(pa, minDiff, distinctPositions[0]); - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - // update size axis and set bar bases and sizes if(barnorm) { stackBars(gd, sa, sieve); @@ -138,6 +128,8 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); } } + + applyBarbase(gd, sa, traces); } @@ -151,18 +143,12 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) { dontMergeOverlappingData = !(barnorm || stack || relative), sieve = new Sieve( traces, separateNegativeValues, dontMergeOverlappingData - ), - minDiff = sieve.minDiff, - distinctPositions = sieve.distinctPositions; + ); - // set bar offsets and widths + // set bar offsets and widths and update position axis setOffsetAndWidth(gd, pa, sieve); - // update position axis - Axes.minDtick(pa, minDiff, distinctPositions[0]); - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); - - // set bar bases and sizes + // set bar bases and sizes and update size axis stackBars(gd, sa, sieve); } @@ -199,6 +185,11 @@ function setOffsetAndWidth(gd, pa, sieve) { bar[pLetter] = bar.p + barCenter; } } + + // update position axis + var distinctPositions = sieve.distinctPositions; + Axes.minDtick(pa, minDiff, distinctPositions[0]); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); } @@ -243,6 +234,10 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { bar[pLetter] = bar.p + barCenter; } } + + // update position axis + Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); } @@ -318,9 +313,11 @@ function normalizeBars(gd, sa, sieve) { var trace = traces[i]; for(var j = 0; j < trace.length; j++) { - var bar = trace[j], - scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); + var bar = trace[j]; + + if(!isNumeric(bar.s)) continue; + var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); bar.b *= scale; bar.s *= scale; var barEnd = bar.b + bar.s; @@ -343,6 +340,27 @@ function normalizeBars(gd, sa, sieve) { } +function applyBarbase(gd, sa, traces) { + var barbase = gd._fullLayout.barbase; + if(!barbase || !isNumeric(sa.c2l(barbase))) return; + + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + + for(var j = 0; j < trace.length; j++) { + var bar = trace[j], + bartop = bar.b + bar.s; + if(isNumeric(sa.c2l(bartop))) { + bar.b = barbase; + bar.s = bartop - barbase; + } + } + } + + Axes.expand(sa, [barbase], {tozero: true, padded: true}); +} + + function getAxisLetter(ax) { return ax._id.charAt(0); } From c667e78affb911850889088dd8a47b678ed24c48 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 17 Oct 2016 14:21:45 +0100 Subject: [PATCH 12/13] test: layout.barbase --- test/jasmine/tests/bar_test.js | 184 +++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 31 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index d4197bad8ea..fcee4a2de83 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -61,46 +61,134 @@ describe('bar supplyDefaults', function() { }); }); -describe('heatmap calc / setPositions', function() { +describe('bar supplyLayoutDefaults', function() { + 'use strict'; + + var gd; + + it('should set barbase to 0', function() { + gd = { + data: [{ + type: 'bar', + x: [], + y: [], + }], + layout: {} + }; + + Plots.supplyDefaults(gd); + + expect(gd._fullLayout.barbase).toBe(0); + }); +}); + +describe('barbase', function() { 'use strict'; beforeAll(function() { jasmine.addMatchers(customMatchers); }); - function _calc(dataOpts, layout) { - var baseData = { type: 'bar' }; - - var data = dataOpts.map(function(traceOpts) { - return Lib.extendFlat({}, baseData, traceOpts); - }); + function assertPointField(calcData, prop, expectation) { + var values = []; - var gd = { - data: data, - layout: layout, - calcdata: [] - }; + calcData.forEach(function(calcTrace) { + var vals = calcTrace.map(function(pt) { + return Lib.nestedProperty(pt, prop).get(); + }); - Plots.supplyDefaults(gd); + values.push(vals); + }); - gd._fullData.forEach(function(fullTrace) { - var cd = Bar.calc(gd, fullTrace); + expect(values).toBeCloseTo2DArray( + expectation, undefined, '- field ' + prop); + } - cd[0].t = {}; - cd[0].trace = fullTrace; + it('should be honored in overlay mode', function() { + var data = [{ + y: [10, 20, 30] + }, { + y: [-10, 20, -30] + }, { + y: [null, null, -30] + }], + layout = { + barbase: 10, + barmode: 'overlay' + }, + cd = getCalcdata(data, layout); + + // Note: + // The base of null bars is set to zero and the size is left undefined + assertPointField(cd, 'b', [[10, 10, 10], [10, 10, 10], [0, 0, 10]]); + assertPointField(cd, 's', + [[0, 10, 20], [-20, 10, -40], [undefined, undefined, -40]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', + [[10, 20, 30], [-10, 20, -30], [undefined, undefined, -30]]); + }); - gd.calcdata.push(cd); - }); + it('should be honored in group mode', function() { + var data = [{ + y: [10, 20, 30] + }, { + y: [-10, 20, -30] + }, { + y: [null, null, -30] + }], + layout = { + barbase: 10, + barmode: 'group' + }, + cd = getCalcdata(data, layout); + + // Note: + // The base of null bars is set to zero and the size is left undefined + assertPointField(cd, 'b', [[10, 10, 10], [10, 10, 10], [0, 0, 10]]); + assertPointField(cd, 's', + [[0, 10, 20], [-20, 10, -40], [undefined, undefined, -40]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'x', + [[-0.27, 0.73, 1.73], [0, 1, 2], [0.27, 1.27, 2.27]]); + assertPointField(cd, 'y', + [[10, 20, 30], [-10, 20, -30], [undefined, undefined, -30]]); + }); - var plotinfo = { - xaxis: gd._fullLayout.xaxis, - yaxis: gd._fullLayout.yaxis - }; + it('should be honored in group mode when barnorm is set', function() { + var data = [{ + y: [30, 70, 20] + }, { + y: [70, 30, 30] + }, { + y: [null, null, 50] + }], + layout = { + barbase: 1, + barmode: 'group', + barnorm: 'fraction' + }, + cd = getCalcdata(data, layout); + + // Note: + // The base of null bars is set to zero and the size is left undefined + assertPointField(cd, 'b', [[1, 1, 1], [1, 1, 1], [0, 0, 1]]); + assertPointField(cd, 's', + [[-0.7, -0.3, -0.8], [-0.3, -0.7, -0.7], [undefined, undefined, -0.5]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'x', + [[-0.27, 0.73, 1.73], [0, 1, 2], [0.27, 1.27, 2.27]]); + assertPointField(cd, 'y', + [[0.3, 0.7, 0.2], [0.7, 0.3, 0.3], [undefined, undefined, 0.5]]); + }); +}); - Bar.setPositions(gd, plotinfo); +describe('heatmap calc / setPositions', function() { + 'use strict'; - return gd.calcdata; - } + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); function assertPtField(calcData, prop, expectation) { var values = []; @@ -125,7 +213,7 @@ describe('heatmap calc / setPositions', function() { } it('should fill in calc pt fields (stack case)', function() { - var out = _calc([{ + var out = getCalcdata([{ y: [2, 1, 2] }, { y: [3, 1, 2] @@ -146,7 +234,7 @@ describe('heatmap calc / setPositions', function() { }); it('should fill in calc pt fields (overlay case)', function() { - var out = _calc([{ + var out = getCalcdata([{ y: [2, 1, 2] }, { y: [3, 1, 2] @@ -165,7 +253,7 @@ describe('heatmap calc / setPositions', function() { }); it('should fill in calc pt fields (group case)', function() { - var out = _calc([{ + var out = getCalcdata([{ y: [2, 1, 2] }, { y: [3, 1, 2] @@ -186,7 +274,7 @@ describe('heatmap calc / setPositions', function() { }); it('should fill in calc pt fields (relative case)', function() { - var out = _calc([{ + var out = getCalcdata([{ y: [20, 14, -23] }, { y: [-12, -18, -29] @@ -205,7 +293,7 @@ describe('heatmap calc / setPositions', function() { }); it('should fill in calc pt fields (relative / percent case)', function() { - var out = _calc([{ + var out = getCalcdata([{ x: ['A', 'B', 'C', 'D'], y: [20, 14, 40, -60] }, { @@ -226,3 +314,37 @@ describe('heatmap calc / setPositions', function() { assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); }); }); + +function getCalcdata(dataOpts, layout) { + var baseData = { type: 'bar' }; + + var data = dataOpts.map(function(traceOpts) { + return Lib.extendFlat({}, baseData, traceOpts); + }); + + var gd = { + data: data, + layout: layout, + calcdata: [] + }; + + Plots.supplyDefaults(gd); + + gd._fullData.forEach(function(fullTrace) { + var cd = Bar.calc(gd, fullTrace); + + cd[0].t = {}; + cd[0].trace = fullTrace; + + gd.calcdata.push(cd); + }); + + var plotinfo = { + xaxis: gd._fullLayout.xaxis, + yaxis: gd._fullLayout.yaxis + }; + + Bar.setPositions(gd, plotinfo); + + return gd.calcdata; +} From 4bd88a7112a6217383f2f07cec4aea6958b5111b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 17 Oct 2016 14:23:07 +0100 Subject: [PATCH 13/13] test: Add mock with layout.barbase --- test/image/baselines/bar_group_barbase.png | Bin 0 -> 17309 bytes test/image/mocks/bar_group_barbase.json | 44 +++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 test/image/baselines/bar_group_barbase.png create mode 100644 test/image/mocks/bar_group_barbase.json diff --git a/test/image/baselines/bar_group_barbase.png b/test/image/baselines/bar_group_barbase.png new file mode 100644 index 0000000000000000000000000000000000000000..5e407d216a111a921b4fa1d8acd4d3c2b73ef70d GIT binary patch literal 17309 zcmeHvc|4Tu|F0$@WhtWU@kFwv#bC0Oo+P2D>{KdAO!hGhO+7+FS}1FaCE1dlv9t`8 zkeMuD##R{H%w#Ohm~pOq=sDl#`F_9W_d2iNd7b5V`mgS}@9Vy<&+`6k?_2aSOH(01 zDM20{9wD>C2d#N{U^pJ0RSbbO;7Y$er=N#MfyeCN{u3ydDbl)o&gX;SG!=otJAXJS zh-MzFySj>a?IPCtMB*t24ccDpn%m~8Nx%KpcYZ@5;Xsw0*;k1J$<|ws!K`~Wt|MGp zZ@2F7Rzs(c`g|~*j-+ZAhRnM>0koqI7%udstTszO=q`O;k zO4>qN*N=~ljR}arcm+fijIZ*nf;|*mzY(>&ApiV^<^S^vh{Iq-hkpJ?^qqt8Kua}F z!W6n0dP;#dzWfjHI5wY;mz>}1>GS7uup+r%kNy(VFOv}X||EpUO@$0E`pPll`X#5{$$~!yP=rUp<7Wo>F6D;hOEg-@PymbSe=`0 z@di4Uh^f+Uzcz!IV~gEx@%U{?PIqiX?AHBU$P-#f`uX(N_jiYHNP9;mxrXVDw4_LO zIr&*=t49pK5k{y>sN0E}+*q$#H^-bPawjSL3_!U0hIa`{^wO(W8)_^Q$e4N3kmj^V z814GP2iv-h=85X6(_^ivdL^Epb92(Aa#DSdOulT^)Hvp1^V?4?h@!$tt9M`c80DUU z(JAlsuF+juoEd&2&$-eULan7Uh*px0Qu^}lX#uBXFpT!%Cb^I?xj&6rLR1vhrD}~{ z-D?&0{ca^#edwlIVN?c(kwbr{6#uqrG`)B44!&Wcm61UpDWFSYdy(qK<(OEYeq}Z& ztS5&;EoajfCQZwyh}u&-?FLB3_v;tGX-Q@fb_Py%SB^i=8v=*cI#gg^qDBbbKHo)hasNVltz5t6>Rv-y9lcSSKAu-3A+MJx zXSDRAEe28TAwm^KOp2OM0@5^6xIk_KQ!9l~z;R!sbdI z>~^;-#{1p1(20)6OuG5J;*&i1;Yg$$`r_qcyoB?{kf}0TdTpyIx}Vr#>sp)sxffaQ zjH8FY{$s6$_+ogHH>-iw?8@0ikR^>~gd6IRQNx=JLs2pTpO4d{twNvhphq6M-<%RE z!hJvD(*%zg)OKs!Q+q{uMMYn=KD}!QS)?~fQf-dNcT}Ti#}BVURgePJU`Qs}h)pz_ zm0EnoFMkUtuR!1v#myy!eUa7BI|-{aTrsi%Hm}qcl!*S=qamv3<+>ju6Bao|SU7uY zkq0pw|GT8KID(4v^C=un578{Fo$EnSvXX8h@DvsWDU)d`WfU~sXIJg-u^~=mAFW}? z?exSCQPe~(#c3te!Ria{*+t8)S7)^%sBak*qFY`;`MAUHuB(yNc?cA3K$rejEXFTW zgzv$w^G7!L&VO;w7&CC6 z@saXjy3=*6=^-~)gnUq+kLiX?V)|eyW)Q#(h3O*LLecaQ#LuDyaI<&(mmTQXd2G0@ z@=#O%v+wJrjVfZu7Z>-kQ1}bHbW z^SjGmJVxO7+yTA{8-%&NLH?$$o5#1_%;Q-2RGP(^fW-wm zKIfDH&;&m5S`lW-6ED~I5`ojITR58*2S7wcn)QJlD?br(MdzJ^e+4l~01)3mklXn|7J-Gq# z>2<3W_wsX)lb4?xsAVFIrflXzLnjZ1JD^sdg&{L~-ks*+)~6>h7}9(VSz@aEgN5Tj zfs@(MD6Y$|2QaEe6E=1%`=H84K9zefmf0cVTrTMNxd@JMvgPzmd1y#cq&+OUwMAy%A{wfFui`!jV22S!0x_ zZf_kLPHF=9BnyR?qYrjI&NaFUL)!dsPUVVV&tc<%O(@nD6flb+)JzL}7%UPG5FxMB zM?RGHN>L_9q4x1rC-gOaVE_S9loMQ|d-Rff==g>j-)|puXFl#8{%m6v8!}y`qFFg` zO|Gr{QpfWLI$jP={_VdPgV-qPmH~dOoa^x%&PsB*sZrkjWPt-5#XeLkHUkmlt?pc1 zD$oQx^R!H0_n82e&qmb5AA%JE#MlB`({7eXot*AJ}?Ro>(ZvgO$YXR+4A<5B2j~{ zBb>J~vHWsT_!vpkoF;lv+*Q-L_ARm}3{I-lg+8SqXvsBZ%jM?VC`YW!S8>B%QE2?zYD z%~KEf7|6kp2I6Vgxr#pF3@O@kJ#J>CCB|pC5kG1;Usbp}vp~?5Nk}>yDH~`<&9CSW z3)u&xg-`q=KprGTYULW2$AqaM);RSIS|G1X^eGPtBRevVeT-jT^f-4>0UWh<-bKdm zqc(o-4}ci{X@e84OK99|rz1YCueB*gtIiHrtiks+LCLf*sLbDXVPQBvKG!Cv5#Xoe zujXa;vjM|3-j&1?a-~=tx=B1+A`13p5BzX!9Z`!7|u!X6^mLQPFEFD?Jl${ zelj6+2_!l}+UfvxdKBqkE0U}*5#52SXH(uh+JAlg`}J+K&WLuiWq`wT0d7b`|29z! z#c$nJ1^M??8{zw#T4Q)LI7}CrI4`GAa}_QoY=kgD=hFPN`6g6v&TVkz#xMW&a9I;Fhu)1X%I%%8C-)6}`iQ{>6IJv~r(fjWoy%dh8%8=&fXv2EB%d z9QC1KTABcDy!?1g&70mve7Qe#_=|3D9!_Zw+EkU@P=h^i6ofkO+SyJhU62X>ngFE^g$NuevdE>jS}|*n5Lc5l4(z(5 zMYM=|4S#zPpZ<;b*oqt@0x}-t47mQ$E7!2Vt82Dsxz_E|1khSZ$xgXR&7>jFjIqP7 z0!Pz=;wZ$NrU%+?)p2OnR0V(Ck;3KlkE#0Ny|an{ZenwG1FQ9xDJxg1_{)WqS(c3D zP8tkG)o+BHt=N4YWU#RS;&K*RpP$TAWY3jfXi2s!y?9*R1*ba#@*moLA3p9*k6Ep9 zlBWwI1E zFB((k+R&oOR@$M1f;A=XZE?Vn6T@I{Kndga>KY%O2H>JjMwYa{N8_fOV zPcAHa$)nVqq-~VA5}g%;%$ONq)0}2D;8k_!;ZE+Up6!QT>3K25Nq7UwFE=xQ+Y-d4 zFGue11HAw293k^rz?c5+a6ur@$9OGUb0hcsjZ;+Nf6fI+kh_(yWCF_$Tu~3X+us~G z2y(aX75C-lJYIbq$ld5G?#2z-z0XLHyPaKew_>il`LMX|Ri=`yQsAKTD->RY)TgaeUR8(1j;5~!Mo0=OyqIoiZY zAi|U0G1~x$hH&#NkDPV5tb=+&qA0)T*=5{UY=sOi%SaH0R6TCFnwOmV+(QFG3k6!8 zl}Iyovr&G^OFk;-u?2ejfH2a^ zV8FtY+Q z8+s8rM-Q#VJrpPGg5pX;;u=ROCe22HHn%EoHc}8*;A)}H zLyijl;~@cj{);ni=b9|TbpL?Hwhtax8&Yic7I2Y`mpo75ZsFo+4nMXcEe|;b&hhR< zgRDys%G|M6mTdhbb&UW{?K@#KvO(7xs#hJLDze0GGN$>Aj^lhKr>x6^E{+OAVq`OtMGyV;s#XjxT*;_i?5?P z6fI1+hd+XF{`+<>d%Vbc9Av}?LsCRFVtYqYnh0_V)G-pp9}lHL%B{7?j+N&OxM2@dk5t zPQLHA>CIk+j9vi&d8>xkrImeA*F4t)IZ!3Yp@x5qX2b{2jNtcnIoaGgvTEVQ8{rtf zbrNrHZ#B7D1hq+W!d^huer_x6sibY=+bWUs#ffN*Ar?X-YRY9Hu-3ARIl6`Z*v@Z! zi88NmNW+RSR>BDqKK!jYsR=LXDg!6^+o;>W%&dG0Re*)ZL2v6)s_zHY;%3bnaaZ$R z1t#l3)!2JZx?;_+yG765n!Wa-t6;HW3-p^6L+jvkMj!WhH?9uP;t#@Um%Gx_u=U4c zoX$)oBHlXMFqh2R6CJ(3D5`l=Jp^$RpOVBWW`JwGg`RYtEQ6kEWU!d>thBFR#_0Tfd#|F z{FG=7A&bSeUW-|LWY?6ODZwP?K>Nv3qNUH<4W{C*wbVM+P=cvkG-%CLwwpy7*=iLI}0c>n1?v57$|F^wu*9h^PxDIEjHOlDbJ}kRtB4FodZrf zg|lbzU4$^w`@1Sl8WnvT-`o(&bCid2Gg9(Qgcctvpa;k0Z}^=GzBML8egPX`;7f6$toi&<%+do*36?%q6@AAI=;qQtW$1{#BnZU9*YinS;2gJ( z&JxmC!IA>wy?kJ!Ft3Y(yf3*~Ea8U43X-0BlnnV8R%Z_%%pR1_Fr~DO5E3ZeKj>t0W)!=Bc^CH19ZVtFVgAzj>n7%;HS0y)a2XE#>F`1lmpD9eJRLQ}ydoyu=J z0`!2=zCmybsr>T}C}@|>SzeD91DDoRIAv+701e2j=r6*7TXuAkJ!n4QQtv=w-sf+; zWWjqTE4Fql56WRWdk{i~mf(_fpYQ$ZRG@laNb8W{T-pS^%OND^RTFgl>yCxiNn$9q zW5O#|0L~7*%agiiK^I)|y3%}d+!s9hVe3B}6r_ApX=~(Fz$GE?sw2+e*v5L0pFrkU z2C6m~vY?hYFbrLKrD2=fycV}Rr`b1O$q#$Rm0j@Bw!7jRyN#^Ugvih@Lh=hWjL8?F)P$FM+*V&1 z=`{Fu9YNmQTeM&~q4}tFw`#*!*Q@L9#Gg^C5#XK0oxM%XXf%uPSvQKHUB2#~-gv{p{lc&S}1& zXE~m)dtIl2oT|%d>M>e~sgvEq^E8|>^x=U&X6emGo}&dGIg6Z1zG6~Lzjx1UUf^Kd zpqpDu?ov)au?ICA%fbk&Y(}lZR{TaQbST@Q(;qrxS8~BZ-Jx_lG(MOaZ7cFTf7!z> zW9BNUJZ2)EiE@rjyVY%FFqUw#F7zkgL7fN&ub$Je5^0;UG-ZshhY|{R9HX z&$!ff$Hm1#!^+`yQ+W-;vd8HnkP)~W>DgZA8Ww=OiEof$b&}r?f>;pQ!1~^-)dZTh zQfjC2qh4L-mz1~HacjJ_dGDnY&YoJ(KE1Z9c;<8WSw4KJE*aWq-@Yv}+X47T{oexq zZ(J+})t2VYEuQ`{Q7hFed|l3-A$udlQKOi7 zckr0zbzZXfeF>CWFB3Z0myyd2Z+XdU6q1||0Hu>loK1jiu)<2|`QsfTZu&Ex+XQ zpEQObwxScQK1|BSwR-zmR)2wUz>b{}l3}9SQ~gSYm8L`$2yL<~wu5whuP4`!^X8vl zaRw%PQWVt><#0OpA6{#4d}!^T@$P#|T>fNV@-A&`h3aH&)idDjbJ(Ah#+t+a;#}?y3CiJzU!_0=CabuA4h*ISg`1Zt-E>dkj=xmpXZ% zMD7%IcD5Qk*V~o;-UA0xLT)YLAq}O!Euh^Zh-(?It#h#jKFI3Xtrsf^JP9fdG`vhe z+49VOH3Vq(wOw5kPz~Z5qMSnqX(6|e$@2Qkv6*OR@Z6MV+t)oVATi|DC8a`8QE>tE zr@(P$U0jRQzzZ=`4^tc0f&3UMGZM=mgA(X-{wipX`Q(WS?um;G?h;Xu-$O+aK}Q@^ zG|79R;G5DBf~hTp1Xt$nE2Ra5!mV+7{uJDQd7;dAb%YK^NDjSTr*-SPu>N*oy|1?m zrL-K&)i1QA%NEtnIoy(lYl4uExF9UF?H`T)FWQfdf*K>B#lr1OoOyF2 zfR7B#{C-k(j?aKlsyq4O%~_}ezQ90%nH-BGB40=AWgD7Ws_2F{o!9ol+G%tQ?0^V*GCQd7{Mnz zVEytH3gsc~_3(+#ucUQhU#J_sUcOgm8%Bz)2Z=#V933zo#6fyT6~z5J?hn3;jGib6 zv!zg6^T+4)eDz-qRHVntR!qM0Fk`!zH%UBw>Ndml&dNP+jh=;fG$I*J8u#qwO9Jzj z<`=s^HcjO9$J+LfF9}6oD11!rlB4J4xTTy)>135^VCv9}aWH^&?0Mv@tVNp>MGKqddwHvu3fD^8uXST+v?=`QK+FW{+F- zMpHoWF*9tePS3MDGrMwU0rmb)oL$+alMuVD_^+eSqWGWQ z%j<9uK!V$2VWUOb1Hd!6492>ceS|B2y6OcWQ_P8seZju!b}tle2+Q^~DG5RIbnY83 z-TtiyhH2kF4KJY+OMw%M1fYL>OByDaYsGF-JM|VYQrrL|5EQK3 zV#)JFm-?Une_)`4_fOBZ2lbnMQrFO*o91;f__+gx+ht&&6A-Ks_Y=B|2Rc^&dK8kz zFEOozkN-K7aI_|$Z%)KdekmC)>vG|8fq+R%f?@iHYE|{0c6R$NFnyD~U4_ol|Iz4* z>ntw;StMjT^{$oS7KmZwSy+|5xdY6-AYu{MwbpO34xOSdai}HUqA|;pd0w*XKpSVW zjRx_VdrcA`x>{d5#0Khm24*aKE%mjpHZn?)qwW(|#F7k;vs*Blsl;1;(fim30N z27o}$7*Ha}C?+K*MnfnqzNA%OEzXcR29iI6aVNo?m6r*KZWaKco4Xs|HQXr?$F(zAc}Wk_4xBj4s#;rIN**%gK3Xs?`DWb zHfpUayP|`u4aNXU44(b#|vSnszmRz-MCo9$6YQ^17VSU-cPw~+|}95p<^q!@$Gl%$0H z*VoMcNEKH2v>v;}ELyq=m@*nVB!A)sC-UC4{$S9O{MZ>tHn1S{dS{wpj|Th63%rCQ z#g`5u*;*aN^W1*X7Q zpxpD@MCCL?gb73}X#}V_Lx8ZWSuS%K5EbcVMHmfD9PRbOYsorMdG?rTOuco|fysW( zRDa`mM_x?rCson5Gz6gG4##$1d>T6h2)}Wpp#EA^)fBXWn0oK|8XzZ#B!f3vnlX|L z$pP#34i&Q2Y2l9DGVd~XHJ6u%gZQ%WO)K0!bV;&);rGS1r9~?l)BqMQArK$R$C)u& z+U<>_H!gG(g>8~w9N-@YRI=?HxIvr|XW^`;^?1qW$k9|k%g_nO{+rOE64Y_w#&1R`xJ%)g~R{`WR9*T#)#d4uU~Ag zzg60y8u0~Wr=^?chhnrOT%Ol1R3EWXcJG^1ajFmQ8`U}L`*B!S5@vUc`VC}$=^%SGEf{x2kN zX+dJPg<3f|%M@Kmg66Iv{VW$c`7WR>?Q1n!T70t9DTK?72YKS09#d z%{1yvxONad2qyc;SgK&fXu*yCZ{2iTYRy8-A-o`kun zY?bfgFNtD{fHLF;d|LC`VYDycST@I8O?ch>Hd<^L8XE%)S<-~vGEIkpmgII$yXF2} zv+d!FP2u93+2SYF1mfRv+W-nDEt>MIQk zRz{Aw{Y3dXGsh5uRuV2=7_bK)7-G;H(8P=qt5v!Y?Tknvf|jf5>1=7f+$F-y zWwh|own&K9lnN+Y_!(~|-*y8kWN1`R`HCP#F0B;?F^?BtEF#s8?o?tLHKD51-`_{OuptdN{V3crllXQ;U`;R z#%&VPKf@V^2ltRHSdH+y<5DOh=__O8mAE27iG+t3tV4j|pgBz@0rJD|SH7Hyl$BB# z*w`Yz&;$f|HXeia^B7g)!}_ZP<)j7fU}~^8mH|#T@=CJN!Yh%RHQA~S**kB$>^;1y zd?o>g?1`g+Pd1uB?^?>0^F1jBiD6H;Y8^NrSb`D4O<0~I)ln%-Rl7`t7huh!LZ|&Q z)L^luF0fo%8`muOShlHPjq8sOw$)k$Z2^~6L(;o{~sYHn!882pBdS`0*Jz+?)THJ;BG-vd)sXS&+-H#Tegb_a_sf-cR z|2krw66RFxXb%p(bF>3+U2H4%dxp?n@y8?u6Yrj>wn3ALo4qJ`ee!L;Aw5?Pt|0a#zOObz?^A*X~MyO#m*jy-WsEH3BOh z2fhONQ0SLc^E{j`8GfAMC)h!0i_J0qo~4D!6_E63wwP~7Deuj+-KXzbFcua$&t}%; zOyszd3C<+(J=TY<>SY4!a3o{zBq?L;bZzqVyKGx&f}aCA^U=fdiOeYnO7M5^4bVig z1$vXBmymh=WY#DtAj6ZG=SsOB&lq=rq-c-EEAWl`2f+={XiJMCm&~e6>GhNuij90W zqMb5W704s~I(Gs&uSt}#2)r^8TRRd3G?Wn+2`NP`xuDrOQmXX<~9 zq{P??5Snq6R@+gn%hG5QEB65lgXHq=%=9BcF+`o{>mr7gx?W7)gwptzyZx8Ii!}|c zi+0npPbXaS6%;m@#eA7XblrBDw!|XXi&LP z9HqOItyU|T7}jo1y}v!g(t^!;EP~5f*Ge<^8hWa(EEC4;`ENp zqT!Z zjG2*!q;E&q2`-A$xHrA*-AB5Fa9}QYmad#Ool$;!+BZufjhy*Vj*1 zMQIK_&VqL5mpVPk?nJUrwNpLTHOeo(k{1t&GWae!<~3lkYOhzBS#1?aLL}NS6pNZ% zs&z&_3zu@PnxEV%x5SyRcfE`}4Cf_CJe&uT89m&VMQM5OA$?F^**&wMoKUQWiWrtL8k#(s9%|23*zG z;Y;(6gd1$g;knHjw31)C2lEqb^U$OSyD+~-IvDb!c;2U0x56m$)8d+lex8Reb5ngK zy#@y}Z8s89t`KVah&mDewt?^OY|FyJ`C1rvuJpv+Chti^t6|V4)y>IZxmFJ?A0~R7 z{W&rX3$IO0oxYtlk;MtCucNBc0_)c9ncBu_;5R&mQ!<(j>@k9D1i2d7lakJalX5NU zjkD^Gw7sI7jATHfawDp3pb#YqYQItEQ+sk#%C$4&BaRwm$fbMEjMW6%M>OVlB zkXg}%6IBi)ouClNGy1bOh4TCo%_97t?Jofw`e&cZSiy4@7C!Oa;paz_Ag#~i{~tcu zQB6Ve@T@)x{at|n#4^7gg*uP_p|pRA>A$>~uuJR4cTRP$tvQ(v{_F;i+3%JI3lE&W G_TK;?ILhMy literal 0 HcmV?d00001 diff --git a/test/image/mocks/bar_group_barbase.json b/test/image/mocks/bar_group_barbase.json new file mode 100644 index 00000000000..d9937a8943f --- /dev/null +++ b/test/image/mocks/bar_group_barbase.json @@ -0,0 +1,44 @@ +{ + "data": [ + { + "x": [ + "giraffes", + "orangutans", + "monkeys" + ], + "y": [ + 20, + 14, + 23 + ], + "name": "SF Zoo", + "type": "bar" + }, + { + "x": [ + "giraffes", + "orangutans", + "monkeys" + ], + "y": [ + 12, + 18, + 29 + ], + "name": "LA Zoo", + "type": "bar" + } + ], + "layout": { + "xaxis": { + "type": "category" + }, + "barbase": 10, + "barmode": "group", + "categories": [ + "giraffes", + "orangutans", + "monkeys" + ] + } +}