Skip to content

Commit

Permalink
Merge pull request #3845 from plotly/bingroup-finalist
Browse files Browse the repository at this point in the history
Introducing histogram* bingroup attributes
  • Loading branch information
etpinard authored May 21, 2019
2 parents 863e8d0 + b8ea71e commit 08e2d7e
Show file tree
Hide file tree
Showing 24 changed files with 13,032 additions and 296 deletions.
1 change: 0 additions & 1 deletion src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,6 @@ axes.autoBin = function(data, ax, nbins, is2d, calendar, size) {
axes.autoTicks(dummyAx, size0);
}


var finalSize = dummyAx.dtick;
var binStart = axes.tickIncrement(
axes.tickFirst(dummyAx), finalSize, 'reverse', calendar);
Expand Down
1 change: 0 additions & 1 deletion src/plots/cartesian/clean_ticks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var isNumeric = require('fast-isnumeric');
Expand Down
2 changes: 1 addition & 1 deletion src/traces/bar/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {

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

Expand Down
2 changes: 0 additions & 2 deletions src/traces/heatmap/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/


'use strict';

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


module.exports = function calc(gd, trace) {
// prepare the raw data
// run makeCalcdata on x and y even for heatmaps, in case of category mappings
Expand Down
15 changes: 15 additions & 0 deletions src/traces/histogram/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,21 @@ module.exports = {
].join(' ')
},

bingroup: {
valType: 'string',
role: 'info',
dflt: '',
editType: 'calc',
description: [
'Set a group of histogram traces which will have compatible bin settings.',
'Note that traces on the same subplot and with the same *orientation*',
'under `barmode` *stack*, *relative* and *group* are forced into the same bingroup,',
'Using `bingroup`, traces under `barmode` *overlay* and on different axes',
'(of the same axis type) can have compatible bin settings.',
'Note that histogram and histogram2d* trace can share the same `bingroup`'
].join(' ')
},

hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
Expand Down
108 changes: 68 additions & 40 deletions src/traces/histogram/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* LICENSE file in the root directory of this source tree.
*/


'use strict';

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

var Lib = require('../../lib');
var Registry = require('../../registry');
var Axes = require('../../plots/cartesian/axes');

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

module.exports = function calc(gd, trace) {
// ignore as much processing as possible (and including in autorange) if not visible
if(trace.visible !== true) return;

// depending on orientation, set position and size axes and data ranges
// note: this logic for choosing orientation is duplicated in graph_obj->setstyles
function calc(gd, trace) {
var pos = [];
var size = [];
var pa = Axes.getFromId(gd, trace.orientation === 'h' ?
(trace.yaxis || 'y') : (trace.xaxis || 'x'));
var pa = Axes.getFromId(gd, trace.orientation === 'h' ? trace.yaxis : trace.xaxis);
var mainData = trace.orientation === 'h' ? 'y' : 'x';
var counterData = {x: 'y', y: 'x'}[mainData];
var calendar = trace[mainData + 'calendar'];
Expand Down Expand Up @@ -143,7 +137,6 @@ module.exports = function calc(gd, trace) {
// after all normalization etc, now we can accumulate if desired
if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin);


var seriesLen = Math.min(pos.length, size.length);
var cd = [];
var firstNonzero = 0;
Expand Down Expand Up @@ -201,23 +194,30 @@ module.exports = function calc(gd, trace) {
}

return cd;
};
}

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

var cleanBound = (pa.type === 'date') ?
function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, pa.calendar) : null; } :
var r2c = function(v) { return pa.r2c(v, 0, calendar); };
var c2r = function(v) { return pa.c2r(v, 0, calendar); };

var cleanBound = pa.type === 'date' ?
function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, calendar) : null; } :
function(v) { return isNumeric(v) ? Number(v) : null; };

function setBound(attr, bins, newBins) {
Expand All @@ -230,45 +230,73 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
}
}

var binOpts = fullLayout._histogramBinOpts[trace._groupName];

