From abab6727a70005044048b202adaabbdea45dacf0 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 6 Oct 2016 15:27:31 +0100 Subject: [PATCH 01/34] 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 c8b33f02551f381f2592790666bbb5295a0104c9 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 7 Oct 2016 13:12:41 +0100 Subject: [PATCH 02/34] 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 2a72e972a2ad5693657a275d01424267e527c45a Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 7 Oct 2016 14:51:52 +0100 Subject: [PATCH 03/34] 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 3dd6ed042ccd77e544d3f3c9ca57a1612f7f0a56 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 10 Oct 2016 14:17:01 +0100 Subject: [PATCH 04/34] 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 f63a7b7881712b2d25fa363df231761dd6b3875d Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 10 Oct 2016 15:04:39 +0100 Subject: [PATCH 05/34] 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 451ee82890bb1ce10857406280cd0a958a94f6bc Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 12 Oct 2016 13:49:07 +0100 Subject: [PATCH 06/34] 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 c150b3dd6812904548069e9e2fb1cb35da21ea43 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 12 Oct 2016 18:28:45 +0100 Subject: [PATCH 07/34] 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 fa37487757d7b5d7f543e13b838668ce4a8c76e5 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 13 Oct 2016 16:54:46 +0100 Subject: [PATCH 08/34] 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 57deb7b15643ab137cbf1afa2fd0f8df79b0815f Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 14 Oct 2016 18:25:16 +0100 Subject: [PATCH 09/34] 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 8dfa0bd36c53960aeafdfebdfe626e0c87b96939 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 14 Oct 2016 19:18:08 +0100 Subject: [PATCH 10/34] 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 c61e64256265d3d7183a1a84a723dfa49277ff62 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 17 Oct 2016 17:52:05 +0100 Subject: [PATCH 11/34] bar: fix image test failure in hist_grouped * Allow minDtick update if barmode is group and bars overlap. --- src/traces/bar/set_positions.js | 45 +++++++++++++-------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 14f1d901956..cd755b21fbf 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -77,18 +77,12 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { traces.forEach(function(trace) { var sieve = new Sieve( [trace], 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}); - - // update size axis and set bar bases and sizes + // set bar bases and sizes, and update size axis if(barnorm) { stackBars(gd, sa, sieve); } @@ -112,18 +106,12 @@ 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 + // set bar bases and sizes, and update size axis if(barnorm) { stackBars(gd, sa, sieve); } @@ -151,18 +139,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); } @@ -173,6 +155,7 @@ function setOffsetAndWidth(gd, pa, sieve) { traces = sieve.traces, bargap = fullLayout.bargap, bargroupgap = fullLayout.bargroupgap, + distinctPositions = sieve.distinctPositions, minDiff = sieve.minDiff; // set bar offsets and widths @@ -199,6 +182,10 @@ function setOffsetAndWidth(gd, pa, sieve) { bar[pLetter] = bar.p + barCenter; } } + + // update position axes + Axes.minDtick(pa, minDiff, distinctPositions[0]); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); } @@ -243,6 +230,10 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { bar[pLetter] = bar.p + barCenter; } } + + // update position axes + Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); + Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); } From b25467bc4d700ccf1890f260e43b22e907da6930 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 17 Oct 2016 17:58:23 +0100 Subject: [PATCH 12/34] bar: do not normalise bars with no size --- src/traces/bar/set_positions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index cd755b21fbf..b83f9c5846a 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -309,9 +309,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; From 54ac1f108f6c987b51d82ad28339ff3a072abfa0 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 19 Oct 2016 18:06:10 +0100 Subject: [PATCH 13/34] bar: Add attributes base, offset and width --- src/traces/bar/attributes.js | 35 +++++++++++++++++++++++++++++++++++ src/traces/bar/defaults.js | 3 +++ 2 files changed, 38 insertions(+) diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 25b7568bd13..e7733d983cf 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -48,6 +48,41 @@ module.exports = { ].join(' ') }, + base: { + valType: 'any', + arrayOk: true, + role: 'info', + description: [ + 'Sets where the bar base is drawn (in position axis units).', + 'In *stack* or *relative* barmode,', + 'traces that set *base* will be excluded', + 'and drawn in *overlay* mode instead.' + ].join(' ') + }, + + offset: { + valType: 'number', + arrayOk: true, + role: 'info', + description: [ + 'Shifts the position where the bar is drawn', + '(in position axis units).', + 'In *group* barmode,', + 'traces that set *offset* will be excluded', + 'and drawn in *overlay* mode instead.' + ].join(' ') + }, + + width: { + valType: 'number', + min: 0, + arrayOk: true, + role: 'info', + description: [ + 'Sets the bar width (in position axis units).' + ].join(' ') + }, + marker: marker, r: scatterAttrs.r, diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 89290bf8cee..6e094021140 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -30,6 +30,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v'); + coerce('base'); + coerce('offset'); + coerce('width'); coerce('text'); handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); From 23bdc825d94ecbf6fbd28473bb641e6c27173dc6 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 19 Oct 2016 19:18:07 +0100 Subject: [PATCH 14/34] bar: add function sieveBars * Function sieveBars sieves all the bars without updating the bar bases. This step is required before calling normalizeBars. * Function stackBars sieves all the bars and updates the bar bases and tops accordingly. --- src/traces/bar/set_positions.js | 60 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index b83f9c5846a..bda81219c30 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -84,7 +84,8 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { // set bar bases and sizes, and update size axis if(barnorm) { - stackBars(gd, sa, sieve); + sieveBars(gd, sa, sieve); + normalizeBars(gd, sa, sieve); } else { // make sure the size axis includes zero, @@ -113,7 +114,8 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { // set bar bases and sizes, and update size axis if(barnorm) { - stackBars(gd, sa, sieve); + sieveBars(gd, sa, sieve); + normalizeBars(gd, sa, sieve); } else { // make sure the size axis includes zero, @@ -231,6 +233,9 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { } } + // stack bars that only differ by rounding + sieve.binWidth = traces[0][0].t.barwidth / 100; + // update position axes Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); @@ -239,15 +244,12 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { function stackBars(gd, sa, sieve) { var fullLayout = gd._fullLayout, + barnorm = fullLayout.barnorm, sLetter = getAxisLetter(sa), traces = sieve.traces, i, trace, j, bar; - var stack = (fullLayout.barmode === 'stack'), - relative = (fullLayout.barmode === 'relative'), - norm = fullLayout.barnorm; - // bar size range and stacking calculation // for stacked bars, we need to evaluate every step in every // stack, because negative bars mean the extremes could be @@ -257,37 +259,32 @@ function stackBars(gd, sa, sieve) { var sMax = sa.l2c(sa.c2l(0)), sMin = sMax; - // 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; + // store the bar base and top in each calcdata item + bar.b = previousSum; - // 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); - } + var barEnd = bar.b + bar.s; + bar[sLetter] = barEnd; + + if(!barnorm && isNumeric(sa.c2l(barEnd))) { + sMax = Math.max(sMax, barEnd); + sMin = Math.min(sMin, barEnd); } } } - if(norm) { + // if barnorm is set, let normalizeBars update the axis range + if(barnorm) { normalizeBars(gd, sa, sieve); } else { @@ -296,7 +293,27 @@ function stackBars(gd, sa, sieve) { } +function sieveBars(gd, sa, sieve) { + var traces = sieve.traces; + + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + + for(var j = 0; j < trace.length; j++) { + var bar = trace[j]; + + if(isNumeric(bar.s)) sieve.put(bar.p, bar.s); + } + } +} + + function normalizeBars(gd, sa, sieve) { + // Note: + // + // normalizeBars requires that either sieveBars or stackBars has been + // previously invoked. + var traces = sieve.traces, sLetter = getAxisLetter(sa), sTop = (gd._fullLayout.barnorm === 'fraction') ? 1 : 100, @@ -332,6 +349,7 @@ function normalizeBars(gd, sa, sieve) { } } + // update range of size axis Axes.expand(sa, [sMin, sMax], {tozero: true, padded: padded}); } From 19e114e7ffd20078169b7fb430149ef44c76750d Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 19 Oct 2016 22:41:21 +0100 Subject: [PATCH 15/34] bar: do not assume initial bar base is zero --- src/traces/bar/set_positions.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index bda81219c30..e25f82d5d9c 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -92,7 +92,11 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { // 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; }; + fs = function(v) { + var barTop = v.b + v.s; + v[sLetter] = barTop; + return barTop; + }; Axes.expand(sa, trace.map(fs), {tozero: true, padded: true}); } @@ -122,7 +126,11 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { // 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; }; + fs = function(v) { + var barTop = v.b + v.s; + v[sLetter] = barTop; + return barTop; + }; for(var i = 0; i < traces.length; i++) { Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); @@ -268,7 +276,7 @@ function stackBars(gd, sa, sieve) { if(!isNumeric(bar.s)) continue; // stack current bar and get previous sum - var previousSum = sieve.put(bar.p, bar.s); + var previousSum = sieve.put(bar.p, bar.b + bar.s); // store the bar base and top in each calcdata item bar.b = previousSum; @@ -302,7 +310,7 @@ function sieveBars(gd, sa, sieve) { for(var j = 0; j < trace.length; j++) { var bar = trace[j]; - if(isNumeric(bar.s)) sieve.put(bar.p, bar.s); + if(isNumeric(bar.s)) sieve.put(bar.p, bar.b + bar.s); } } } @@ -330,7 +338,7 @@ function normalizeBars(gd, sa, sieve) { if(!isNumeric(bar.s)) continue; - var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); + var scale = Math.abs(sTop / sieve.get(bar.p, bar.b + bar.s)); bar.b *= scale; bar.s *= scale; var barEnd = bar.b + bar.s; From f2a365e140f49b1540115ea685a36b4bc58fa0c4 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 20 Oct 2016 10:42:29 +0100 Subject: [PATCH 16/34] bar: ensure size axis includes bar bases --- src/traces/bar/set_positions.js | 114 +++++++++++++++++++------------- 1 file changed, 69 insertions(+), 45 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index e25f82d5d9c..debde942039 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -88,17 +88,7 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { normalizeBars(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) { - var barTop = v.b + v.s; - v[sLetter] = barTop; - return barTop; - }; - - Axes.expand(sa, trace.map(fs), {tozero: true, padded: true}); + setBaseAndTop(gd, sa, sieve); } }); } @@ -122,19 +112,7 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { normalizeBars(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) { - var barTop = v.b + v.s; - v[sLetter] = barTop; - return barTop; - }; - - for(var i = 0; i < traces.length; i++) { - Axes.expand(sa, traces[i].map(fs), {tozero: true, padded: true}); - } + setBaseAndTop(gd, sa, sieve); } } @@ -250,6 +228,40 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { } +function setBaseAndTop(gd, sa, sieve) { + // store these bar bases and tops in calcdata + // and make sure the size axis includes zero, + // along with the bases and tops of each bar. + var traces = sieve.traces, + sLetter = getAxisLetter(sa), + sMax = sa.l2c(sa.c2l(0)), + sMin = sMax; + + for(var i = 0; i < traces.length; i++) { + var trace = traces[i]; + + for(var j = 0; j < trace.length; j++) { + var bar = trace[j], + barBase = bar.b, + barTop = barBase + bar.s; + + bar[sLetter] = barTop; + + if(isNumeric(sa.c2l(barTop))) { + sMax = Math.max(sMax, barTop); + sMin = Math.min(sMin, barTop); + } + if(isNumeric(sa.c2l(barBase))) { + sMax = Math.max(sMax, barBase); + sMin = Math.min(sMin, barBase); + } + } + } + + Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}); +} + + function stackBars(gd, sa, sieve) { var fullLayout = gd._fullLayout, barnorm = fullLayout.barnorm, @@ -258,12 +270,6 @@ function stackBars(gd, sa, sieve) { i, trace, j, bar; - // bar size range and stacking calculation - // 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; @@ -276,17 +282,22 @@ function stackBars(gd, sa, sieve) { if(!isNumeric(bar.s)) continue; // stack current bar and get previous sum - var previousSum = sieve.put(bar.p, bar.b + bar.s); + var barBase = sieve.put(bar.p, bar.b + bar.s), + barTop = barBase + bar.s; // store the bar base and top in each calcdata item - bar.b = previousSum; + bar.b = barBase; + bar[sLetter] = barTop; - var barEnd = bar.b + bar.s; - bar[sLetter] = barEnd; - - if(!barnorm && isNumeric(sa.c2l(barEnd))) { - sMax = Math.max(sMax, barEnd); - sMin = Math.min(sMin, barEnd); + if(!barnorm) { + if(isNumeric(sa.c2l(barTop))) { + sMax = Math.max(sMax, barTop); + sMin = Math.min(sMin, barTop); + } + if(isNumeric(sa.c2l(barBase))) { + sMax = Math.max(sMax, barBase); + sMin = Math.min(sMin, barBase); + } } } } @@ -341,17 +352,30 @@ function normalizeBars(gd, sa, sieve) { var scale = Math.abs(sTop / sieve.get(bar.p, bar.b + 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) { + var barBase = bar.b, + barTop = barBase + bar.s; + bar[sLetter] = barTop; + + if(isNumeric(sa.c2l(barTop))) { + if(barTop < sMin - sTiny) { + padded = true; + sMin = barTop; + } + if(barTop > sMax + sTiny) { + padded = true; + sMax = barTop; + } + } + + if(isNumeric(sa.c2l(barBase))) { + if(barBase < sMin - sTiny) { padded = true; - sMin = barEnd; + sMin = barBase; } - if(barEnd > sMax + sTiny) { + if(barBase > sMax + sTiny) { padded = true; - sMax = barEnd; + sMax = barBase; } } } From 83fdb449cd9f1d98c6e8892e715698bfe3bf4a80 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 20 Oct 2016 12:07:29 +0100 Subject: [PATCH 17/34] bar: do not stack traces that set bar base --- src/traces/bar/set_positions.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index debde942039..c0548ed8337 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -54,7 +54,10 @@ function setGroupPositions(gd, pa, sa, traces) { var barmode = gd._fullLayout.barmode, overlay = (barmode === 'overlay'), - group = (barmode === 'group'); + group = (barmode === 'group'), + excluded, + included, + i, trace; if(overlay) { setGroupPositionsInOverlayMode(gd, pa, sa, traces); @@ -63,7 +66,21 @@ function setGroupPositions(gd, pa, sa, traces) { setGroupPositionsInGroupMode(gd, pa, sa, traces); } else { - setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces); + // exclude from the stack those traces for which the user set a base + excluded = []; + included = []; + for(i = 0; i < traces.length; i++) { + trace = traces[i]; + if(trace.base === undefined) included.push(trace); + else excluded.push(trace); + } + + if(included.length) { + setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included); + } + if(excluded.length) { + setGroupPositionsInOverlayMode(gd, pa, sa, excluded); + } } } From 8d75c3af00ed5471ecc7838e213b6df8c1430c96 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 11:06:02 +0100 Subject: [PATCH 18/34] bar: implement attributes base, offset and width --- src/traces/bar/calc.js | 28 ++- src/traces/bar/plot.js | 29 ++- src/traces/bar/set_positions.js | 313 ++++++++++++++++++++++++++------ 3 files changed, 301 insertions(+), 69 deletions(-) diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index dc8ee1e77e4..46825ecfc5d 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -40,6 +40,7 @@ module.exports = function calc(gd, trace) { var serieslen = Math.min(pos.length, size.length), cd = []; + // set position for(i = 0; i < serieslen; i++) { // add bars with non-numeric sizes to calcdata @@ -47,7 +48,32 @@ module.exports = function calc(gd, trace) { // plotted in the correct order if(isNumeric(pos[i])) { - cd.push({p: pos[i], s: size[i], b: 0}); + cd.push({p: pos[i]}); + } + } + + // set base + var base = trace.base; + + if(Array.isArray(base)) { + for(i = 0; i < Math.min(base.length, cd.length); i++) { + cd[i].b = base[i]; + } + for(; i < cd.length; i++) { + cd[i].b = 0; + } + } + else { + var b = (base === undefined) ? 0 : base; + for(i = 0; i < cd.length; i++) { + cd[i].b = b; + } + } + + // set size + for(i = 0; i < cd.length; i++) { + if(isNumeric(size[i])) { + cd[i].s = size[i] - cd[i].b; } } diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 0ea14bf3ee8..4e0cb53ac29 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -34,30 +34,39 @@ module.exports = function plot(gd, plotinfo, cdbar) { .attr('class', 'points') .each(function(d) { var t = d[0].t, - trace = d[0].trace; + trace = d[0].trace, + poffset = t.poffset, + poffsetIsArray = Array.isArray(poffset), + barwidth = t.barwidth, + barwidthIsArray = Array.isArray(barwidth); arraysToCalcdata(d); d3.select(this).selectAll('path') .data(Lib.identity) .enter().append('path') - .each(function(di) { + .each(function(di, i) { // now display the bar // clipped xf/yf (2nd arg true): non-positive // log values go off-screen by plotwidth // so you see them continue if you drag the plot + var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset), + p1 = p0 + ((barwidthIsArray) ? barwidth[i] : barwidth), + s0 = di.b, + s1 = s0 + di.s; + var x0, x1, y0, y1; if(trace.orientation === 'h') { - y0 = ya.c2p(t.poffset + di.p, true); - y1 = ya.c2p(t.poffset + di.p + t.barwidth, true); - x0 = xa.c2p(di.b, true); - x1 = xa.c2p(di.s + di.b, true); + y0 = ya.c2p(p0, true); + y1 = ya.c2p(p1, true); + x0 = xa.c2p(s0, true); + x1 = xa.c2p(s1, true); } else { - x0 = xa.c2p(t.poffset + di.p, true); - x1 = xa.c2p(t.poffset + di.p + t.barwidth, true); - y1 = ya.c2p(di.s + di.b, true); - y0 = ya.c2p(di.b, true); + x0 = xa.c2p(p0, true); + x1 = xa.c2p(p1, true); + y0 = ya.c2p(s0, true); + y1 = ya.c2p(s1, true); } if(!isNumeric(x0) || !isNumeric(x1) || diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index c0548ed8337..fa164596771 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -26,53 +26,75 @@ module.exports = function setPositions(gd, plotinfo) { var xa = plotinfo.xaxis, ya = plotinfo.yaxis; - var traces = gd._fullData, - tracesCalc = gd.calcdata, - tracesHorizontal = [], - tracesVertical = [], + var fullTraces = gd._fullData, + calcTraces = gd.calcdata, + calcTracesHorizontal = [], + calcTracesVertical = [], i; - for(i = 0; i < traces.length; i++) { - var trace = traces[i]; + for(i = 0; i < fullTraces.length; i++) { + var fullTrace = fullTraces[i]; if( - trace.visible === true && - Registry.traceIs(trace, 'bar') && - trace.xaxis === xa._id && - trace.yaxis === ya._id + fullTrace.visible === true && + Registry.traceIs(fullTrace, 'bar') && + fullTrace.xaxis === xa._id && + fullTrace.yaxis === ya._id ) { - if(trace.orientation === 'h') tracesHorizontal.push(tracesCalc[i]); - else tracesVertical.push(tracesCalc[i]); + if(fullTrace.orientation === 'h') { + calcTracesHorizontal.push(calcTraces[i]); + } + else { + calcTracesVertical.push(calcTraces[i]); + } } } - setGroupPositions(gd, xa, ya, tracesVertical); - setGroupPositions(gd, ya, xa, tracesHorizontal); + setGroupPositions(gd, xa, ya, calcTracesVertical); + setGroupPositions(gd, ya, xa, calcTracesHorizontal); }; -function setGroupPositions(gd, pa, sa, traces) { - if(!traces.length) return; +function setGroupPositions(gd, pa, sa, calcTraces) { + if(!calcTraces.length) return; var barmode = gd._fullLayout.barmode, overlay = (barmode === 'overlay'), group = (barmode === 'group'), excluded, included, - i, trace; + i, calcTrace, fullTrace; if(overlay) { - setGroupPositionsInOverlayMode(gd, pa, sa, traces); + setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces); } else if(group) { - setGroupPositionsInGroupMode(gd, pa, sa, traces); + // exclude from the group those traces for which the user set an offset + excluded = []; + included = []; + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + fullTrace = calcTrace[0].trace; + + if(fullTrace.offset === undefined) included.push(calcTrace); + else excluded.push(calcTrace); + } + + if(included.length) { + setGroupPositionsInGroupMode(gd, pa, sa, included); + } + if(excluded.length) { + setGroupPositionsInOverlayMode(gd, pa, sa, excluded); + } } else { // exclude from the stack those traces for which the user set a base excluded = []; included = []; - for(i = 0; i < traces.length; i++) { - trace = traces[i]; - if(trace.base === undefined) included.push(trace); - else excluded.push(trace); + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + fullTrace = calcTrace[0].trace; + + if(fullTrace.base === undefined) included.push(calcTrace); + else excluded.push(calcTrace); } if(included.length) { @@ -85,16 +107,16 @@ function setGroupPositions(gd, pa, sa, traces) { } -function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { +function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) { var barnorm = gd._fullLayout.barnorm, separateNegativeValues = false, dontMergeOverlappingData = !barnorm; // update position axis and set bar offsets and widths - traces.forEach(function(trace) { + calcTraces.forEach(function(calcTrace) { var sieve = new Sieve( - [trace], separateNegativeValues, dontMergeOverlappingData - ); + [calcTrace], separateNegativeValues, dontMergeOverlappingData + ); // set bar offsets and widths, and update position axis setOffsetAndWidth(gd, pa, sieve); @@ -111,13 +133,13 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, traces) { } -function setGroupPositionsInGroupMode(gd, pa, sa, traces) { +function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces) { var fullLayout = gd._fullLayout, barnorm = fullLayout.barnorm, separateNegativeValues = false, dontMergeOverlappingData = !barnorm, sieve = new Sieve( - traces, separateNegativeValues, dontMergeOverlappingData + calcTraces, separateNegativeValues, dontMergeOverlappingData ); // set bar offsets and widths, and update position axis @@ -134,7 +156,7 @@ function setGroupPositionsInGroupMode(gd, pa, sa, traces) { } -function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) { +function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces) { var fullLayout = gd._fullLayout, barmode = fullLayout.barmode, stack = (barmode === 'stack'), @@ -143,7 +165,7 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) { separateNegativeValues = relative, dontMergeOverlappingData = !(barnorm || stack || relative), sieve = new Sieve( - traces, separateNegativeValues, dontMergeOverlappingData + calcTraces, separateNegativeValues, dontMergeOverlappingData ); // set bar offsets and widths, and update position axis @@ -157,11 +179,13 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, traces) { function setOffsetAndWidth(gd, pa, sieve) { var fullLayout = gd._fullLayout, pLetter = getAxisLetter(pa), - traces = sieve.traces, bargap = fullLayout.bargap, bargroupgap = fullLayout.bargroupgap, - distinctPositions = sieve.distinctPositions, - minDiff = sieve.minDiff; + minDiff = sieve.minDiff, + calcTraces = sieve.traces, + i, calcTrace, calcTrace0, fullTrace, + j, calcBar, + t; // set bar offsets and widths var barGroupWidth = minDiff * (1 - bargap), @@ -172,76 +196,249 @@ function setOffsetAndWidth(gd, pa, sieve) { var offsetFromCenter = -barWidth / 2, barCenter = 0; - for(var i = 0; i < traces.length; i++) { - var trace = traces[i]; + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + calcTrace0 = calcTrace[0]; // store bar width and offset for this trace - var t = trace[0].t; + t = calcTrace0.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; + for(j = 0; j < calcTrace.length; j++) { + calcBar = calcTrace[j]; + calcBar[pLetter] = calcBar.p + barCenter; + } + } + + // stack bars that only differ by rounding + sieve.binWidth = calcTraces[0][0].t.barwidth / 100; + + // if defined, apply trace offset and width + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + calcTrace0 = calcTrace[0]; + fullTrace = calcTrace0.trace; + t = calcTrace0.t; + + var offset = fullTrace.offset, + initialPoffset = t.poffset, + newPoffset; + if(Array.isArray(offset)) { + // if offset is an array, then clone it into t.poffset. + newPoffset = offset.slice(0, calcTrace.length); + + // if the length of the array is too short, + // then extend it with the initial value of t.poffset + for(j = newPoffset.length; j < calcTrace.length; j++) { + newPoffset.push(initialPoffset); + } + + t.poffset = newPoffset; + } + else if(offset !== undefined) { + t.poffset = offset; + } + + var width = fullTrace.width, + initialBarwidth = t.barwidth; + if(Array.isArray(width)) { + // if width is an array, then clone it into t.barwidth. + var newBarwidth = width.slice(0, calcTrace.length); + + // if the length of the array is too short, + // then extend it with the initial value of t.barwidth + for(j = newBarwidth.length; j < calcTrace.length; j++) { + newBarwidth.push(initialBarwidth); + } + + t.barwidth = newBarwidth; + + // if user didn't set offset, + // then correct t.poffset to ensure bars remain centered + if(offset === undefined) { + newPoffset = []; + for(j = 0; j < calcTrace.length; j++) { + newPoffset.push( + initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 + ); + } + t.poffset = newPoffset; + } + } + else if(width !== undefined) { + t.barwidth = width; + + // if user didn't set offset, + // then correct t.poffset to ensure bars remain centered + if(offset === undefined) { + t.poffset = initialPoffset + (initialBarwidth - width) / 2; + } } } // update position axes - Axes.minDtick(pa, minDiff, distinctPositions[0]); - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); + updatePositionAxis(gd, pa, sieve); } 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; + minDiff = sieve.minDiff, + calcTraces = sieve.traces, + i, calcTrace, calcTrace0, fullTrace, + j, calcBar, + t; // 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, + var nTraces = calcTraces.length, + barGroupWidth = minDiff * (1 - bargap), + barWidthPlusGap = (overlap) ? barGroupWidth / nTraces : barGroupWidth, barWidth = barWidthPlusGap * (1 - bargroupgap); - for(var i = 0; i < traces.length; i++) { - var trace = traces[i]; + for(i = 0; i < nTraces; i++) { + calcTrace = calcTraces[i]; + calcTrace0 = calcTrace[0]; // computer bar group center and bar offset var offsetFromCenter = (overlap) ? - ((2 * i + 1 - traces.length) * barWidthPlusGap - barWidth) / 2 : + ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 : -barWidth / 2, barCenter = offsetFromCenter + barWidth / 2; // store bar width and offset for this trace - var t = trace[0].t; + t = calcTrace0.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; + for(j = 0; j < calcTrace.length; j++) { + calcBar = calcTrace[j]; + calcBar[pLetter] = calcBar.p + barCenter; } } // stack bars that only differ by rounding - sieve.binWidth = traces[0][0].t.barwidth / 100; + sieve.binWidth = calcTraces[0][0].t.barwidth / 100; + + // if defined, apply trace width + for(i = 0; i < calcTraces.length; i++) { + calcTrace = calcTraces[i]; + calcTrace0 = calcTrace[0]; + fullTrace = calcTrace0.trace; + + var width = fullTrace.width; + if(width === undefined) continue; + + t = calcTrace0.t; + var initialBarwidth = t.barwidth, + initialPoffset = t.poffset; + if(Array.isArray(width)) { + // if width is an array, then clone it into t.barwidth. + var newBarwidth = width.slice(0, calcTrace.length); + + // if the length of the array is too short, + // then extend it with the initial value of t.barwidth + for(j = newBarwidth.length; j < calcTrace.length; j++) { + newBarwidth.push(initialBarwidth); + } + + t.barwidth = newBarwidth; + + // correct t.poffset to ensure bars remain centered + var newPoffset = []; + for(j = 0; j < calcTrace.length; j++) { + newPoffset.push( + initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 + ); + } + t.poffset = newPoffset; + } + else { + t.barwidth = width; + + // correct t.poffset to ensure bars remain centered + t.poffset = initialPoffset + (initialBarwidth - width) / 2; + } + } // update position axes - Axes.minDtick(pa, minDiff, distinctPositions[0], overlap); - Axes.expand(pa, distinctPositions, {vpad: minDiff / 2}); + updatePositionAxis(gd, pa, sieve, overlap); +} + + +function updatePositionAxis(gd, pa, sieve, allowMinDtick) { + var calcTraces = sieve.traces, + distinctPositions = sieve.distinctPositions, + distinctPositions0 = distinctPositions[0], + minDiff = sieve.minDiff, + vpad = minDiff / 2; + + Axes.minDtick(pa, minDiff, distinctPositions0, allowMinDtick); + Axes.expand(pa, distinctPositions, {vpad: vpad}); + + // if user set bar width or offset, + // then check whether axis needs expanding + var minPos = Math.min.apply(Math, distinctPositions) - vpad, + maxPos = Math.max.apply(Math, distinctPositions) + vpad, + pMin, pMinOffset, pMinWidth, + pMax, pMaxOffset, pMaxWidth; + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i], + calcTrace0 = calcTrace[0], + fullTrace = calcTrace0.trace, + t = calcTrace0.t, + poffset = t.poffset, + poffsetIsArray = Array.isArray(poffset), + barwidth = t.barwidth, + barwidthIsArray = Array.isArray(barwidth); + + if(fullTrace.width === undefined && fullTrace.offset === undefined) { + continue; + } + + for(var j = 0; j < calcTrace.length; j++) { + var calcBar = calcTrace[j], + calcBarOffset = (poffsetIsArray) ? poffset[j] : poffset, + calcBarWidth = (barwidthIsArray) ? barwidth[j] : barwidth, + p = calcBar.p, + l = p + calcBarOffset, + r = l + calcBarWidth; + + if(r >= maxPos) { + maxPos = r; + pMax = p; + pMaxOffset = calcBarOffset; + pMaxWidth = calcBarWidth; + } + if(l <= minPos) { + minPos = r; + pMin = p; + pMinOffset = calcBarOffset; + pMinWidth = calcBarWidth; + } + } + } + + if(pMin) { + vpad = pMinWidth / 2; + Axes.expand(pa, [pMin + pMinOffset + vpad], {vpad: vpad}); + } + if(pMax) { + vpad = pMaxWidth / 2; + Axes.expand(pa, [pMax + pMaxOffset + vpad], {vpad: vpad}); + } } From 2e8c7a1232bdc2fb8db8fd26e92cc8e71d76df9d Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 16:10:50 +0100 Subject: [PATCH 19/34] bar: guard against invalid base items --- src/traces/bar/calc.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 46825ecfc5d..1943ffd45cc 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -25,13 +25,15 @@ module.exports = function calc(gd, trace) { var xa = Axes.getFromId(gd, trace.xaxis || 'x'), ya = Axes.getFromId(gd, trace.yaxis || 'y'), orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'), - pos, size, i; + sa, pos, size, i; if(orientation === 'h') { + sa = xa; size = xa.makeCalcdata(trace, 'x'); pos = ya.makeCalcdata(trace, 'y'); } else { + sa = ya; size = ya.makeCalcdata(trace, 'y'); pos = xa.makeCalcdata(trace, 'x'); } @@ -53,18 +55,21 @@ module.exports = function calc(gd, trace) { } // set base - var base = trace.base; + var base = trace.base, + b; if(Array.isArray(base)) { for(i = 0; i < Math.min(base.length, cd.length); i++) { - cd[i].b = base[i]; + b = sa.d2c(base[i]); + cd[i].b = (isNumeric(b)) ? b : 0; } for(; i < cd.length; i++) { cd[i].b = 0; } } else { - var b = (base === undefined) ? 0 : base; + b = sa.d2c(base); + b = (isNumeric(b)) ? b : 0; for(i = 0; i < cd.length; i++) { cd[i].b = b; } From dbbf8e7e7e01807fa587f1a3e8fcb3d720dce72f Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 18:46:59 +0100 Subject: [PATCH 20/34] bar: guard against invalid offset or width items --- src/traces/bar/set_positions.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index fa164596771..794e392f18f 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -230,6 +230,11 @@ function setOffsetAndWidth(gd, pa, sieve) { // if offset is an array, then clone it into t.poffset. newPoffset = offset.slice(0, calcTrace.length); + // guard against non-numeric items + for(j = 0; j < newPoffset.length; j++) { + if(!isNumeric(newPoffset[j])) newPoffset[j] = initialPoffset; + } + // if the length of the array is too short, // then extend it with the initial value of t.poffset for(j = newPoffset.length; j < calcTrace.length; j++) { @@ -248,6 +253,11 @@ function setOffsetAndWidth(gd, pa, sieve) { // if width is an array, then clone it into t.barwidth. var newBarwidth = width.slice(0, calcTrace.length); + // guard against non-numeric items + for(j = 0; j < newBarwidth.length; j++) { + if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth; + } + // if the length of the array is too short, // then extend it with the initial value of t.barwidth for(j = newBarwidth.length; j < calcTrace.length; j++) { @@ -348,6 +358,11 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { // if width is an array, then clone it into t.barwidth. var newBarwidth = width.slice(0, calcTrace.length); + // guard against non-numeric items + for(j = 0; j < newBarwidth.length; j++) { + if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth; + } + // if the length of the array is too short, // then extend it with the initial value of t.barwidth for(j = newBarwidth.length; j < calcTrace.length; j++) { From 655b3b91571a56ffc9002df1e479e8a3f652672f Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 18:55:24 +0100 Subject: [PATCH 21/34] bar: remove use of forEach --- src/traces/bar/set_positions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 794e392f18f..3217f9189c9 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -113,7 +113,9 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) { dontMergeOverlappingData = !barnorm; // update position axis and set bar offsets and widths - calcTraces.forEach(function(calcTrace) { + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + var sieve = new Sieve( [calcTrace], separateNegativeValues, dontMergeOverlappingData ); @@ -129,7 +131,7 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) { else { setBaseAndTop(gd, sa, sieve); } - }); + } } From 1eaa1305386d9edee016fcb736ed703fb1246806 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 18:59:34 +0100 Subject: [PATCH 22/34] bar: set base, offset and width default to null * Null defaults are used to denote optional properties. * Note that null values are ignored by `Lib.nestedProperty(/**/).set()`, so that the pattern `fullTrace.base === undefined` still works. --- src/traces/bar/attributes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index e7733d983cf..c5659f56611 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -50,6 +50,7 @@ module.exports = { base: { valType: 'any', + dflt: null, arrayOk: true, role: 'info', description: [ @@ -62,6 +63,7 @@ module.exports = { offset: { valType: 'number', + dflt: null, arrayOk: true, role: 'info', description: [ @@ -75,6 +77,7 @@ module.exports = { width: { valType: 'number', + dflt: null, min: 0, arrayOk: true, role: 'info', From 83168bdc518d3096dc7f2ef20fa7026ab2c788f6 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 22:43:54 +0100 Subject: [PATCH 23/34] bar: add note to setGroupPositionsInOverlayMode * Added note to explain the reason why setGroupPositionsInOverlayMode handles the case barnorm is defined. --- src/traces/bar/set_positions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 3217f9189c9..bb1bdb4a91e 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -124,6 +124,10 @@ function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) { setOffsetAndWidth(gd, pa, sieve); // set bar bases and sizes, and update size axis + // + // (note that `setGroupPositionsInOverlayMode` handles the case barnorm + // is defined, because this function is also invoked for traces that + // can't be grouped or stacked) if(barnorm) { sieveBars(gd, sa, sieve); normalizeBars(gd, sa, sieve); From 5d4c84a07c699a55af80bd04dded24235718c054 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 24 Oct 2016 09:33:46 +0100 Subject: [PATCH 24/34] bar: fix expansion of position axis --- src/traces/bar/set_positions.js | 44 ++++++++++----------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index bb1bdb4a91e..663c9fbcd61 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -407,28 +407,27 @@ function updatePositionAxis(gd, pa, sieve, allowMinDtick) { vpad = minDiff / 2; Axes.minDtick(pa, minDiff, distinctPositions0, allowMinDtick); - Axes.expand(pa, distinctPositions, {vpad: vpad}); // if user set bar width or offset, // then check whether axis needs expanding - var minPos = Math.min.apply(Math, distinctPositions) - vpad, - maxPos = Math.max.apply(Math, distinctPositions) + vpad, - pMin, pMinOffset, pMinWidth, - pMax, pMaxOffset, pMaxWidth; + var pMin = Math.min.apply(Math, distinctPositions) - vpad, + pMax = Math.max.apply(Math, distinctPositions) + vpad; + for(var i = 0; i < calcTraces.length; i++) { var calcTrace = calcTraces[i], calcTrace0 = calcTrace[0], - fullTrace = calcTrace0.trace, - t = calcTrace0.t, - poffset = t.poffset, - poffsetIsArray = Array.isArray(poffset), - barwidth = t.barwidth, - barwidthIsArray = Array.isArray(barwidth); + fullTrace = calcTrace0.trace; if(fullTrace.width === undefined && fullTrace.offset === undefined) { continue; } + var t = calcTrace0.t, + poffset = t.poffset, + barwidth = t.barwidth, + poffsetIsArray = Array.isArray(poffset), + barwidthIsArray = Array.isArray(barwidth); + for(var j = 0; j < calcTrace.length; j++) { var calcBar = calcTrace[j], calcBarOffset = (poffsetIsArray) ? poffset[j] : poffset, @@ -437,29 +436,12 @@ function updatePositionAxis(gd, pa, sieve, allowMinDtick) { l = p + calcBarOffset, r = l + calcBarWidth; - if(r >= maxPos) { - maxPos = r; - pMax = p; - pMaxOffset = calcBarOffset; - pMaxWidth = calcBarWidth; - } - if(l <= minPos) { - minPos = r; - pMin = p; - pMinOffset = calcBarOffset; - pMinWidth = calcBarWidth; - } + pMin = Math.min(pMin, l); + pMax = Math.max(pMax, r); } } - if(pMin) { - vpad = pMinWidth / 2; - Axes.expand(pa, [pMin + pMinOffset + vpad], {vpad: vpad}); - } - if(pMax) { - vpad = pMaxWidth / 2; - Axes.expand(pa, [pMax + pMaxOffset + vpad], {vpad: vpad}); - } + Axes.expand(pa, [pMin, pMax], {padded: false}); } From b4732daae13477a18863c694bbcf21791eb00e8e Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 24 Oct 2016 10:03:03 +0100 Subject: [PATCH 25/34] bar: interpret trace data as sizes --- src/traces/bar/calc.js | 2 +- src/traces/bar/set_positions.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 1943ffd45cc..22c2a1a9871 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -78,7 +78,7 @@ module.exports = function calc(gd, trace) { // set size for(i = 0; i < cd.length; i++) { if(isNumeric(size[i])) { - cd[i].s = size[i] - cd[i].b; + cd[i].s = size[i]; } } diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 663c9fbcd61..c18eaf0dec6 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -499,7 +499,7 @@ function stackBars(gd, sa, sieve) { if(!isNumeric(bar.s)) continue; // stack current bar and get previous sum - var barBase = sieve.put(bar.p, bar.b + bar.s), + var barBase = sieve.put(bar.p, bar.s), barTop = barBase + bar.s; // store the bar base and top in each calcdata item @@ -538,7 +538,7 @@ function sieveBars(gd, sa, sieve) { for(var j = 0; j < trace.length; j++) { var bar = trace[j]; - if(isNumeric(bar.s)) sieve.put(bar.p, bar.b + bar.s); + if(isNumeric(bar.s)) sieve.put(bar.p, bar.s); } } } @@ -566,7 +566,7 @@ function normalizeBars(gd, sa, sieve) { if(!isNumeric(bar.s)) continue; - var scale = Math.abs(sTop / sieve.get(bar.p, bar.b + bar.s)); + var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); bar.b *= scale; bar.s *= scale; From 7440346b3a9c3a77c06f3176aee106c3dd330fbe Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 24 Oct 2016 20:54:32 +0100 Subject: [PATCH 26/34] bar: add function applyAttributes * Moved shared code between `setOffsetAndWidth` and `setOffsetAndWidthInGroupMode` to handle offset and width attributes into function `applyAttributes`. --- src/traces/bar/set_positions.js | 153 +++++++++++++------------------- 1 file changed, 62 insertions(+), 91 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index c18eaf0dec6..73797579e7b 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -189,7 +189,7 @@ function setOffsetAndWidth(gd, pa, sieve) { bargroupgap = fullLayout.bargroupgap, minDiff = sieve.minDiff, calcTraces = sieve.traces, - i, calcTrace, calcTrace0, fullTrace, + i, calcTrace, calcTrace0, j, calcBar, t; @@ -223,77 +223,7 @@ function setOffsetAndWidth(gd, pa, sieve) { sieve.binWidth = calcTraces[0][0].t.barwidth / 100; // if defined, apply trace offset and width - for(i = 0; i < calcTraces.length; i++) { - calcTrace = calcTraces[i]; - calcTrace0 = calcTrace[0]; - fullTrace = calcTrace0.trace; - t = calcTrace0.t; - - var offset = fullTrace.offset, - initialPoffset = t.poffset, - newPoffset; - if(Array.isArray(offset)) { - // if offset is an array, then clone it into t.poffset. - newPoffset = offset.slice(0, calcTrace.length); - - // guard against non-numeric items - for(j = 0; j < newPoffset.length; j++) { - if(!isNumeric(newPoffset[j])) newPoffset[j] = initialPoffset; - } - - // if the length of the array is too short, - // then extend it with the initial value of t.poffset - for(j = newPoffset.length; j < calcTrace.length; j++) { - newPoffset.push(initialPoffset); - } - - t.poffset = newPoffset; - } - else if(offset !== undefined) { - t.poffset = offset; - } - - var width = fullTrace.width, - initialBarwidth = t.barwidth; - if(Array.isArray(width)) { - // if width is an array, then clone it into t.barwidth. - var newBarwidth = width.slice(0, calcTrace.length); - - // guard against non-numeric items - for(j = 0; j < newBarwidth.length; j++) { - if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth; - } - - // if the length of the array is too short, - // then extend it with the initial value of t.barwidth - for(j = newBarwidth.length; j < calcTrace.length; j++) { - newBarwidth.push(initialBarwidth); - } - - t.barwidth = newBarwidth; - - // if user didn't set offset, - // then correct t.poffset to ensure bars remain centered - if(offset === undefined) { - newPoffset = []; - for(j = 0; j < calcTrace.length; j++) { - newPoffset.push( - initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 - ); - } - t.poffset = newPoffset; - } - } - else if(width !== undefined) { - t.barwidth = width; - - // if user didn't set offset, - // then correct t.poffset to ensure bars remain centered - if(offset === undefined) { - t.poffset = initialPoffset + (initialBarwidth - width) / 2; - } - } - } + applyAttributes(sieve); // update position axes updatePositionAxis(gd, pa, sieve); @@ -309,7 +239,7 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { distinctPositions = sieve.distinctPositions, minDiff = sieve.minDiff, calcTraces = sieve.traces, - i, calcTrace, calcTrace0, fullTrace, + i, calcTrace, calcTrace0, j, calcBar, t; @@ -349,17 +279,55 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { sieve.binWidth = calcTraces[0][0].t.barwidth / 100; // if defined, apply trace width + applyAttributes(sieve); + + // update position axes + updatePositionAxis(gd, pa, sieve, overlap); +} + + +function applyAttributes(sieve) { + var calcTraces = sieve.traces, + i, calcTrace, calcTrace0, fullTrace, + j, + t; + for(i = 0; i < calcTraces.length; i++) { calcTrace = calcTraces[i]; calcTrace0 = calcTrace[0]; fullTrace = calcTrace0.trace; + t = calcTrace0.t; - var width = fullTrace.width; - if(width === undefined) continue; + var offset = fullTrace.offset, + initialPoffset = t.poffset, + newPoffset; + + if(Array.isArray(offset)) { + // if offset is an array, then clone it into t.poffset. + newPoffset = offset.slice(0, calcTrace.length); + + // guard against non-numeric items + for(j = 0; j < newPoffset.length; j++) { + if(!isNumeric(newPoffset[j])) { + newPoffset[j] = initialPoffset; + } + } + + // if the length of the array is too short, + // then extend it with the initial value of t.poffset + for(j = newPoffset.length; j < calcTrace.length; j++) { + newPoffset.push(initialPoffset); + } + + t.poffset = newPoffset; + } + else if(offset !== undefined) { + t.poffset = offset; + } + + var width = fullTrace.width, + initialBarwidth = t.barwidth; - t = calcTrace0.t; - var initialBarwidth = t.barwidth, - initialPoffset = t.poffset; if(Array.isArray(width)) { // if width is an array, then clone it into t.barwidth. var newBarwidth = width.slice(0, calcTrace.length); @@ -377,25 +345,28 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { t.barwidth = newBarwidth; - // correct t.poffset to ensure bars remain centered - var newPoffset = []; - for(j = 0; j < calcTrace.length; j++) { - newPoffset.push( - initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 - ); + // if user didn't set offset, + // then correct t.poffset to ensure bars remain centered + if(offset === undefined) { + newPoffset = []; + for(j = 0; j < calcTrace.length; j++) { + newPoffset.push( + initialPoffset + (initialBarwidth - newBarwidth[j]) / 2 + ); + } + t.poffset = newPoffset; } - t.poffset = newPoffset; } - else { + else if(width !== undefined) { t.barwidth = width; - // correct t.poffset to ensure bars remain centered - t.poffset = initialPoffset + (initialBarwidth - width) / 2; + // if user didn't set offset, + // then correct t.poffset to ensure bars remain centered + if(offset === undefined) { + t.poffset = initialPoffset + (initialBarwidth - width) / 2; + } } } - - // update position axes - updatePositionAxis(gd, pa, sieve, overlap); } From 152fefe0e4a8e0f170366c6214af04cef2962977 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 24 Oct 2016 21:34:15 +0100 Subject: [PATCH 27/34] bar: document purpose of updatePositionAxis --- src/traces/bar/set_positions.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 73797579e7b..cf061c61c5e 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -379,8 +379,12 @@ function updatePositionAxis(gd, pa, sieve, allowMinDtick) { Axes.minDtick(pa, minDiff, distinctPositions0, allowMinDtick); - // if user set bar width or offset, - // then check whether axis needs expanding + // If the user set the bar width or the offset, + // then bars can be shifted away from their positions + // and widths can be larger than minDiff. + // + // Here, we compute pMin and pMax to expand the position axis, + // so that all bars are fully within the axis range. var pMin = Math.min.apply(Math, distinctPositions) - vpad, pMax = Math.max.apply(Math, distinctPositions) + vpad; From a12872d39ed7e37f71ec359f20cf5e1054fcc800 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 24 Oct 2016 09:42:57 +0100 Subject: [PATCH 28/34] test: update bar tests * Test the use of the bar attributes: base, offset and width. --- test/jasmine/tests/bar_test.js | 647 ++++++++++++++++++++++++++++----- 1 file changed, 546 insertions(+), 101 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index d4197bad8ea..df977623846 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1,6 +1,9 @@ var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); +var PlotlyInternal = require('@src/plotly'); +var Axes = PlotlyInternal.Axes; + var Bar = require('@src/traces/bar'); var customMatchers = require('../assets/custom_matchers'); @@ -59,6 +62,25 @@ describe('bar supplyDefaults', function() { supplyDefaults(traceIn, traceOut, defaultColor); expect(traceOut.visible).toBe(false); }); + + it('should not set base, offset or width', function() { + traceIn = { + y: [1, 2, 3] + }; + supplyDefaults(traceIn, traceOut, defaultColor); + expect(traceOut.base).toBeUndefined(); + expect(traceOut.offset).toBeUndefined(); + expect(traceOut.width).toBeUndefined(); + }); + + it('should coerce a non-negative width', function() { + traceIn = { + width: -1, + y: [1, 2, 3] + }; + supplyDefaults(traceIn, traceOut, defaultColor); + expect(traceOut.width).toBeUndefined(); + }); }); describe('heatmap calc / setPositions', function() { @@ -68,64 +90,8 @@ describe('heatmap calc / setPositions', function() { jasmine.addMatchers(customMatchers); }); - function _calc(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; - } - - function assertPtField(calcData, prop, expectation) { - var values = []; - - calcData.forEach(function(calcTrace) { - var vals = calcTrace.map(function(pt) { - return Lib.nestedProperty(pt, prop).get(); - }); - - values.push(vals); - }); - - expect(values).toBeCloseTo2DArray(expectation, undefined, '- field ' + prop); - } - - function assertTraceField(calcData, prop, expectation) { - var values = calcData.map(function(calcTrace) { - return Lib.nestedProperty(calcTrace[0], prop).get(); - }); - - expect(values).toBeCloseToArray(expectation, undefined, '- field ' + prop); - } - it('should fill in calc pt fields (stack case)', function() { - var out = _calc([{ + var gd = mockBarPlot([{ y: [2, 1, 2] }, { y: [3, 1, 2] @@ -135,18 +101,19 @@ describe('heatmap calc / setPositions', function() { barmode: 'stack' }); - assertPtField(out, 'x', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); - assertPtField(out, 'y', [[2, 1, 2], [5, 2, 4], [undefined, undefined, 6]]); - assertPtField(out, 'b', [[0, 0, 0], [2, 1, 2], [0, 0, 4]]); - assertPtField(out, 's', [[2, 1, 2], [3, 1, 2], [undefined, undefined, 2]]); - 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.bargroupwidth', [0.8, 0.8, 0.8]); + var cd = gd.calcdata; + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[2, 1, 2], [5, 2, 4], [undefined, undefined, 6]]); + assertPointField(cd, 'b', [[0, 0, 0], [2, 1, 2], [0, 0, 4]]); + assertPointField(cd, 's', [[2, 1, 2], [3, 1, 2], [undefined, undefined, 2]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertTraceField(cd, 't.barwidth', [0.8, 0.8, 0.8]); + assertTraceField(cd, 't.poffset', [-0.4, -0.4, -0.4]); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8, 0.8]); }); it('should fill in calc pt fields (overlay case)', function() { - var out = _calc([{ + var gd = mockBarPlot([{ y: [2, 1, 2] }, { y: [3, 1, 2] @@ -154,18 +121,19 @@ describe('heatmap calc / setPositions', function() { barmode: 'overlay' }); - assertPtField(out, 'x', [[0, 1, 2], [0, 1, 2]]); - assertPtField(out, 'y', [[2, 1, 2], [3, 1, 2]]); - 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.8, 0.8]); - assertTraceField(out, 't.poffset', [-0.4, -0.4]); - assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); + var cd = gd.calcdata; + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[2, 1, 2], [3, 1, 2]]); + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0]]); + assertPointField(cd, 's', [[2, 1, 2], [3, 1, 2]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2]]); + assertTraceField(cd, 't.barwidth', [0.8, 0.8]); + assertTraceField(cd, 't.poffset', [-0.4, -0.4]); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8]); }); it('should fill in calc pt fields (group case)', function() { - var out = _calc([{ + var gd = mockBarPlot([{ y: [2, 1, 2] }, { y: [3, 1, 2] @@ -175,18 +143,19 @@ describe('heatmap calc / setPositions', function() { bargroupgap: 0.1 }); - assertPtField(out, 'x', [[-0.2, 0.8, 1.8], [0.2, 1.2, 2.2]]); - assertPtField(out, 'y', [[2, 1, 2], [3, 1, 2]]); - 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.36, 0.36]); - assertTraceField(out, 't.poffset', [-0.38, 0.02]); - assertTraceField(out, 't.bargroupwidth', [0.8, 0.8]); + var cd = gd.calcdata; + assertPointField(cd, 'x', [[-0.2, 0.8, 1.8], [0.2, 1.2, 2.2]]); + assertPointField(cd, 'y', [[2, 1, 2], [3, 1, 2]]); + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0]]); + assertPointField(cd, 's', [[2, 1, 2], [3, 1, 2]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2]]); + assertTraceField(cd, 't.barwidth', [0.36, 0.36]); + assertTraceField(cd, 't.poffset', [-0.38, 0.02]); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8]); }); it('should fill in calc pt fields (relative case)', function() { - var out = _calc([{ + var gd = mockBarPlot([{ y: [20, 14, -23] }, { y: [-12, -18, -29] @@ -194,18 +163,19 @@ describe('heatmap calc / setPositions', function() { barmode: 'relative' }); - assertPtField(out, 'x', [[0, 1, 2], [0, 1, 2]]); - assertPtField(out, 'y', [[20, 14, -23], [-12, -18, -52]]); - assertPtField(out, 'b', [[0, 0, 0], [0, 0, -23]]); - assertPtField(out, 's', [[20, 14, -23], [-12, -18, -29]]); - 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.bargroupwidth', [0.8, 0.8]); + var cd = gd.calcdata; + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[20, 14, -23], [-12, -18, -52]]); + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, -23]]); + assertPointField(cd, 's', [[20, 14, -23], [-12, -18, -29]]); + assertPointField(cd, 'p', [[0, 1, 2], [0, 1, 2]]); + assertTraceField(cd, 't.barwidth', [0.8, 0.8]); + assertTraceField(cd, 't.poffset', [-0.4, -0.4]); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8]); }); it('should fill in calc pt fields (relative / percent case)', function() { - var out = _calc([{ + var gd = mockBarPlot([{ x: ['A', 'B', 'C', 'D'], y: [20, 14, 40, -60] }, { @@ -216,13 +186,488 @@ describe('heatmap calc / setPositions', function() { barnorm: 'percent' }); - assertPtField(out, 'x', [[0, 1, 2, 3], [0, 1, 2, 3]]); - assertPtField(out, 'y', [[100, 100, 40, -60], [-100, -100, 100, -100]]); - assertPtField(out, 'b', [[0, 0, 0, 0], [0, 0, 40, -60]]); - assertPtField(out, 's', [[100, 100, 40, -60], [-100, -100, 60, -40]]); - 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.bargroupwidth', [0.8, 0.8]); + var cd = gd.calcdata; + assertPointField(cd, 'x', [[0, 1, 2, 3], [0, 1, 2, 3]]); + assertPointField(cd, 'y', [[100, 100, 40, -60], [-100, -100, 100, -100]]); + assertPointField(cd, 'b', [[0, 0, 0, 0], [0, 0, 40, -60]]); + assertPointField(cd, 's', [[100, 100, 40, -60], [-100, -100, 60, -40]]); + assertPointField(cd, 'p', [[0, 1, 2, 3], [0, 1, 2, 3]]); + assertTraceField(cd, 't.barwidth', [0.8, 0.8]); + assertTraceField(cd, 't.poffset', [-0.4, -0.4]); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8]); + }); +}); + +describe('Bar.calc', function() { + 'use strict'; + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + it('should guard against invalid base items', function() { + var gd = mockBarPlot([{ + base: [null, 1, 2], + y: [1, 2, 3] + }, { + base: [null, 1], + y: [1, 2, 3] + }, { + base: null, + y: [1, 2] + }], { + barmode: 'overlay' + }); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 1, 2], [0, 1, 0], [0, 0]]); }); }); + +describe('Bar.setPositions', function() { + 'use strict'; + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + it('should guard against invalid offset items', function() { + var gd = mockBarPlot([{ + offset: [null, 0, 1], + y: [1, 2, 3] + }, { + offset: [null, 1], + y: [1, 2, 3] + }, { + offset: null, + y: [1] + }], { + bargap: 0.2, + barmode: 'overlay' + }); + + var cd = gd.calcdata; + assertArrayField(cd[0][0], 't.poffset', [-0.4, 0, 1]); + assertArrayField(cd[1][0], 't.poffset', [-0.4, 1, -0.4]); + assertArrayField(cd[2][0], 't.poffset', [-0.4]); + }); + + it('should guard against invalid width items', function() { + var gd = mockBarPlot([{ + width: [null, 1, 0.8], + y: [1, 2, 3] + }, { + width: [null, 1], + y: [1, 2, 3] + }, { + width: null, + y: [1] + }], { + bargap: 0.2, + barmode: 'overlay' + }); + + var cd = gd.calcdata; + assertArrayField(cd[0][0], 't.barwidth', [0.8, 1, 0.8]); + assertArrayField(cd[1][0], 't.barwidth', [0.8, 1, 0.8]); + assertArrayField(cd[2][0], 't.barwidth', [0.8]); + }); + + it('should guard against invalid width items (group case)', function() { + var gd = mockBarPlot([{ + width: [null, 0.1, 0.2], + y: [1, 2, 3] + }, { + width: [null, 0.1], + y: [1, 2, 3] + }, { + width: null, + y: [1] + }], { + bargap: 0, + barmode: 'group' + }); + + var cd = gd.calcdata; + assertArrayField(cd[0][0], 't.barwidth', [0.33, 0.1, 0.2]); + assertArrayField(cd[1][0], 't.barwidth', [0.33, 0.1, 0.33]); + assertArrayField(cd[2][0], 't.barwidth', [0.33]); + }); + + it('should stack vertical and horizontal traces separately', function() { + var gd = mockBarPlot([{ + y: [1, 2, 3] + }, { + y: [10, 20, 30] + }, { + x: [-1, -2, -3] + }, { + x: [-10, -20, -30] + }], { + barmode: 'stack' + }); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [1, 2, 3], [0, 0, 0], [-1, -2, -3]]); + assertPointField(cd, 's', [[1, 2, 3], [10, 20, 30], [-1, -2, -3], [-10, -20, -30]]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2], [-1, -2, -3], [-11, -22, -33]]); + assertPointField(cd, 'y', [[1, 2, 3], [11, 22, 33], [0, 1, 2], [0, 1, 2]]); + }); + + it('should not group traces that set offset', function() { + var gd = mockBarPlot([{ + y: [1, 2, 3] + }, { + y: [10, 20, 30] + }, { + offset: -1, + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'group' + }); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0], [0, 0, 0]]); + assertPointField(cd, 's', [[1, 2, 3], [10, 20, 30], [-1, -2, -3]]); + assertPointField(cd, 'x', [[-0.25, 0.75, 1.75], [0.25, 1.25, 2.25], [0, 1, 2]]); + assertPointField(cd, 'y', [[1, 2, 3], [10, 20, 30], [-1, -2, -3]]); + }); + + it('should not stack traces that set base', function() { + var gd = mockBarPlot([{ + y: [1, 2, 3] + }, { + y: [10, 20, 30] + }, { + base: -1, + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'stack' + }); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [1, 2, 3], [-1, -1, -1]]); + assertPointField(cd, 's', [[1, 2, 3], [10, 20, 30], [-1, -2, -3]]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[1, 2, 3], [11, 22, 33], [-2, -3, -4]]); + }); + + it('should draw traces separately in overlay mode', function() { + var gd = mockBarPlot([{ + y: [1, 2, 3] + }, { + y: [10, 20, 30] + }], { + bargap: 0, + barmode: 'overlay', + barnorm: false + }); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0]]); + assertPointField(cd, 's', [[1, 2, 3], [10, 20, 30]]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[1, 2, 3], [10, 20, 30]]); + }); + + it('should ignore barnorm in overlay mode', function() { + var gd = mockBarPlot([{ + y: [1, 2, 3] + }, { + y: [10, 20, 30] + }], { + bargap: 0, + barmode: 'overlay', + barnorm: 'percent' + }); + + expect(gd._fullLayout.barnorm).toBeUndefined(); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0]]); + assertPointField(cd, 's', [[1, 2, 3], [10, 20, 30]]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[1, 2, 3], [10, 20, 30]]); + }); + + it('should honor barnorm for traces that cannot be grouped', function() { + var gd = mockBarPlot([{ + offset: 0, + y: [1, 2, 3] + }], { + bargap: 0, + barmode: 'group', + barnorm: 'percent' + }); + + expect(gd._fullLayout.barnorm).toBe('percent'); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0]]); + assertPointField(cd, 's', [[100, 100, 100]]); + assertPointField(cd, 'x', [[0, 1, 2]]); + assertPointField(cd, 'y', [[100, 100, 100]]); + }); + + it('should honor barnorm for traces that cannot be stacked', function() { + var gd = mockBarPlot([{ + offset: 0, + y: [1, 2, 3] + }], { + bargap: 0, + barmode: 'stack', + barnorm: 'percent' + }); + + expect(gd._fullLayout.barnorm).toBe('percent'); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0]]); + assertPointField(cd, 's', [[100, 100, 100]]); + assertPointField(cd, 'x', [[0, 1, 2]]); + assertPointField(cd, 'y', [[100, 100, 100]]); + }); + + it('should honor barnorm (group case)', function() { + var gd = mockBarPlot([{ + y: [3, 2, 1] + }, { + y: [1, 2, 3] + }], { + bargap: 0, + barmode: 'group', + barnorm: 'fraction' + }); + + expect(gd._fullLayout.barnorm).toBe('fraction'); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0]]); + assertPointField(cd, 's', [[0.75, 0.50, 0.25], [0.25, 0.50, 0.75]]); + assertPointField(cd, 'x', [[-0.25, 0.75, 1.75], [0.25, 1.25, 2.25]]); + assertPointField(cd, 'y', [[0.75, 0.50, 0.25], [0.25, 0.50, 0.75]]); + }); + + it('should honor barnorm (stack case)', function() { + var gd = mockBarPlot([{ + y: [3, 2, 1] + }, { + y: [1, 2, 3] + }], { + bargap: 0, + barmode: 'stack', + barnorm: 'fraction' + }); + + expect(gd._fullLayout.barnorm).toBe('fraction'); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0, 0, 0], [0.75, 0.50, 0.25]]); + assertPointField(cd, 's', [[0.75, 0.50, 0.25], [0.25, 0.50, 0.75]]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [[0.75, 0.50, 0.25], [1, 1, 1]]); + }); + + it('should honor barnorm (relative case)', function() { + var gd = mockBarPlot([{ + y: [3, 2, 1] + }, { + y: [1, 2, 3] + }, { + y: [-3, -2, -1] + }, { + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'relative', + barnorm: 'fraction' + }); + + expect(gd._fullLayout.barnorm).toBe('fraction'); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [ + [0, 0, 0], [0.75, 0.50, 0.25], + [0, 0, 0], [-0.75, -0.50, -0.25] + ]); + assertPointField(cd, 's', [ + [0.75, 0.50, 0.25], [0.25, 0.50, 0.75], + [-0.75, -0.50, -0.25], [-0.25, -0.50, -0.75], + ]); + assertPointField(cd, 'x', [[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]]); + assertPointField(cd, 'y', [ + [0.75, 0.50, 0.25], [1, 1, 1], + [-0.75, -0.50, -0.25], [-1, -1, -1], + ]); + }); + + it('should expand position axis', function() { + var gd = mockBarPlot([{ + offset: 10, + width: 2, + y: [3, 2, 1] + }, { + offset: -5, + width: 2, + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'overlay', + barnorm: false + }); + + expect(gd._fullLayout.barnorm).toBeUndefined(); + + var xa = gd._fullLayout.xaxis, + ya = gd._fullLayout.yaxis; + expect(Axes.getAutoRange(xa)).toBeCloseToArray([-5, 14], undefined, '(xa.range)'); + expect(Axes.getAutoRange(ya)).toBeCloseToArray([-3.33, 3.33], undefined, '(ya.range)'); + }); + + it('should expand size axis (overlay case)', function() { + var gd = mockBarPlot([{ + base: 7, + y: [3, 2, 1] + }, { + base: 2, + y: [1, 2, 3] + }, { + base: -2, + y: [-3, -2, -1] + }, { + base: -7, + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'overlay', + barnorm: false + }); + + expect(gd._fullLayout.barnorm).toBeUndefined(); + + var xa = gd._fullLayout.xaxis, + ya = gd._fullLayout.yaxis; + expect(Axes.getAutoRange(xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); + expect(Axes.getAutoRange(ya)).toBeCloseToArray([-11.11, 11.11], undefined, '(ya.range)'); + }); + + it('should expand size axis (relative case)', function() { + var gd = mockBarPlot([{ + y: [3, 2, 1] + }, { + y: [1, 2, 3] + }, { + y: [-3, -2, -1] + }, { + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'relative', + barnorm: false + }); + + expect(gd._fullLayout.barnorm).toBe(''); + + var xa = gd._fullLayout.xaxis, + ya = gd._fullLayout.yaxis; + expect(Axes.getAutoRange(xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); + expect(Axes.getAutoRange(ya)).toBeCloseToArray([-4.44, 4.44], undefined, '(ya.range)'); + }); + + it('should expand size axis (barnorm case)', function() { + var gd = mockBarPlot([{ + y: [3, 2, 1] + }, { + y: [1, 2, 3] + }, { + y: [-3, -2, -1] + }, { + y: [-1, -2, -3] + }], { + bargap: 0, + barmode: 'relative', + barnorm: 'fraction' + }); + + expect(gd._fullLayout.barnorm).toBe('fraction'); + + var xa = gd._fullLayout.xaxis, + ya = gd._fullLayout.yaxis; + expect(Axes.getAutoRange(xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); + expect(Axes.getAutoRange(ya)).toBeCloseToArray([-1.11, 1.11], undefined, '(ya.range)'); + }); +}); + +function mockBarPlot(dataWithoutTraceType, layout) { + var traceTemplate = { type: 'bar' }; + + var dataWithTraceType = dataWithoutTraceType.map(function(trace) { + return Lib.extendFlat({}, traceTemplate, trace); + }); + + var gd = { + data: dataWithTraceType, + layout: layout, + calcdata: [] + }; + + // call Bar.supplyDefaults + Plots.supplyDefaults(gd); + + // call Bar.calc + 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 + }; + + // call Bar.setPositions + Bar.setPositions(gd, plotinfo); + + return gd; +} + +function assertArrayField(calcData, prop, expectation) { + // Note that this functions requires to add `customMatchers` to jasmine + // matchers; i.e: `jasmine.addMatchers(customMatchers);`. + var values = Lib.nestedProperty(calcData, prop).get(); + if(!Array.isArray(values)) values = [values]; + + expect(values).toBeCloseToArray(expectation, undefined, '(field ' + prop + ')'); +} + +function assertPointField(calcData, prop, expectation) { + // Note that this functions requires to add `customMatchers` to jasmine + // matchers; i.e: `jasmine.addMatchers(customMatchers);`. + var values = []; + + calcData.forEach(function(calcTrace) { + var vals = calcTrace.map(function(pt) { + return Lib.nestedProperty(pt, prop).get(); + }); + + values.push(vals); + }); + + expect(values).toBeCloseTo2DArray(expectation, undefined, '(field ' + prop + ')'); +} + +function assertTraceField(calcData, prop, expectation) { + // Note that this functions requires to add `customMatchers` to jasmine + // matchers; i.e: `jasmine.addMatchers(customMatchers);`. + var values = calcData.map(function(calcTrace) { + return Lib.nestedProperty(calcTrace[0], prop).get(); + }); + + expect(values).toBeCloseToArray(expectation, undefined, '(field ' + prop + ')'); +} From c3638a6500da713aeed4b88e877cc3b167f2ce88 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 21 Oct 2016 11:13:36 +0100 Subject: [PATCH 29/34] test: add more mock images of bar plots * Added mock images of bar plots using the attributes base, offset and width. --- test/image/baselines/bar_attrs_group.png | Bin 0 -> 9975 bytes test/image/baselines/bar_attrs_group_norm.png | Bin 0 -> 10324 bytes test/image/baselines/bar_attrs_overlay.png | Bin 0 -> 10618 bytes test/image/baselines/bar_attrs_relative.png | Bin 0 -> 11045 bytes test/image/mocks/bar_attrs_group.json | 33 +++++++++++++++ test/image/mocks/bar_attrs_group_norm.json | 19 +++++++++ test/image/mocks/bar_attrs_overlay.json | 38 ++++++++++++++++++ test/image/mocks/bar_attrs_relative.json | 31 ++++++++++++++ 8 files changed, 121 insertions(+) create mode 100644 test/image/baselines/bar_attrs_group.png create mode 100644 test/image/baselines/bar_attrs_group_norm.png create mode 100644 test/image/baselines/bar_attrs_overlay.png create mode 100644 test/image/baselines/bar_attrs_relative.png create mode 100644 test/image/mocks/bar_attrs_group.json create mode 100644 test/image/mocks/bar_attrs_group_norm.json create mode 100644 test/image/mocks/bar_attrs_overlay.json create mode 100644 test/image/mocks/bar_attrs_relative.json diff --git a/test/image/baselines/bar_attrs_group.png b/test/image/baselines/bar_attrs_group.png new file mode 100644 index 0000000000000000000000000000000000000000..1b965bb78937962dfe1f95b51227904ca310280b GIT binary patch literal 9975 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 literal 0 HcmV?d00001 diff --git a/test/image/baselines/bar_attrs_group_norm.png b/test/image/baselines/bar_attrs_group_norm.png new file mode 100644 index 0000000000000000000000000000000000000000..f209c383e666dc85563201dbdb11ec4768443deb GIT binary patch literal 10324 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 U>z`|_IluMIZ?1@2+G;0h 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 literal 0 HcmV?d00001 diff --git a/test/image/mocks/bar_attrs_group.json b/test/image/mocks/bar_attrs_group.json new file mode 100644 index 00000000000..d8f26b1d53a --- /dev/null +++ b/test/image/mocks/bar_attrs_group.json @@ -0,0 +1,33 @@ +{ + "data":[ + { + "base":[0,1,2,3], + "width":0.2, + "y":[1,1,1,1], + "x":[1,2,3,4], + "type":"bar" + }, { + "base":[4,3,2,1], + "y":[-1,-1,-1,-1], + "x":[1,2,3,4], + "type":"bar" + }, { + "base":[0,1,3,2], + "width":0.125, + "y":[1,2,-1,2], + "x":[1,2,3,4], + "type":"bar" + }, { + "base":[1,3,2,4], + "y":[-1,-2,1,-2], + "x":[1,2,3,4], + "type":"bar" + } + ], + "layout":{ + "height":400, + "width":400, + "barmode":"group", + "barnorm":false + } +} diff --git a/test/image/mocks/bar_attrs_group_norm.json b/test/image/mocks/bar_attrs_group_norm.json new file mode 100644 index 00000000000..f08d1d8e8b5 --- /dev/null +++ b/test/image/mocks/bar_attrs_group_norm.json @@ -0,0 +1,19 @@ +{ + "data":[ + { + "base":4, + "x":[3,2,1,0], + "type":"bar" + }, { + "base":[7,6,5,4], + "x":[1,2,3,4], + "type":"bar" + } + ], + "layout":{ + "height":400, + "width":400, + "barmode":"group", + "barnorm":"percent" + } +} diff --git a/test/image/mocks/bar_attrs_overlay.json b/test/image/mocks/bar_attrs_overlay.json new file mode 100644 index 00000000000..5160c620bfb --- /dev/null +++ b/test/image/mocks/bar_attrs_overlay.json @@ -0,0 +1,38 @@ +{ + "data":[ + { + "base":[0,1,2,3], + "width":[1,0.8,0.6,0.4], + "offset":[0,0,-0.2,-0.6], + "y":[1,1,1,1], + "x":[1,2,3,4], + "type":"bar" + }, { + "base":[4,3,2,1], + "width":[0.4,0.6,0.8,1], + "offset":[-0.2,-0.8,-1.2,-1.4], + "y":[-1,-1,-1,-1], + "x":[5,6,7,8], + "type":"bar" + }, { + "base":[0,1,3,2], + "width":1, + "offset":[1.5], + "y":[1,2,-1,2], + "x":[9,10,11,12], + "type":"bar" + }, { + "base":[1,3,2,4], + "y":[-1,-2,1,-2], + "x":[13,14,15,16], + "type":"bar" + } + ], + "layout":{ + "xaxis": {"showgrid":true}, + "height":400, + "width":400, + "barmode":"overlay", + "barnorm":false + } +} diff --git a/test/image/mocks/bar_attrs_relative.json b/test/image/mocks/bar_attrs_relative.json new file mode 100644 index 00000000000..a7e15e0ff96 --- /dev/null +++ b/test/image/mocks/bar_attrs_relative.json @@ -0,0 +1,31 @@ +{ + "data":[ + { + "width":[1,0.8,0.6,0.4], + "y":[1,2,3,4], + "x":[1,2,3,4], + "type":"bar" + }, { + "width":[0.4,0.6,0.8,1], + "y":[3,2,1,0], + "x":[1,2,3,4], + "type":"bar" + }, { + "width":1, + "y":[-1,-3,-2,-4], + "x":[1,2,3,4], + "type":"bar" + }, { + "y":[0,-1,-3,-2], + "x":[1,2,3,4], + "type":"bar" + } + ], + "layout":{ + "xaxis": {"showgrid":true}, + "height":400, + "width":400, + "barmode":"relative", + "barnorm":false + } +} From db7105d8b4c7ac5cb66cd907e0627560e37af78b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 24 Oct 2016 23:46:30 +0100 Subject: [PATCH 30/34] coerce: allow `arrayOk` and `valType: 'any'` * This change is needed so that the attribute `base` in bar traces can accept `number` and `Date` values. --- src/lib/coerce.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 989daa6a331..1b9a60095db 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -228,7 +228,7 @@ exports.valObjects = { any: { description: 'Any type.', requiredOpts: [], - otherOpts: ['dflt', 'values'], + otherOpts: ['dflt', 'values', 'arrayOk'], coerceFunction: function(v, propOut, dflt) { if(v === undefined) propOut.set(dflt); else propOut.set(v); From 339f5c914fe8d5249564b424534ed9ea9fda4fd7 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 25 Oct 2016 20:30:17 +0100 Subject: [PATCH 31/34] test: fix bar tests * Fixed the bar tests that make use of the attribute offset. The values of the bar center in these tests were incorrect. --- test/jasmine/tests/bar_test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index df977623846..a07af3757f8 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -330,7 +330,7 @@ describe('Bar.setPositions', function() { var cd = gd.calcdata; assertPointField(cd, 'b', [[0, 0, 0], [0, 0, 0], [0, 0, 0]]); assertPointField(cd, 's', [[1, 2, 3], [10, 20, 30], [-1, -2, -3]]); - assertPointField(cd, 'x', [[-0.25, 0.75, 1.75], [0.25, 1.25, 2.25], [0, 1, 2]]); + assertPointField(cd, 'x', [[-0.25, 0.75, 1.75], [0.25, 1.25, 2.25], [-0.5, 0.5, 1.5]]); assertPointField(cd, 'y', [[1, 2, 3], [10, 20, 30], [-1, -2, -3]]); }); @@ -407,7 +407,7 @@ describe('Bar.setPositions', function() { var cd = gd.calcdata; assertPointField(cd, 'b', [[0, 0, 0]]); assertPointField(cd, 's', [[100, 100, 100]]); - assertPointField(cd, 'x', [[0, 1, 2]]); + assertPointField(cd, 'x', [[0.5, 1.5, 2.5]]); assertPointField(cd, 'y', [[100, 100, 100]]); }); @@ -426,7 +426,7 @@ describe('Bar.setPositions', function() { var cd = gd.calcdata; assertPointField(cd, 'b', [[0, 0, 0]]); assertPointField(cd, 's', [[100, 100, 100]]); - assertPointField(cd, 'x', [[0, 1, 2]]); + assertPointField(cd, 'x', [[0.5, 1.5, 2.5]]); assertPointField(cd, 'y', [[100, 100, 100]]); }); From cec4ba0a05f1f700d85c496249032c6425f87e06 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 25 Oct 2016 20:33:41 +0100 Subject: [PATCH 32/34] bar: set bar center after applying attributes * Refactored `setWidthAndOffset` and `setWidthAndOffsetInGroupMode` so that the bar center is computed after applying the trace attributes offset and width. --- src/traces/bar/set_positions.js | 51 ++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index cf061c61c5e..fd5edd0487f 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -184,13 +184,11 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces) { function setOffsetAndWidth(gd, pa, sieve) { var fullLayout = gd._fullLayout, - pLetter = getAxisLetter(pa), bargap = fullLayout.bargap, bargroupgap = fullLayout.bargroupgap, minDiff = sieve.minDiff, calcTraces = sieve.traces, i, calcTrace, calcTrace0, - j, calcBar, t; // set bar offsets and widths @@ -199,8 +197,7 @@ function setOffsetAndWidth(gd, pa, sieve) { barWidth = barWidthPlusGap * (1 - bargroupgap); // computer bar group center and bar offset - var offsetFromCenter = -barWidth / 2, - barCenter = 0; + var offsetFromCenter = -barWidth / 2; for(i = 0; i < calcTraces.length; i++) { calcTrace = calcTraces[i]; @@ -211,12 +208,6 @@ function setOffsetAndWidth(gd, pa, sieve) { t.barwidth = barWidth; t.poffset = offsetFromCenter; t.bargroupwidth = barGroupWidth; - - // store the bar center in each calcdata item - for(j = 0; j < calcTrace.length; j++) { - calcBar = calcTrace[j]; - calcBar[pLetter] = calcBar.p + barCenter; - } } // stack bars that only differ by rounding @@ -225,6 +216,9 @@ function setOffsetAndWidth(gd, pa, sieve) { // if defined, apply trace offset and width applyAttributes(sieve); + // store the bar center in each calcdata item + setBarCenter(gd, pa, sieve); + // update position axes updatePositionAxis(gd, pa, sieve); } @@ -232,7 +226,6 @@ function setOffsetAndWidth(gd, pa, sieve) { function setOffsetAndWidthInGroupMode(gd, pa, sieve) { var fullLayout = gd._fullLayout, - pLetter = getAxisLetter(pa), bargap = fullLayout.bargap, bargroupgap = fullLayout.bargroupgap, positions = sieve.positions, @@ -240,7 +233,6 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { minDiff = sieve.minDiff, calcTraces = sieve.traces, i, calcTrace, calcTrace0, - j, calcBar, t; // if there aren't any overlapping positions, @@ -259,20 +251,13 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { // computer bar group center and bar offset var offsetFromCenter = (overlap) ? ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 : - -barWidth / 2, - barCenter = offsetFromCenter + barWidth / 2; + -barWidth / 2; // store bar width and offset for this trace t = calcTrace0.t; t.barwidth = barWidth; t.poffset = offsetFromCenter; t.bargroupwidth = barGroupWidth; - - // store the bar center in each calcdata item - for(j = 0; j < calcTrace.length; j++) { - calcBar = calcTrace[j]; - calcBar[pLetter] = calcBar.p + barCenter; - } } // stack bars that only differ by rounding @@ -281,6 +266,9 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { // if defined, apply trace width applyAttributes(sieve); + // store the bar center in each calcdata item + setBarCenter(gd, pa, sieve); + // update position axes updatePositionAxis(gd, pa, sieve, overlap); } @@ -370,6 +358,29 @@ function applyAttributes(sieve) { } +function setBarCenter(gd, pa, sieve) { + var calcTraces = sieve.traces, + pLetter = getAxisLetter(pa); + + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i], + t = calcTrace[0].t, + poffset = t.poffset, + poffsetIsArray = Array.isArray(poffset), + barwidth = t.barwidth, + barwidthIsArray = Array.isArray(barwidth); + + for(var j = 0; j < calcTrace.length; j++) { + var calcBar = calcTrace[j]; + + calcBar[pLetter] = calcBar.p + + ((poffsetIsArray) ? poffset[j] : poffset) + + ((barwidthIsArray) ? barwidth[j] : barwidth) / 2; + } + } +} + + function updatePositionAxis(gd, pa, sieve, allowMinDtick) { var calcTraces = sieve.traces, distinctPositions = sieve.distinctPositions, From e4f43060a72755d45787c6531a9957276f449f9b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 25 Oct 2016 20:37:35 +0100 Subject: [PATCH 33/34] bar: fix restyle of bar base, width and offset --- src/plot_api/plot_api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index af891ba692d..2a6c0926033 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1267,6 +1267,7 @@ function _restyle(gd, aobj, _traces) { 'x', 'y', 'z', 'a', 'b', 'c', 'open', 'high', 'low', 'close', + 'base', 'width', 'offset', 'xtype', 'x0', 'dx', 'ytype', 'y0', 'dy', 'xaxis', 'yaxis', 'line.width', 'connectgaps', 'transpose', 'zsmooth', From cc69e8fefef8322d00740492dc89605885a5b9cb Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 25 Oct 2016 20:39:07 +0100 Subject: [PATCH 34/34] test: restyle of bar base, width and offset --- test/jasmine/tests/bar_test.js | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index a07af3757f8..533cdddfdde 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1,3 +1,4 @@ +var Plotly = require('@lib/index'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); @@ -6,6 +7,8 @@ var Axes = PlotlyInternal.Axes; var Bar = require('@src/traces/bar'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); var customMatchers = require('../assets/custom_matchers'); describe('bar supplyDefaults', function() { @@ -600,6 +603,80 @@ describe('Bar.setPositions', function() { }); }); +describe('A bar plot', function() { + 'use strict'; + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + afterEach(destroyGraphDiv); + + it('should be able to restyle', function(done) { + var gd = createGraphDiv(), + mock = Lib.extendDeep({}, require('@mocks/bar_attrs_relative')); + + Plotly.plot(gd, mock.data, mock.layout).then(function() { + var cd = gd.calcdata; + assertPointField(cd, 'x', [ + [1, 2, 3, 4], [1, 2, 3, 4], + [1, 2, 3, 4], [1, 2, 3, 4]]); + assertPointField(cd, 'y', [ + [1, 2, 3, 4], [4, 4, 4, 4], + [-1, -3, -2, -4], [4, -4, -5, -6]]); + assertPointField(cd, 'b', [ + [0, 0, 0, 0], [1, 2, 3, 4], + [0, 0, 0, 0], [4, -3, -2, -4]]); + assertPointField(cd, 's', [ + [1, 2, 3, 4], [3, 2, 1, 0], + [-1, -3, -2, -4], [0, -1, -3, -2]]); + assertPointField(cd, 'p', [ + [1, 2, 3, 4], [1, 2, 3, 4], + [1, 2, 3, 4], [1, 2, 3, 4]]); + assertArrayField(cd[0][0], 't.barwidth', [1, 0.8, 0.6, 0.4]); + assertArrayField(cd[1][0], 't.barwidth', [0.4, 0.6, 0.8, 1]); + expect(cd[2][0].t.barwidth).toBe(1); + expect(cd[3][0].t.barwidth).toBe(0.8); + assertArrayField(cd[0][0], 't.poffset', [-0.5, -0.4, -0.3, -0.2]); + assertArrayField(cd[1][0], 't.poffset', [-0.2, -0.3, -0.4, -0.5]); + expect(cd[2][0].t.poffset).toBe(-0.5); + expect(cd[3][0].t.poffset).toBe(-0.4); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8, 0.8, 0.8]); + + return Plotly.restyle(gd, 'offset', 0); + }).then(function() { + var cd = gd.calcdata; + assertPointField(cd, 'x', [ + [1.5, 2.4, 3.3, 4.2], [1.2, 2.3, 3.4, 4.5], + [1.5, 2.5, 3.5, 4.5], [1.4, 2.4, 3.4, 4.4]]); + assertPointField(cd, 'y', [ + [1, 2, 3, 4], [4, 4, 4, 4], + [-1, -3, -2, -4], [4, -4, -5, -6]]); + assertPointField(cd, 'b', [ + [0, 0, 0, 0], [1, 2, 3, 4], + [0, 0, 0, 0], [4, -3, -2, -4]]); + assertPointField(cd, 's', [ + [1, 2, 3, 4], [3, 2, 1, 0], + [-1, -3, -2, -4], [0, -1, -3, -2]]); + assertPointField(cd, 'p', [ + [1, 2, 3, 4], [1, 2, 3, 4], + [1, 2, 3, 4], [1, 2, 3, 4]]); + assertArrayField(cd[0][0], 't.barwidth', [1, 0.8, 0.6, 0.4]); + assertArrayField(cd[1][0], 't.barwidth', [0.4, 0.6, 0.8, 1]); + expect(cd[2][0].t.barwidth).toBe(1); + expect(cd[3][0].t.barwidth).toBe(0.8); + expect(cd[0][0].t.poffset).toBe(0); + expect(cd[1][0].t.poffset).toBe(0); + expect(cd[2][0].t.poffset).toBe(0); + expect(cd[3][0].t.poffset).toBe(0); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8, 0.8, 0.8]); + + done(); + }); + }); +}); + + function mockBarPlot(dataWithoutTraceType, layout) { var traceTemplate = { type: 'bar' };