Skip to content

Commit 4d02af7

Browse files
committed
nest histogram cumulative attributes and test histnorm/histfunc more
1 parent c12b7cf commit 4d02af7

File tree

5 files changed

+125
-73
lines changed

5 files changed

+125
-73
lines changed

src/plot_api/plot_api.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,8 @@ function _restyle(gd, aobj, _traces) {
12961296
'tilt', 'tiltaxis', 'depth', 'direction', 'rotation', 'pull',
12971297
'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale',
12981298
'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale',
1299-
'xcalendar', 'ycalendar', 'cumulative', 'currentbin'
1299+
'xcalendar', 'ycalendar',
1300+
'cumulative', 'cumulative.enabled', 'cumulative.direction', 'cumulative.currentbin'
13001301
];
13011302

13021303
for(i = 0; i < traces.length; i++) {

src/traces/histogram/attributes.js

+49-41
Original file line numberDiff line numberDiff line change
@@ -56,59 +56,67 @@ module.exports = {
5656
'If **, the span of each bar corresponds to the number of',
5757
'occurrences (i.e. the number of data points lying inside the bins).',
5858

59-
'If *percent*, the span of each bar corresponds to the percentage',
60-
'of occurrences with respect to the total number of sample points',
61-
'(here, the sum of all bin area equals 100%).',
59+
'If *percent* / *probability*, the span of each bar corresponds to',
60+
'the percentage / fraction of occurrences with respect to the total',
61+
'number of sample points',
62+
'(here, the sum of all bin HEIGHTS equals 100% / 1).',
6263

6364
'If *density*, the span of each bar corresponds to the number of',
6465
'occurrences in a bin divided by the size of the bin interval',
65-
'(here, the sum of all bin area equals the',
66+
'(here, the sum of all bin AREAS equals the',
6667
'total number of sample points).',
6768

68-
'If *probability density*, the span of each bar corresponds to the',
69+
'If *probability density*, the area of each bar corresponds to the',
6970
'probability that an event will fall into the corresponding bin',
70-
'(here, the sum of all bin area equals 1).'
71+
'(here, the sum of all bin AREAS equals 1).'
7172
].join(' ')
7273
},
7374

7475
cumulative: {
75-
valType: 'boolean',
76-
dflt: false,
77-
role: 'info',
78-
description: [
79-
'If true, display the cumulative distribution by summing the',
80-
'binned values. Use the `direction` and `centralbin` attributes',
81-
'to tune the accumulation method.'
82-
].join(' ')
83-
},
76+
enabled: {
77+
valType: 'boolean',
78+
dflt: false,
79+
role: 'info',
80+
description: [
81+
'If true, display the cumulative distribution by summing the',
82+
'binned values. Use the `direction` and `centralbin` attributes',
83+
'to tune the accumulation method.',
84+
'Note: in this mode, the *density* `histnorm` settings behave',
85+
'the same as their equivalents without *density*:',
86+
'** and *density* both rise to the number of data points, and',
87+
'*probability* and *probability density* both rise to the',
88+
'number of sample points.'
89+
].join(' ')
90+
},
8491

85-
direction: {
86-
valType: 'enumerated',
87-
values: ['increasing', 'decreasing'],
88-
dflt: 'increasing',
89-
role: 'info',
90-
description: [
91-
'Only applies if `cumulative=true.',
92-
'If *increasing* (default) we sum all prior bins, so the result',
93-
'increases from left to right. If *decreasing* we sum later bins',
94-
'so the fresult decreases from left to right.'
95-
].join(' ')
96-
},
92+
direction: {
93+
valType: 'enumerated',
94+
values: ['increasing', 'decreasing'],
95+
dflt: 'increasing',
96+
role: 'info',
97+
description: [
98+
'Only applies if cumulative is enabled.',
99+
'If *increasing* (default) we sum all prior bins, so the result',
100+
'increases from left to right. If *decreasing* we sum later bins',
101+
'so the result decreases from left to right.'
102+
].join(' ')
103+
},
97104

98-
currentbin: {
99-
valType: 'enumerated',
100-
values: ['include', 'exclude', 'half'],
101-
dflt: 'include',
102-
role: 'info',
103-
description: [
104-
'Only applies if `cumulative=true.',
105-
'Sets whether the current bin is included, excluded, or has half',
106-
'of its value included in the current cumulative value.',
107-
'*include* is the default for compatibility with various other',
108-
'tools, however it introduces a half-bin bias to the results.',
109-
'*exclude* makes the opposite half-bin bias, and *half* removes',
110-
'it.'
111-
].join(' ')
105+
currentbin: {
106+
valType: 'enumerated',
107+
values: ['include', 'exclude', 'half'],
108+
dflt: 'include',
109+
role: 'info',
110+
description: [
111+
'Only applies if cumulative is enabled.',
112+
'Sets whether the current bin is included, excluded, or has half',
113+
'of its value included in the current cumulative value.',
114+
'*include* is the default for compatibility with various other',
115+
'tools, however it introduces a half-bin bias to the results.',
116+
'*exclude* makes the opposite half-bin bias, and *half* removes',
117+
'it.'
118+
].join(' ')
119+
}
112120
},
113121

