From b82190d3daa39b716fd1b424d5d63dc292a38432 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 15 Nov 2016 12:48:01 -0500 Subject: [PATCH 1/3] make histogram bins work with date strings --- src/plots/cartesian/axes.js | 10 ++-- src/plots/cartesian/set_convert.js | 3 ++ src/traces/heatmap/calc.js | 7 ++- src/traces/histogram/attributes.js | 10 ++-- src/traces/histogram/bin_defaults.js | 19 +++++--- src/traces/histogram/calc.js | 10 ++-- src/traces/histogram/clean_bins.js | 73 ++++++++++++++++++++++++++++ src/traces/histogram2d/calc.js | 40 +++++++++------ test/image/mocks/date_histogram.json | 4 +- 9 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 src/traces/histogram/clean_bins.js diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index b3dbcc63626..27359e06df9 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -518,7 +518,8 @@ axes.autoBin = function(data, ax, nbins, is2d) { if(ax.type === 'log') { dummyax = { type: 'linear', - range: [datamin, datamax] + range: [datamin, datamax], + r2l: Number }; } else { @@ -526,7 +527,8 @@ axes.autoBin = function(data, ax, nbins, is2d) { type: ax.type, // conversion below would be ax.c2r but that's only different from l2r // for log, and this is the only place (so far?) we would want c2r. - range: [datamin, datamax].map(ax.l2r) + range: [datamin, datamax].map(ax.l2r), + r2l: ax.r2l }; } @@ -593,8 +595,8 @@ axes.autoBin = function(data, ax, nbins, is2d) { } return { - start: binstart, - end: binend, + start: ax.c2r(binstart), + end: ax.c2r(binend), size: dummyax.dtick }; }; diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 534e380c7a2..e4fae71290f 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -237,6 +237,9 @@ module.exports = function setConvert(ax) { ax.r2p = function(v, clip) { return ax.l2p(ax.r2l(v, clip)); }; ax.p2r = function(px) { return ax.l2r(ax.p2l(px)); }; + ax.r2c = function(v) { return ax.l2c(ax.r2l(v)); }; + ax.c2r = function(v) { return ax.l2r(ax.c2l(v)); }; + if(['linear', 'log', '-'].indexOf(ax.type) !== -1) { ax.c2d = num; ax.d2c = Lib.cleanNumber; diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js index ba9aeee2c0a..29d878f5aee 100644 --- a/src/traces/heatmap/calc.js +++ b/src/traces/heatmap/calc.js @@ -108,9 +108,9 @@ module.exports = function calc(gd, trace) { // create arrays of brick boundaries, to be used by autorange and heatmap.plot var xlen = maxRowLength(z), - xIn = trace.xtype === 'scaled' ? '' : trace.x, + xIn = trace.xtype === 'scaled' ? '' : x, xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa), - yIn = trace.ytype === 'scaled' ? '' : trace.y, + yIn = trace.ytype === 'scaled' ? '' : y, yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya); // handled in gl2d convert step @@ -180,7 +180,6 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { var isArrayOfTwoItemsOrMore = Array.isArray(arrayIn) && arrayIn.length > 1; if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) { - arrayIn = arrayIn.map(ax.d2c); var len = arrayIn.length; // given vals are brick centers @@ -223,7 +222,7 @@ function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { else { dv = dvIn || 1; - if(isHist || ax.type === 'category') v0 = v0In || 0; + if(isHist || ax.type === 'category') v0 = ax.r2c(v0In) || 0; else if(Array.isArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0]; else if(v0In === undefined) v0 = 0; else v0 = ax.d2c(v0In); diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 757c923c190..1cce4a02457 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -73,7 +73,7 @@ module.exports = { autobinx: { valType: 'boolean', - dflt: true, + dflt: null, role: 'style', description: [ 'Determines whether or not the x axis bin attributes are picked', @@ -97,7 +97,7 @@ module.exports = { autobiny: { valType: 'boolean', - dflt: true, + dflt: null, role: 'style', description: [ 'Determines whether or not the y axis bin attributes are picked', @@ -135,7 +135,7 @@ module.exports = { function makeBinsAttr(axLetter) { return { start: { - valType: 'number', + valType: 'any', // for date axes dflt: null, role: 'style', description: [ @@ -144,7 +144,7 @@ function makeBinsAttr(axLetter) { ].join(' ') }, end: { - valType: 'number', + valType: 'any', // for date axes dflt: null, role: 'style', description: [ @@ -154,7 +154,7 @@ function makeBinsAttr(axLetter) { }, size: { valType: 'any', // for date axes - dflt: 1, + dflt: null, role: 'style', description: [ 'Sets the step in-between value each', axLetter, diff --git a/src/traces/histogram/bin_defaults.js b/src/traces/histogram/bin_defaults.js index 3c588989b9a..cbc53f03377 100644 --- a/src/traces/histogram/bin_defaults.js +++ b/src/traces/histogram/bin_defaults.js @@ -14,14 +14,17 @@ module.exports = function handleBinDefaults(traceIn, traceOut, coerce, binDirect coerce('histnorm'); binDirections.forEach(function(binDirection) { - // data being binned - note that even though it's a little weird, - // it's possible to have bins without data, if there's inferred data - var binstrt = coerce(binDirection + 'bins.start'), - binend = coerce(binDirection + 'bins.end'), - autobin = coerce('autobin' + binDirection, !(binstrt && binend)); - - if(autobin) coerce('nbins' + binDirection); - else coerce(binDirection + 'bins.size'); + /* + * Because date axes have string values for start and end, + * and string options for size, we cannot validate these attributes + * now. We will do this during calc (immediately prior to binning) + * in ./clean_bins, and push the cleaned values back to _fullData. + */ + coerce(binDirection + 'bins.start'); + coerce(binDirection + 'bins.end'); + coerce(binDirection + 'bins.size'); + coerce('autobin' + binDirection); + coerce('nbins' + binDirection); }); return traceOut; diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index 2053dc36321..5b43a8ad36c 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -17,6 +17,7 @@ var Axes = require('../../plots/cartesian/axes'); var binFunctions = require('./bin_functions'); var normFunctions = require('./norm_functions'); var doAvg = require('./average'); +var cleanBins = require('./clean_bins'); module.exports = function calc(gd, trace) { @@ -33,6 +34,8 @@ module.exports = function calc(gd, trace) { maindata = trace.orientation === 'h' ? 'y' : 'x', counterdata = {x: 'y', y: 'x'}[maindata]; + cleanBins(trace, pa, maindata); + // prepare the raw data var pos0 = pa.makeCalcdata(trace, maindata); // calculate the bins @@ -71,10 +74,11 @@ module.exports = function calc(gd, trace) { // create the bins (and any extra arrays needed) // assume more than 5000 bins is an error, so we don't crash the browser - i = binspec.start; + i = pa.r2c(binspec.start); + // decrease end a little in case of rounding errors - binend = binspec.end + - (binspec.start - Axes.tickIncrement(binspec.start, binspec.size)) / 1e6; + binend = pa.r2c(binspec.end) + (i - Axes.tickIncrement(i, binspec.size)) / 1e6; + while(i < binend && pos.length < 5000) { i2 = Axes.tickIncrement(i, binspec.size); pos.push((i + i2) / 2); diff --git a/src/traces/histogram/clean_bins.js b/src/traces/histogram/clean_bins.js new file mode 100644 index 00000000000..d6c4d00e5d7 --- /dev/null +++ b/src/traces/histogram/clean_bins.js @@ -0,0 +1,73 @@ +/** +* 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'; +var isNumeric = require('fast-isnumeric'); +var cleanDate = require('../../lib').cleanDate; +var ONEDAY = require('../../constants/numerical').ONEDAY; + +/* + * cleanBins: validate attributes autobin[xy] and [xy]bins.(start, end, size) + * Mutates trace so all these attributes are valid. + * + * Normally this kind of thing would happen during supplyDefaults, but + * in this case we need to know the axis type, and axis type isn't set until + * after trace supplyDefaults are completed. So this gets called during the + * calc step, when data are inserted into bins. + */ +module.exports = function cleanBins(trace, ax, binDirection) { + var axType = ax.type, + binAttr = binDirection + 'bins', + bins = trace[binAttr]; + + if(!bins) bins = trace[binAttr] = {}; + + var cleanBound = (axType === 'date') ? + function(v) { return (v || v === 0) ? cleanDate(v) : null; } : + function(v) { return isNumeric(v) ? Number(v) : null; }; + + bins.start = cleanBound(bins.start); + bins.end = cleanBound(bins.end); + + // logic for bin size is very similar to dtick (cartesian/tick_value_defaults) + // but without the extra string options for log axes + // ie the only strings we accept are M for months + var sizeDflt = (axType === 'date') ? ONEDAY : 1, + binSize = bins.size; + + if(isNumeric(binSize)) { + bins.size = (binSize > 0) ? Number(binSize) : sizeDflt; + } + else if(typeof binSize !== 'string') { + bins.size = sizeDflt; + } + else { + // date special case: "M" gives bins every (integer) n months + var prefix = binSize.charAt(0), + sizeNum = binSize.substr(1); + + sizeNum = isNumeric(sizeNum) ? Number(sizeNum) : 0; + if((sizeNum <= 0) || !( + axType === 'date' && prefix === 'M' && sizeNum === Math.round(sizeNum) + )) { + bins.size = sizeDflt; + } + } + + var autoBinAttr = 'autobin' + binDirection; + + if(typeof trace[autoBinAttr] !== 'boolean') { + trace[autoBinAttr] = !( + (bins.start || bins.start === 0) && + (bins.end || bins.end === 0) + ); + } + + if(!trace[autoBinAttr]) delete trace['nbins' + binDirection]; +}; diff --git a/src/traces/histogram2d/calc.js b/src/traces/histogram2d/calc.js index b6d24535930..79a42406f61 100644 --- a/src/traces/histogram2d/calc.js +++ b/src/traces/histogram2d/calc.js @@ -15,6 +15,7 @@ var Axes = require('../../plots/cartesian/axes'); var binFunctions = require('../histogram/bin_functions'); var normFunctions = require('../histogram/norm_functions'); var doAvg = require('../histogram/average'); +var cleanBins = require('../histogram/clean_bins'); module.exports = function calc(gd, trace) { @@ -29,6 +30,9 @@ module.exports = function calc(gd, trace) { z, i; + cleanBins(trace, xa, 'x'); + cleanBins(trace, ya, 'y'); + var serieslen = Math.min(x.length, y.length); if(x.length > serieslen) x.splice(serieslen, x.length - serieslen); if(y.length > serieslen) y.splice(serieslen, y.length - serieslen); @@ -38,8 +42,10 @@ module.exports = function calc(gd, trace) { if(trace.autobinx || !('xbins' in trace)) { trace.xbins = Axes.autoBin(x, xa, trace.nbinsx, '2d'); if(trace.type === 'histogram2dcontour') { - trace.xbins.start -= trace.xbins.size; - trace.xbins.end += trace.xbins.size; + // the "true" last argument reverses the tick direction (which we can't + // just do with a minus sign because of month bins) + trace.xbins.start = xa.c2r(Axes.tickIncrement(xa.r2c(trace.xbins.start), trace.xbins.size, true)); + trace.xbins.end = xa.c2r(Axes.tickIncrement(xa.r2c(trace.xbins.end), trace.xbins.size)); } // copy bin info back to the source data. @@ -48,8 +54,8 @@ module.exports = function calc(gd, trace) { if(trace.autobiny || !('ybins' in trace)) { trace.ybins = Axes.autoBin(y, ya, trace.nbinsy, '2d'); if(trace.type === 'histogram2dcontour') { - trace.ybins.start -= trace.ybins.size; - trace.ybins.end += trace.ybins.size; + trace.ybins.start = ya.c2r(Axes.tickIncrement(ya.r2c(trace.ybins.start), trace.ybins.size, true)); + trace.ybins.end = ya.c2r(Axes.tickIncrement(ya.r2c(trace.ybins.end), trace.ybins.size)); } trace._input.ybins = trace.ybins; } @@ -91,11 +97,11 @@ module.exports = function calc(gd, trace) { // decrease end a little in case of rounding errors var binspec = trace.xbins, - binend = binspec.end + - (binspec.start - Axes.tickIncrement(binspec.start, binspec.size)) / 1e6; + binStart = xa.r2c(binspec.start), + binEnd = xa.r2c(binspec.end) + + (binStart - Axes.tickIncrement(binStart, binspec.size)) / 1e6; - for(i = binspec.start; i < binend; - i = Axes.tickIncrement(i, binspec.size)) { + for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size)) { onecol.push(sizeinit); if(Array.isArray(xbins)) xbins.push(i); if(doavg) zerocol.push(0); @@ -104,15 +110,16 @@ module.exports = function calc(gd, trace) { var nx = onecol.length; x0 = trace.xbins.start; - dx = (i - x0) / nx; - x0 += dx / 2; + var x0c = xa.r2c(x0); + dx = (i - x0c) / nx; + x0 = xa.c2r(x0c + dx / 2); binspec = trace.ybins; - binend = binspec.end + - (binspec.start - Axes.tickIncrement(binspec.start, binspec.size)) / 1e6; + binStart = ya.r2c(binspec.start); + binEnd = ya.r2c(binspec.end) + + (binStart - Axes.tickIncrement(binStart, binspec.size)) / 1e6; - for(i = binspec.start; i < binend; - i = Axes.tickIncrement(i, binspec.size)) { + for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size)) { z.push(onecol.concat()); if(Array.isArray(ybins)) ybins.push(i); if(doavg) counts.push(zerocol.concat()); @@ -121,8 +128,9 @@ module.exports = function calc(gd, trace) { var ny = z.length; y0 = trace.ybins.start; - dy = (i - y0) / ny; - y0 += dy / 2; + var y0c = ya.r2c(y0); + dy = (i - y0c) / ny; + y0 = ya.c2r(y0c + dy / 2); if(densitynorm) { xinc = onecol.map(function(v, i) { diff --git a/test/image/mocks/date_histogram.json b/test/image/mocks/date_histogram.json index 90d4daf6a3d..9466c5f4213 100644 --- a/test/image/mocks/date_histogram.json +++ b/test/image/mocks/date_histogram.json @@ -13,8 +13,8 @@ "autobinx": false, "nbinsx": 3, "xbins": { - "start": 1325394000000, - "end": 1333252800000, + "start": "2012-01-01", + "end": "2012-04-01", "size": "M1" }, "autobiny": true, From 128115b9858f0aecf5558b5868661763fb07905f Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 15 Nov 2016 14:38:46 -0500 Subject: [PATCH 2/3] update date histogram mock and image to be immune to edge effects --- test/image/baselines/date_histogram.png | Bin 21878 -> 16631 bytes test/image/mocks/date_histogram.json | 113 ++---------------------- 2 files changed, 7 insertions(+), 106 deletions(-) diff --git a/test/image/baselines/date_histogram.png b/test/image/baselines/date_histogram.png index 9d38b322d04af7fab309e5433909ba9a96f847a6..e6b6bed8a9a4bdeaee23caa7ed054514272663a4 100644 GIT binary patch literal 16631 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 Date: Tue, 15 Nov 2016 14:51:43 -0500 Subject: [PATCH 3/3] update histogram supplyDefaults test because it's not doing as much --- test/jasmine/tests/histogram_test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index 7dea7663ba5..3a441fbb7a0 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -84,7 +84,10 @@ describe('Test histogram', function() { }); - it('should set autobinx to false if xbins is supplied and true if not', function() { + // coercing bin attributes got moved to calc because it needs + // axis type - so here we just test that it's NOT happening + + it('should not coerce autobinx regardless of xbins', function() { traceIn = { x: [1, 2, 2], xbins: { @@ -94,16 +97,16 @@ describe('Test histogram', function() { } }; supplyDefaults(traceIn, traceOut); - expect(traceOut.autobinx).toBe(false); + expect(traceOut.autobinx).toBeUndefined(); traceIn = { x: [1, 2, 2] }; supplyDefaults(traceIn, traceOut); - expect(traceOut.autobinx).toBe(true); + expect(traceOut.autobinx).toBeUndefined(); }); - it('should set autobiny to false if ybins is supplied and true if not', function() { + it('should not coerce autobiny regardless of ybins', function() { traceIn = { y: [1, 2, 2], ybins: { @@ -113,13 +116,13 @@ describe('Test histogram', function() { } }; supplyDefaults(traceIn, traceOut); - expect(traceOut.autobiny).toBe(false); + expect(traceOut.autobiny).toBeUndefined(); traceIn = { y: [1, 2, 2] }; supplyDefaults(traceIn, traceOut); - expect(traceOut.autobiny).toBe(true); + expect(traceOut.autobiny).toBeUndefined(); }); });