@@ -19,6 +19,7 @@ var binFunctions = require('./bin_functions');
19
19
var normFunctions = require ( './norm_functions' ) ;
20
20
var doAvg = require ( './average' ) ;
21
21
var cleanBins = require ( './clean_bins' ) ;
22
+ var oneMonth = require ( '../../constants/numerical' ) . ONEAVGMONTH ;
22
23
23
24
24
25
module . exports = function calc ( gd , trace ) {
@@ -27,60 +28,35 @@ module.exports = function calc(gd, trace) {
27
28
28
29
// depending on orientation, set position and size axes and data ranges
29
30
// note: this logic for choosing orientation is duplicated in graph_obj->setstyles
30
- var pos = [ ] ,
31
- size = [ ] ,
32
- i ,
33
- pa = Axes . getFromId ( gd ,
34
- trace . orientation === 'h' ? ( trace . yaxis || 'y' ) : ( trace . xaxis || 'x' ) ) ,
35
- maindata = trace . orientation === 'h' ? 'y' : 'x' ,
36
- counterdata = { x : 'y' , y : 'x' } [ maindata ] ,
37
- calendar = trace [ maindata + 'calendar' ] ,
38
- cumulativeSpec = trace . cumulative ;
31
+ var pos = [ ] ;
32
+ var size = [ ] ;
33
+ var pa = Axes . getFromId ( gd , trace . orientation === 'h' ?
34
+ ( trace . yaxis || 'y' ) : ( trace . xaxis || 'x' ) ) ;
35
+ var maindata = trace . orientation === 'h' ? 'y' : 'x' ;
36
+ var counterdata = { x : 'y' , y : 'x' } [ maindata ] ;
37
+ var calendar = trace [ maindata + 'calendar' ] ;
38
+ var cumulativeSpec = trace . cumulative ;
39
+ var i ;
39
40
40
41
cleanBins ( trace , pa , maindata ) ;
41
42
42
- // prepare the raw data
43
- var pos0 = pa . makeCalcdata ( trace , maindata ) ;
43
+ var binspec = calcAllAutoBins ( gd , trace , pa , maindata ) ;
44
44
45
- // calculate the bins
46
- var binAttr = maindata + 'bins' ;
47
- var autoBinAttr = 'autobin' + maindata ;
48
- var binspec = trace [ binAttr ] ;
49
- if ( ( trace [ autoBinAttr ] !== false ) || ! binspec ||
50
- binspec . start === null || binspec . end === null ) {
51
- binspec = Axes . autoBin ( pos0 , pa , trace [ 'nbins' + maindata ] , false , calendar ) ;
52
-
53
- // adjust for CDF edge cases
54
- if ( cumulativeSpec . enabled && ( cumulativeSpec . currentbin !== 'include' ) ) {
55
- if ( cumulativeSpec . direction === 'decreasing' ) {
56
- binspec . start = pa . c2r ( pa . r2c ( binspec . start ) - binspec . size ) ;
57
- }
58
- else {
59
- binspec . end = pa . c2r ( pa . r2c ( binspec . end ) + binspec . size ) ;
60
- }
61
- }
45
+ // the raw data was prepared in calcAllAutoBins (during the first trace in
46
+ // this group) and stashed. Pull it out and drop the stash
47
+ var pos0 = trace . _pos0 ;
48
+ delete trace . _pos0 ;
62
49
63
- // copy bin info back to the source and full data.
64
- trace . _input [ binAttr ] = trace [ binAttr ] = binspec ;
65
- // note that it's possible to get here with an explicit autobin: false
66
- // if the bins were not specified.
67
- // in that case this will remain in the trace, so that future updates
68
- // which would change the autobinning will not do so.
69
- trace . _input [ autoBinAttr ] = trace [ autoBinAttr ] ;
70
- }
71
-
72
- var nonuniformBins = typeof binspec . size === 'string' ,
73
- bins = nonuniformBins ? [ ] : binspec ,
74
- // make the empty bin array
75
- i2 ,
76
- binend ,
77
- n ,
78
- inc = [ ] ,
79
- counts = [ ] ,
80
- total = 0 ,
81
- norm = trace . histnorm ,
82
- func = trace . histfunc ,
83
- densitynorm = norm . indexOf ( 'density' ) !== - 1 ;
50
+ var nonuniformBins = typeof binspec . size === 'string' ;
51
+ var bins = nonuniformBins ? [ ] : binspec ;
52
+ // make the empty bin array
53
+ var inc = [ ] ;
54
+ var counts = [ ] ;
55
+ var total = 0 ;
56
+ var norm = trace . histnorm ;
57
+ var func = trace . histfunc ;
58
+ var densitynorm = norm . indexOf ( 'density' ) !== - 1 ;
59
+ var i2 , binend , n ;
84
60
85
61
if ( cumulativeSpec . enabled && densitynorm ) {
86
62
// we treat "cumulative" like it means "integral" if you use a density norm,
@@ -89,13 +65,13 @@ module.exports = function calc(gd, trace) {
89
65
densitynorm = false ;
90
66
}
91
67
92
- var extremefunc = func === 'max' || func === 'min' ,
93
- sizeinit = extremefunc ? null : 0 ,
94
- binfunc = binFunctions . count ,
95
- normfunc = normFunctions [ norm ] ,
96
- doavg = false ,
97
- pr2c = function ( v ) { return pa . r2c ( v , 0 , calendar ) ; } ,
98
- rawCounterData ;
68
+ var extremefunc = func === 'max' || func === 'min' ;
69
+ var sizeinit = extremefunc ? null : 0 ;
70
+ var binfunc = binFunctions . count ;
71
+ var normfunc = normFunctions [ norm ] ;
72
+ var doavg = false ;
73
+ var pr2c = function ( v ) { return pa . r2c ( v , 0 , calendar ) ; } ;
74
+ var rawCounterData ;
99
75
100
76
if ( Array . isArray ( trace [ counterdata ] ) && func !== 'count' ) {
101
77
rawCounterData = trace [ counterdata ] ;
@@ -104,7 +80,7 @@ module.exports = function calc(gd, trace) {
104
80
}
105
81
106
82
// create the bins (and any extra arrays needed)
107
- // assume more than 5000 bins is an error, so we don't crash the browser
83
+ // assume more than 1e6 bins is an error, so we don't crash the browser
108
84
i = pr2c ( binspec . start ) ;
109
85
110
86
// decrease end a little in case of rounding errors
@@ -150,10 +126,11 @@ module.exports = function calc(gd, trace) {
150
126
if ( cumulativeSpec . enabled ) cdf ( size , cumulativeSpec . direction , cumulativeSpec . currentbin ) ;
151
127
152
128
153
- var serieslen = Math . min ( pos . length , size . length ) ,
154
- cd = [ ] ,
155
- firstNonzero = 0 ,
156
- lastNonzero = serieslen - 1 ;
129
+ var serieslen = Math . min ( pos . length , size . length ) ;
130
+ var cd = [ ] ;
131
+ var firstNonzero = 0 ;
132
+ var lastNonzero = serieslen - 1 ;
133
+
157
134
// look for empty bins at the ends to remove, so autoscale omits them
158
135
for ( i = 0 ; i < serieslen ; i ++ ) {
159
136
if ( size [ i ] ) {
@@ -180,10 +157,181 @@ module.exports = function calc(gd, trace) {
180
157
return cd ;
181
158
} ;
182
159
160
+ /*
161
+ * calcAllAutoBins: we want all histograms on the same axes to share bin specs
162
+ * if they're grouped or stacked. If the user has explicitly specified differing
163
+ * bin specs, there's nothing we can do, but if possible we will try to use the
164
+ * smallest bins of any of the auto values for all histograms grouped/stacked
165
+ * together.
166
+ */
167
+ function calcAllAutoBins ( gd , trace , pa , maindata ) {
168
+ var binAttr = maindata + 'bins' ;
169
+
170
+ // all but the first trace in this group has already been marked finished
171
+ // clear this flag, so next time we run calc we will run autobin again
172
+ if ( trace . _autoBinFinished ) {
173
+ delete trace . _autoBinFinished ;
174
+
175
+ return trace [ binAttr ] ;
176
+ }
177
+
178
+ // must be the first trace in the group - do the autobinning on them all
179
+ var traceGroup = getConnectedHistograms ( gd , trace ) ;
180
+ var autoBinnedTraces = [ ] ;
181
+
182
+ var minSize = Infinity ;
183
+ var minStart = Infinity ;
184
+ var maxEnd = - Infinity ;
185
+
186
+ var autoBinAttr = 'autobin' + maindata ;
187
+ var i , tracei , calendar , firstManual ;
188
+
189
+
190
+ for ( i = 0 ; i < traceGroup . length ; i ++ ) {
191
+ tracei = traceGroup [ i ] ;
192
+
193
+ // stash pos0 on the trace so we don't need to duplicate this
194
+ // in the main body of calc
195
+ var pos0 = tracei . _pos0 = pa . makeCalcdata ( tracei , maindata ) ;
196
+ var binspec = tracei [ binAttr ] ;
197
+
198
+ if ( ( tracei [ autoBinAttr ] ) || ! binspec ||
199
+ binspec . start === null || binspec . end === null ) {
200
+ calendar = tracei [ maindata + 'calendar' ] ;
201
+ var cumulativeSpec = tracei . cumulative ;
202
+
203
+ binspec = Axes . autoBin ( pos0 , pa , tracei [ 'nbins' + maindata ] , false , calendar ) ;
204
+
205
+ // adjust for CDF edge cases
206
+ if ( cumulativeSpec . enabled && ( cumulativeSpec . currentbin !== 'include' ) ) {
207
+ if ( cumulativeSpec . direction === 'decreasing' ) {
208
+ minStart = Math . min ( minStart , pa . r2c ( binspec . start , 0 , calendar ) - binspec . size ) ;
209
+ }
210
+ else {
211
+ maxEnd = Math . max ( maxEnd , pa . r2c ( binspec . end , 0 , calendar ) + binspec . size ) ;
212
+ }
213
+ }
214
+
215
+ // note that it's possible to get here with an explicit autobin: false
216
+ // if the bins were not specified. mark this trace for followup
217
+ autoBinnedTraces . push ( tracei ) ;
218
+ }
219
+ else if ( ! firstManual ) {
220
+ // Remember the first manually set binspec. We'll try to be extra
221
+ // accommodating of this one, so other bins line up with these
222
+ // if there's more than one manual bin set and they're mutually inconsistent,
223
+ // then there's not much we can do...
224
+ firstManual = {
225
+ size : binspec . size ,
226
+ start : pa . r2c ( binspec . start , 0 , calendar ) ,
227
+ end : pa . r2c ( binspec . end , 0 , calendar )
228
+ } ;
229
+ }
230
+
231
+ // Even non-autobinned traces get included here, so we get the greatest extent
232
+ // and minimum bin size of them all.
233
+ // But manually binned traces won't be adjusted, even if the auto values
234
+ // are inconsistent with the manual ones (or the manual ones are inconsistent
235
+ // with each other).
236
+ //
237
+ // TODO: there's probably a weird case here where a larger bin pushes the
238
+ // start/end out, then it gets shrunk and doesn't make sense with the smaller bin.
239
+ // Need to look for cases like this and see if the results are acceptable
240
+ // or we need to think harder about it.
241
+ minSize = getMinSize ( minSize , binspec . size ) ;
242
+ minStart = Math . min ( minStart , pa . r2c ( binspec . start , 0 , calendar ) ) ;
243
+ maxEnd = Math . max ( maxEnd , pa . r2c ( binspec . end , 0 , calendar ) ) ;
244
+
245
+ // add the flag that lets us abort autobin on later traces
246
+ if ( i ) trace . _autoBinFinished = 1 ;
247
+ }
248
+
249
+ // do what we can to match the auto bins to the first manual bins
250
+ // but only if sizes are all numeric
251
+ if ( firstManual && isNumeric ( firstManual . size ) && isNumeric ( minSize ) ) {
252
+ // first need to ensure the bin size is the same as or an integer fraction
253
+ // of the first manual bin
254
+ // allow the bin size to increase just under the autobin step size to match,
255
+ // (which is a factor of 2 or 2.5) otherwise shrink it
256
+ if ( minSize > firstManual . size / 1.9 ) minSize = firstManual . size ;
257
+ else minSize = firstManual . size / Math . ceil ( firstManual . size / minSize ) ;
258
+
259
+ // now decrease minStart if needed to make the bin centers line up
260
+ var adjustedFirstStart = firstManual . start + ( firstManual . size - minSize ) / 2 ;
261
+ minStart = adjustedFirstStart - minSize * Math . ceil ( ( adjustedFirstStart - minStart ) / minSize ) ;
262
+ }
263
+
264
+ // now go back to the autobinned traces and update their bin specs with the final values
265
+ for ( i = 0 ; i < autoBinnedTraces . length ; i ++ ) {
266
+ tracei = autoBinnedTraces [ i ] ;
267
+ calendar = tracei [ maindata + 'calendar' ] ;
268
+
269
+ tracei . _input [ binAttr ] = tracei [ binAttr ] = {
270
+ start : pa . c2r ( minStart , 0 , calendar ) ,
271
+ end : pa . c2r ( maxEnd , 0 , calendar ) ,
272
+ size : minSize
273
+ } ;
274
+
275
+ // note that it's possible to get here with an explicit autobin: false
276
+ // if the bins were not specified.
277
+ // in that case this will remain in the trace, so that future updates
278
+ // which would change the autobinning will not do so.
279
+ tracei . _input [ autoBinAttr ] = tracei [ autoBinAttr ] ;
280
+ }
281
+
282
+ return trace [ binAttr ] ;
283
+ }
284
+
285
+ /*
286
+ * return an array of traces that are all stacked or grouped together
287
+ * TODO: only considers histograms. Should we also harmonize with bars?
288
+ * in principle people can mix and match these, but bars always
289
+ * specify their positions explicitly...
290
+ */
291
+ function getConnectedHistograms ( gd , trace ) {
292
+ if ( gd . _fullLayout . barmode === 'overlay' ) return [ trace ] ;
293
+
294
+ var xid = trace . xaxis ;
295
+ var yid = trace . yaxis ;
296
+ var orientation = trace . orientation ;
297
+
298
+ var out = [ ] ;
299
+ var fullData = gd . _fullData ;
300
+ for ( var i = 0 ; i < fullData . length ; i ++ ) {
301
+ var tracei = fullData [ i ] ;
302
+ if ( tracei . type === 'histogram' &&
303
+ tracei . orientation === orientation &&
304
+ tracei . xaxis === xid && tracei . yaxis === yid
305
+ ) {
306
+ out . push ( tracei ) ;
307
+ }
308
+ }
309
+
310
+ return out ;
311
+ }
312
+
313
+
314
+ /*
315
+ * getMinSize: find the smallest given that size can be a string code
316
+ * ie 'M6' for 6 months. ('L' wouldn't make sense to compare with numeric sizes)
317
+ */
318
+ function getMinSize ( size1 , size2 ) {
319
+ if ( size1 === Infinity ) return size2 ;
320
+ var sizeNumeric1 = numericSize ( size1 ) ;
321
+ var sizeNumeric2 = numericSize ( size2 ) ;
322
+ return sizeNumeric2 < sizeNumeric1 ? size2 : size1 ;
323
+ }
324
+
325
+ function numericSize ( size ) {
326
+ if ( isNumeric ( size ) ) return size ;
327
+ if ( typeof size === 'string' && size . charAt ( 0 ) === 'M' ) {
328
+ return oneMonth * + ( size . substr ( 1 ) ) ;
329
+ }
330
+ return Infinity ;
331
+ }
332
+
183
333
function cdf ( size , direction , currentbin ) {
184
- var i ,
185
- vi ,
186
- prevSum ;
334
+ var i , vi , prevSum ;
187
335
188
336
function firstHalfPoint ( i ) {
189
337
prevSum = size [ i ] ;
0 commit comments