114122
autobinx: {

src/traces/histogram/calc.js

+15-6
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ module.exports = function calc(gd, trace) {
3333
trace.orientation === 'h' ? (trace.yaxis || 'y') : (trace.xaxis || 'x')),
3434
maindata = trace.orientation === 'h' ? 'y' : 'x',
3535
counterdata = {x: 'y', y: 'x'}[maindata],
36-
calendar = trace[maindata + 'calendar'];
36+
calendar = trace[maindata + 'calendar'],
37+
cumulativeSpec = trace.cumulative;
3738

3839
cleanBins(trace, pa, maindata);
3940

@@ -47,8 +48,8 @@ module.exports = function calc(gd, trace) {
4748
binspec = Axes.autoBin(pos0, pa, trace['nbins' + maindata], false, calendar);
4849

4950
// adjust for CDF edge cases
50-
if(trace.cumulative && (trace.currentbin !== 'include')) {
51-
if(trace.direction === 'decreasing') {
51+
if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) {
52+
if(cumulativeSpec.direction === 'decreasing') {
5253
binspec.start = pa.c2r(pa.r2c(binspec.start) - binspec.size);
5354
}
5455
else {
@@ -74,8 +75,16 @@ module.exports = function calc(gd, trace) {
7475
total = 0,
7576
norm = trace.histnorm,
7677
func = trace.histfunc,
77-
densitynorm = norm.indexOf('density') !== -1,
78-
extremefunc = func === 'max' || func === 'min',
78+
densitynorm = norm.indexOf('density') !== -1;
79+
80+
if(cumulativeSpec.enabled && densitynorm) {
81+
// we treat "cumulative" like it means "integral" if you use a density norm,
82+
// which in the end means it's the same as without "density"
83+
norm = norm.replace(/ ?density$/, '');
84+
densitynorm = false;
85+
}
86+
87+
var extremefunc = func === 'max' || func === 'min',
7988
sizeinit = extremefunc ? null : 0,
8089
binfunc = binFunctions.count,
8190
normfunc = normFunctions[norm],
@@ -131,7 +140,7 @@ module.exports = function calc(gd, trace) {
131140
if(normfunc) normfunc(size, total, inc);
132141

133142
// after all normalization etc, now we can accumulate if desired
134-
if(trace.cumulative) cdf(size, trace.direction, trace.currentbin);
143+
if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin);
135144

136145

137146
var serieslen = Math.min(pos.length, size.length),

src/traces/histogram/defaults.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
2727
var x = coerce('x'),
2828
y = coerce('y');
2929

30-
var cumulative = coerce('cumulative');
30+
var cumulative = coerce('cumulative.enabled');
3131
if(cumulative) {
32-
coerce('direction');
33-
coerce('currentbin');
32+
coerce('cumulative.direction');
33+
coerce('cumulative.currentbin');
3434
}
3535

3636
coerce('text');

test/jasmine/tests/histogram_test.js

+56-22
Original file line numberDiff line numberDiff line change
@@ -247,63 +247,97 @@ describe('Test histogram', function() {
247247
});
248248

249249
describe('cumulative distribution functions', function() {
250-
var base = {x: [1, 2, 3, 4, 2, 3, 4, 3, 4, 4]};
250+
var base = {
251+
x: [0, 5, 10, 15, 5, 10, 15, 10, 15, 15],
252+
y: [2, 2, 2, 14, 6, 6, 6, 10, 10, 2]
253+
};
251254

252255
it('makes the right base histogram', function() {
253256
var baseOut = _calc(base);
254257
expect(baseOut).toEqual([
255-
{b: 0, p: 1, s: 1},
256-
{b: 0, p: 2, s: 2},
257-
{b: 0, p: 3, s: 3},
258-
{b: 0, p: 4, s: 4},
258+
{b: 0, p: 2, s: 1},
259+
{b: 0, p: 7, s: 2},
260+
{b: 0, p: 12, s: 3},
261+
{b: 0, p: 17, s: 4},
259262
]);
260263
});
261264

262265
var CDFs = [
263-
{p: [1, 2, 3, 4], s: [1, 3, 6, 10]},
266+
{p: [2, 7, 12, 17], s: [1, 3, 6, 10]},
264267
{
265268
direction: 'decreasing',
266-
p: [1, 2, 3, 4], s: [10, 9, 7, 4]
269+
p: [2, 7, 12, 17], s: [10, 9, 7, 4]
267270
},
268271
{
269272
currentbin: 'exclude',
270-
p: [2, 3, 4, 5], s: [1, 3, 6, 10]
273+
p: [7, 12, 17, 22], s: [1, 3, 6, 10]
271274
},
272275
{
273276
direction: 'decreasing', currentbin: 'exclude',
274-
p: [0, 1, 2, 3], s: [10, 9, 7, 4]
277+
p: [-3, 2, 7, 12], s: [10, 9, 7, 4]
275278
},
276279
{
277280
currentbin: 'half',
278-
p: [1, 2, 3, 4, 5], s: [0.5, 2, 4.5, 8, 10]
281+
p: [2, 7, 12, 17, 22], s: [0.5, 2, 4.5, 8, 10]
279282
},
280283
{
281284
direction: 'decreasing', currentbin: 'half',
282-
p: [0, 1, 2, 3, 4], s: [10, 9.5, 8, 5.5, 2]
285+
p: [-3, 2, 7, 12, 17], s: [10, 9.5, 8, 5.5, 2]
283286
},
284287
{
285288
direction: 'decreasing', currentbin: 'half', histnorm: 'percent',
286-
p: [0, 1, 2, 3, 4], s: [100, 95, 80, 55, 20]
289+
p: [-3, 2, 7, 12, 17], s: [100, 95, 80, 55, 20]
287290
},
288291
{
289292
currentbin: 'exclude', histnorm: 'probability',
290-
p: [2, 3, 4, 5], s: [0.1, 0.3, 0.6, 1]
293+
p: [7, 12, 17, 22], s: [0.1, 0.3, 0.6, 1]
294+
},
295+
{
296+
// behaves the same as without *density*
297+
direction: 'decreasing', currentbin: 'half', histnorm: 'density',
298+
p: [-3, 2, 7, 12, 17], s: [10, 9.5, 8, 5.5, 2]
299+
},
300+
{
301+
// behaves the same as without *density*, only *probability*
302+
direction: 'decreasing', currentbin: 'half', histnorm: 'probability density',
303+
p: [-3, 2, 7, 12, 17], s: [1, 0.95, 0.8, 0.55, 0.2]
304+
},
305+
{
306+
currentbin: 'half', histfunc: 'sum',
307+
p: [2, 7, 12, 17, 22], s: [1, 6, 19, 44, 60]
308+
},
309+
{
310+
currentbin: 'half', histfunc: 'sum', histnorm: 'probability',
311+
p: [2, 7, 12, 17, 22], s: [0.5 / 30, 0.1, 9.5 / 30, 22 / 30, 1]
312+
},
313+
{
314+
direction: 'decreasing', currentbin: 'half', histfunc: 'max', histnorm: 'percent',
315+
p: [-3, 2, 7, 12, 17], s: [100, 3100 / 32, 2700 / 32, 1900 / 32, 700 / 32]
316+
},
317+
{
318+
direction: 'decreasing', currentbin: 'half', histfunc: 'min', histnorm: 'density',
319+
p: [-3, 2, 7, 12, 17], s: [8, 7, 5, 3, 1]
320+
},
321+
{
322+
currentbin: 'exclude', histfunc: 'avg', histnorm: 'probability density',
323+
p: [7, 12, 17, 22], s: [0.1, 0.3, 0.6, 1]
291324
}
292325
];
293326

294327
CDFs.forEach(function(CDF) {
295-
var direction = CDF.direction,
296-
currentbin = CDF.currentbin,
297-
histnorm = CDF.histnorm,
298-
p = CDF.p,
328+
var p = CDF.p,
299329
s = CDF.s;
300330

301-
it('handles direction=' + direction + ', currentbin=' + currentbin + ', histnorm=' + histnorm, function() {
331+
it('handles direction=' + CDF.direction + ', currentbin=' + CDF.currentbin +
332+
', histnorm=' + CDF.histnorm + ', histfunc=' + CDF.histfunc, function() {
302333
var traceIn = Lib.extendFlat({}, base, {
303-
cumulative: true,
304-
direction: direction,
305-
currentbin: currentbin,
306-
histnorm: histnorm
334+
cumulative: {
335+
enabled: true,
336+
direction: CDF.direction,
337+
currentbin: CDF.currentbin
338+
},
339+
histnorm: CDF.histnorm,
340+
histfunc: CDF.histfunc
307341
});
308342
var out = _calc(traceIn);
309343

0 commit comments

Comments
 (0)