// all but the first trace in this group has already been marked finished
// clear this flag, so next time we run calc we will run autobin again
if(trace._autoBinFinished) {
delete trace._autoBinFinished;
} else {
traces = binOpts.traces;
var sizeFound = binOpts.sizeFound;
var allPos = [];
autoVals = traces[0]._autoBin = {};

// Note: we're including `legendonly` traces here for autobin purposes,
// so that showing & hiding from the legend won't affect bins.
// But this complicates things a bit since those traces don't `calc`,
// hence `isFirstVisible`.
var isFirstVisible = true;
var has2dMap = false;
var hasHist2dContour = false;
for(i = 0; i < traces.length; i++) {
tracei = traces[i];

if(tracei.visible) {
pos0 = tracei._pos0 = pa.makeCalcdata(tracei, mainData);
var mainDatai = binOpts.dirs[i];
pos0 = tracei['_' + mainDatai + 'pos0'] = pa.makeCalcdata(tracei, mainDatai);

allPos = Lib.concat(allPos, pos0);
delete tracei._autoBinFinished;

if(trace.visible === true) {
if(isFirstVisible) {
isFirstVisible = false;
} else {
delete tracei._autoBin;
tracei._autoBinFinished = 1;
}
if(Registry.traceIs(tracei, '2dMap')) {
has2dMap = true;
}
if(tracei.type === 'histogram2dcontour') {
hasHist2dContour = true;
}
}
}
}

calendar = traces[0][mainData + 'calendar'];
var newBinSpec = Axes.autoBin(
allPos, pa, binOpts.nbins, false, calendar, sizeFound && binOpts.size);
var newBinSpec = Axes.autoBin(allPos, pa, binOpts.nbins, has2dMap, calendar, binOpts.sizeFound && binOpts.size);

var autoBin = traces[0]._autoBin = {};
autoVals = autoBin[binOpts.dirs[0]] = {};

if(hasHist2dContour) {
// the "true" 2nd argument reverses the tick direction (which we can't
// just do with a minus sign because of month bins)
if(!binOpts.size) {
newBinSpec.start = c2r(Axes.tickIncrement(
r2c(newBinSpec.start), newBinSpec.size, true, calendar));
}
if(binOpts.end === undefined) {
newBinSpec.end = c2r(Axes.tickIncrement(
r2c(newBinSpec.end), newBinSpec.size, false, calendar));
}
}

// TODO how does work with bingroup ????
// - https://github.com/plotly/plotly.js/issues/3881
//
// Edge case: single-valued histogram overlaying others
// Use them all together to calculate the bin size for the single-valued one
if(isOverlay && newBinSpec._dataSpan === 0 &&
if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 &&
pa.type !== 'category' && pa.type !== 'multicategory') {
// Several single-valued histograms! Stop infinite recursion,
// just return an extra flag that tells handleSingleValueOverlays
Expand All @@ -279,23 +307,19 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
}

// adjust for CDF edge cases
cumulativeSpec = tracei.cumulative;
cumulativeSpec = tracei.cumulative || {};
if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) {
if(cumulativeSpec.direction === 'decreasing') {
newBinSpec.start = pa.c2r(Axes.tickIncrement(
pa.r2c(newBinSpec.start, 0, calendar),
newBinSpec.size, true, calendar
));
newBinSpec.start = c2r(Axes.tickIncrement(
r2c(newBinSpec.start), newBinSpec.size, true, calendar));
} else {
newBinSpec.end = pa.c2r(Axes.tickIncrement(
pa.r2c(newBinSpec.end, 0, calendar),
newBinSpec.size, false, calendar
));
newBinSpec.end = c2r(Axes.tickIncrement(
r2c(newBinSpec.end), newBinSpec.size, false, calendar));
}
}

binOpts.size = newBinSpec.size;
if(!sizeFound) {
if(!binOpts.sizeFound) {
autoVals.size = newBinSpec.size;
Lib.nestedProperty(traces[0], binAttr + '.size').set(newBinSpec.size);
}
Expand All @@ -304,8 +328,8 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
setBound('end', binOpts, newBinSpec);
}

pos0 = trace._pos0;
delete trace._pos0;
pos0 = trace['_' + mainData + 'pos0'];
delete trace['_' + mainData + 'pos0'];

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

if(isSingleValued) {
singleValuedTraces.push(tracei);
Expand All @@ -412,7 +436,7 @@ function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) {
// hunt through pos0 for the first valid value
var dataVals = new Array(singleValuedTraces.length);
for(i = 0; i < singleValuedTraces.length; i++) {
var pos0 = singleValuedTraces[i]._pos0;
var pos0 = singleValuedTraces[i]['_' + mainData + 'pos0'];
for(var j = 0; j < pos0.length; j++) {
if(pos0[j] !== undefined) {
dataVals[i] = pos0[j];
Expand Down Expand Up @@ -470,7 +494,6 @@ function getConnectedHistograms(gd, trace) {
return out;
}


function cdf(size, direction, currentBin) {
var i, vi, prevSum;

Expand Down Expand Up @@ -518,3 +541,8 @@ function cdf(size, direction, currentBin) {
}
}
}

module.exports = {
calc: calc,
calcAllAutoBins: calcAllAutoBins
};
Loading

0 comments on commit 08e2d7e

Please sign in to comment.