Skip to content

Commit 08e2d7e

Browse files
authored
Merge pull request #3845 from plotly/bingroup-finalist
Introducing histogram* bingroup attributes
2 parents 863e8d0 + b8ea71e commit 08e2d7e

24 files changed

+13032
-296
lines changed

Diff for: src/plots/cartesian/axes.js

-1
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,6 @@ axes.autoBin = function(data, ax, nbins, is2d, calendar, size) {
368368
axes.autoTicks(dummyAx, size0);
369369
}
370370

371-
372371
var finalSize = dummyAx.dtick;
373372
var binStart = axes.tickIncrement(
374373
axes.tickFirst(dummyAx), finalSize, 'reverse', calendar);

Diff for: src/plots/cartesian/clean_ticks.js

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
109
'use strict';
1110

1211
var isNumeric = require('fast-isnumeric');

Diff for: src/traces/bar/defaults.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
5555

5656
function handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce) {
5757
var orientation = traceOut.orientation;
58-
// N.B. grouping is done across all trace trace types that support it
58+
// N.B. grouping is done across all trace types that support it
5959
var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis'];
6060
var groupId = getAxisGroup(fullLayout, posAxId) + orientation;
6161

Diff for: src/traces/heatmap/calc.js

-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
109
'use strict';
1110

1211
var Registry = require('../../registry');
@@ -21,7 +20,6 @@ var interp2d = require('./interp2d');
2120
var findEmpties = require('./find_empties');
2221
var makeBoundArray = require('./make_bound_array');
2322

24-
2523
module.exports = function calc(gd, trace) {
2624
// prepare the raw data
2725
// run makeCalcdata on x and y even for heatmaps, in case of category mappings

Diff for: src/traces/histogram/attributes.js

+15
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,21 @@ module.exports = {
193193
].join(' ')
194194
},
195195

196+
bingroup: {
197+
valType: 'string',
198+
role: 'info',
199+
dflt: '',
200+
editType: 'calc',
201+
description: [
202+
'Set a group of histogram traces which will have compatible bin settings.',
203+
'Note that traces on the same subplot and with the same *orientation*',
204+
'under `barmode` *stack*, *relative* and *group* are forced into the same bingroup,',
205+
'Using `bingroup`, traces under `barmode` *overlay* and on different axes',
206+
'(of the same axis type) can have compatible bin settings.',
207+
'Note that histogram and histogram2d* trace can share the same `bingroup`'
208+
].join(' ')
209+
},
210+
196211
hovertemplate: hovertemplateAttrs({}, {
197212
keys: constants.eventDataKeys
198213
}),

Diff for: src/traces/histogram/calc.js

+68-40
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
109
'use strict';
1110

1211
var isNumeric = require('fast-isnumeric');
1312

1413
var Lib = require('../../lib');
14+
var Registry = require('../../registry');
1515
var Axes = require('../../plots/cartesian/axes');
1616

1717
var arraysToCalcdata = require('../bar/arrays_to_calcdata');
@@ -20,16 +20,10 @@ var normFunctions = require('./norm_functions');
2020
var doAvg = require('./average');
2121
var getBinSpanLabelRound = require('./bin_label_vals');
2222

23-
module.exports = function calc(gd, trace) {
24-
// ignore as much processing as possible (and including in autorange) if not visible
25-
if(trace.visible !== true) return;
26-
27-
// depending on orientation, set position and size axes and data ranges
28-
// note: this logic for choosing orientation is duplicated in graph_obj->setstyles
23+
function calc(gd, trace) {
2924
var pos = [];
3025
var size = [];
31-
var pa = Axes.getFromId(gd, trace.orientation === 'h' ?
32-
(trace.yaxis || 'y') : (trace.xaxis || 'x'));
26+
var pa = Axes.getFromId(gd, trace.orientation === 'h' ? trace.yaxis : trace.xaxis);
3327
var mainData = trace.orientation === 'h' ? 'y' : 'x';
3428
var counterData = {x: 'y', y: 'x'}[mainData];
3529
var calendar = trace[mainData + 'calendar'];
@@ -143,7 +137,6 @@ module.exports = function calc(gd, trace) {
143137
// after all normalization etc, now we can accumulate if desired
144138
if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin);
145139

146-
147140
var seriesLen = Math.min(pos.length, size.length);
148141
var cd = [];
149142
var firstNonzero = 0;
@@ -201,23 +194,30 @@ module.exports = function calc(gd, trace) {
201194
}
202195

203196
return cd;
204-
};
197+
}
205198

206199
/*
207-
* calcAllAutoBins: we want all histograms on the same axes to share bin specs
208-
* if they're grouped or stacked. If the user has explicitly specified differing
200+
* calcAllAutoBins: we want all histograms inside the same bingroup
201+
* (see logic in Histogram.crossTraceDefaults) to share bin specs
202+
*
203+
* If the user has explicitly specified differing
209204
* bin specs, there's nothing we can do, but if possible we will try to use the
210-
* smallest bins of any of the auto values for all histograms grouped/stacked
211-
* together.
205+
* smallest bins of any of the auto values for all histograms inside the same
206+
* bingroup.
212207
*/
213208
function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
214209
var binAttr = mainData + 'bins';
215210
var fullLayout = gd._fullLayout;
211+
var groupName = trace['_' + mainData + 'bingroup'];
212+
var binOpts = fullLayout._histogramBinOpts[groupName];
216213
var isOverlay = fullLayout.barmode === 'overlay';
217214
var i, traces, tracei, calendar, pos0, autoVals, cumulativeSpec;
218215

219-
var cleanBound = (pa.type === 'date') ?
220-
function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, pa.calendar) : null; } :
216+
var r2c = function(v) { return pa.r2c(v, 0, calendar); };
217+
var c2r = function(v) { return pa.c2r(v, 0, calendar); };
218+
219+
var cleanBound = pa.type === 'date' ?
220+
function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, calendar) : null; } :
221221
function(v) { return isNumeric(v) ? Number(v) : null; };
222222

223223
function setBound(attr, bins, newBins) {
@@ -230,45 +230,73 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
230230
}
231231
}
232232

233-
var binOpts = fullLayout._histogramBinOpts[trace._groupName];
234-
235233
// all but the first trace in this group has already been marked finished
236234
// clear this flag, so next time we run calc we will run autobin again
237235
if(trace._autoBinFinished) {
238236
delete trace._autoBinFinished;
239237
} else {
240238
traces = binOpts.traces;
241-
var sizeFound = binOpts.sizeFound;
242239
var allPos = [];
243-
autoVals = traces[0]._autoBin = {};
240+
244241
// Note: we're including `legendonly` traces here for autobin purposes,
245242
// so that showing & hiding from the legend won't affect bins.
246243
// But this complicates things a bit since those traces don't `calc`,
247244
// hence `isFirstVisible`.
248245
var isFirstVisible = true;
246+
var has2dMap = false;
247+
var hasHist2dContour = false;
249248
for(i = 0; i < traces.length; i++) {
250249
tracei = traces[i];
250+
251251
if(tracei.visible) {
252-
pos0 = tracei._pos0 = pa.makeCalcdata(tracei, mainData);
252+
var mainDatai = binOpts.dirs[i];
253+
pos0 = tracei['_' + mainDatai + 'pos0'] = pa.makeCalcdata(tracei, mainDatai);
254+
253255
allPos = Lib.concat(allPos, pos0);
254256
delete tracei._autoBinFinished;
257+
255258
if(trace.visible === true) {
256259
if(isFirstVisible) {
257260
isFirstVisible = false;
258261
} else {
259262
delete tracei._autoBin;
260263
tracei._autoBinFinished = 1;
261264
}
265+
if(Registry.traceIs(tracei, '2dMap')) {
266+
has2dMap = true;
267+
}
268+
if(tracei.type === 'histogram2dcontour') {
269+
hasHist2dContour = true;
270+
}
262271
}
263272
}
264273
}
274+
265275
calendar = traces[0][mainData + 'calendar'];
266-
var newBinSpec = Axes.autoBin(
267-
allPos, pa, binOpts.nbins, false, calendar, sizeFound && binOpts.size);
276+
var newBinSpec = Axes.autoBin(allPos, pa, binOpts.nbins, has2dMap, calendar, binOpts.sizeFound && binOpts.size);
268277

278+
var autoBin = traces[0]._autoBin = {};
279+
autoVals = autoBin[binOpts.dirs[0]] = {};
280+
281+
if(hasHist2dContour) {
282+
// the "true" 2nd argument reverses the tick direction (which we can't
283+
// just do with a minus sign because of month bins)
284+
if(!binOpts.size) {
285+
newBinSpec.start = c2r(Axes.tickIncrement(
286+
r2c(newBinSpec.start), newBinSpec.size, true, calendar));
287+
}
288+
if(binOpts.end === undefined) {
289+
newBinSpec.end = c2r(Axes.tickIncrement(
290+
r2c(newBinSpec.end), newBinSpec.size, false, calendar));
291+
}
292+
}
293+
294+
// TODO how does work with bingroup ????
295+
// - https://github.com/plotly/plotly.js/issues/3881
296+
//
269297
// Edge case: single-valued histogram overlaying others
270298
// Use them all together to calculate the bin size for the single-valued one
271-
if(isOverlay && newBinSpec._dataSpan === 0 &&
299+
if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 &&
272300
pa.type !== 'category' && pa.type !== 'multicategory') {
273301
// Several single-valued histograms! Stop infinite recursion,
274302
// just return an extra flag that tells handleSingleValueOverlays
@@ -279,23 +307,19 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
279307
}
280308

281309
// adjust for CDF edge cases
282-
cumulativeSpec = tracei.cumulative;
310+
cumulativeSpec = tracei.cumulative || {};
283311
if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) {
284312
if(cumulativeSpec.direction === 'decreasing') {
285-
newBinSpec.start = pa.c2r(Axes.tickIncrement(
286-
pa.r2c(newBinSpec.start, 0, calendar),
287-
newBinSpec.size, true, calendar
288-
));
313+
newBinSpec.start = c2r(Axes.tickIncrement(
314+
r2c(newBinSpec.start), newBinSpec.size, true, calendar));
289315
} else {
290-
newBinSpec.end = pa.c2r(Axes.tickIncrement(
291-
pa.r2c(newBinSpec.end, 0, calendar),
292-
newBinSpec.size, false, calendar
293-
));
316+
newBinSpec.end = c2r(Axes.tickIncrement(
317+
r2c(newBinSpec.end), newBinSpec.size, false, calendar));
294318
}
295319
}
296320

297321
binOpts.size = newBinSpec.size;
298-
if(!sizeFound) {
322+
if(!binOpts.sizeFound) {
299323
autoVals.size = newBinSpec.size;
300324
Lib.nestedProperty(traces[0], binAttr + '.size').set(newBinSpec.size);
301325
}
@@ -304,8 +328,8 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
304328
setBound('end', binOpts, newBinSpec);
305329
}
306330

307-
pos0 = trace._pos0;
308-
delete trace._pos0;
331+
pos0 = trace['_' + mainData + 'pos0'];
332+
delete trace['_' + mainData + 'pos0'];
309333

310334
// Each trace can specify its own start/end, or if omitted
311335
// we ensure they're beyond the bounds of this trace's data,
@@ -398,7 +422,7 @@ function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) {
398422
// so we can use this result when we get to tracei in the normal
399423
// course of events, mark it as done and put _pos0 back
400424
tracei._autoBinFinished = 1;
401-
tracei._pos0 = resulti[1];
425+
tracei['_' + mainData + 'pos0'] = resulti[1];
402426

403427
if(isSingleValued) {
404428
singleValuedTraces.push(tracei);
@@ -412,7 +436,7 @@ function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) {
412436
// hunt through pos0 for the first valid value
413437
var dataVals = new Array(singleValuedTraces.length);
414438
for(i = 0; i < singleValuedTraces.length; i++) {
415-
var pos0 = singleValuedTraces[i]._pos0;
439+
var pos0 = singleValuedTraces[i]['_' + mainData + 'pos0'];
416440
for(var j = 0; j < pos0.length; j++) {
417441
if(pos0[j] !== undefined) {
418442
dataVals[i] = pos0[j];
@@ -470,7 +494,6 @@ function getConnectedHistograms(gd, trace) {
470494
return out;
471495
}
472496

473-
474497
function cdf(size, direction, currentBin) {
475498
var i, vi, prevSum;
476499

@@ -518,3 +541,8 @@ function cdf(size, direction, currentBin) {
518541
}
519542
}
520543
}
544+
545+
module.exports = {
546+
calc: calc,
547+
calcAllAutoBins: calcAllAutoBins
548+
};

0 commit comments

Comments
 (0)