Skip to content

Commit 504081c

Browse files
authored
Merge pull request #4432 from plotly/quartiles-more-methods
Box pre-computed q1/median/q3 input signature + more quartile-computing methods
2 parents 4d88717 + cac9a88 commit 504081c

13 files changed

+1910
-176
lines changed

src/lib/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,9 @@ lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) {
584584
for(var i = 0; i < selectedpoints.length; i++) {
585585
var ptIndex = selectedpoints[i];
586586

587-
if(lib.isIndex(ptIndex)) {
587+
if(lib.isIndex(ptIndex) ||
588+
(lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1]))
589+
) {
588590
var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex;
589591
var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber;
590592

src/plots/cartesian/type_defaults.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function setAutoType(ax, data) {
4040

4141
var id = ax._id;
4242
var axLetter = id.charAt(0);
43+
var i;
4344

4445
// support 3d
4546
if(id.indexOf('scene') !== -1) id = axLetter;
@@ -50,15 +51,22 @@ function setAutoType(ax, data) {
5051
// first check for histograms, as the count direction
5152
// should always default to a linear axis
5253
if(d0.type === 'histogram' &&
53-
axLetter === {v: 'y', h: 'x'}[d0.orientation || 'v']) {
54+
axLetter === {v: 'y', h: 'x'}[d0.orientation || 'v']
55+
) {
5456
ax.type = 'linear';
5557
return;
5658
}
5759

5860
var calAttr = axLetter + 'calendar';
5961
var calendar = d0[calAttr];
6062
var opts = {noMultiCategory: !traceIs(d0, 'cartesian') || traceIs(d0, 'noMultiCategory')};
61-
var i;
63+
64+
// To not confuse 2D x/y used for per-box sample points for multicategory coordinates
65+
if(d0.type === 'box' && d0._hasPreCompStats &&
66+
axLetter === {h: 'x', v: 'y'}[d0.orientation || 'v']
67+
) {
68+
opts.noMultiCategory = true;
69+
}
6270

6371
// check all boxes on this x axis to see
6472
// if they're dates, numbers, or categories

src/traces/box/attributes.js

+199-42
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ module.exports = {
3939
role: 'info',
4040
editType: 'calc+clearAxisTypes',
4141
description: [
42-
'Sets the x coordinate of the box.',
42+
'Sets the x coordinate for single-box traces',
43+
'or the starting coordinate for multi-box traces',
44+
'set using q1/median/q3.',
4345
'See overview for more info.'
4446
].join(' ')
4547
},
@@ -48,10 +50,32 @@ module.exports = {
4850
role: 'info',
4951
editType: 'calc+clearAxisTypes',
5052
description: [
51-
'Sets the y coordinate of the box.',
53+
'Sets the y coordinate for single-box traces',
54+
'or the starting coordinate for multi-box traces',
55+
'set using q1/median/q3.',
5256
'See overview for more info.'
5357
].join(' ')
5458
},
59+
60+
dx: {
61+
valType: 'number',
62+
role: 'info',
63+
editType: 'calc',
64+
description: [
65+
'Sets the x coordinate step for multi-box traces',
66+
'set using q1/median/q3.'
67+
].join(' ')
68+
},
69+
dy: {
70+
valType: 'number',
71+
role: 'info',
72+
editType: 'calc',
73+
description: [
74+
'Sets the y coordinate step for multi-box traces',
75+
'set using q1/median/q3.'
76+
].join(' ')
77+
},
78+
5579
name: {
5680
valType: 'string',
5781
role: 'info',
@@ -64,48 +88,71 @@ module.exports = {
6488
'missing and the position axis is categorical'
6589
].join(' ')
6690
},
67-
text: extendFlat({}, scatterAttrs.text, {
91+
92+
q1: {
93+
valType: 'data_array',
94+
role: 'info',
95+
editType: 'calc+clearAxisTypes',
6896
description: [
69-
'Sets the text elements associated with each sample value.',
70-
'If a single string, the same string appears over',
71-
'all the data points.',
72-
'If an array of string, the items are mapped in order to the',
73-
'this trace\'s (x,y) coordinates.',
74-
'To be seen, trace `hoverinfo` must contain a *text* flag.'
97+
'Sets the Quartile 1 values.',
98+
'There should be as many items as the number of boxes desired.',
7599
].join(' ')
76-
}),
77-
hovertext: extendFlat({}, scatterAttrs.hovertext, {
78-
description: 'Same as `text`.'
79-
}),
80-
hovertemplate: hovertemplateAttrs({
100+
},
101+
median: {
102+
valType: 'data_array',
103+
role: 'info',
104+
editType: 'calc+clearAxisTypes',
81105
description: [
82-
'N.B. This only has an effect when hovering on points.'
106+
'Sets the median values.',
107+
'There should be as many items as the number of boxes desired.',
83108
].join(' ')
84-
}),
85-
whiskerwidth: {
86-
valType: 'number',
87-
min: 0,
88-
max: 1,
89-
dflt: 0.5,
90-
role: 'style',
109+
},
110+
q3: {
111+
valType: 'data_array',
112+
role: 'info',
113+
editType: 'calc+clearAxisTypes',
114+
description: [
115+
'Sets the Quartile 3 values.',
116+
'There should be as many items as the number of boxes desired.',
117+
].join(' ')
118+
},
119+
lowerfence: {
120+
valType: 'data_array',
121+
role: 'info',
91122
editType: 'calc',
92123
description: [
93-
'Sets the width of the whiskers relative to',
94-
'the box\' width.',
95-
'For example, with 1, the whiskers are as wide as the box(es).'
124+
'Sets the lower fence values.',
125+
'There should be as many items as the number of boxes desired.',
126+
'This attribute has effect only under the q1/median/q3 signature.',
127+
'If `lowerfence` is not provided but a sample (in `y` or `x`) is set,',
128+
'we compute the lower as the last sample point below 1.5 times the IQR.'
129+
].join(' ')
130+
},
131+
upperfence: {
132+
valType: 'data_array',
133+
role: 'info',
134+
editType: 'calc',
135+
description: [
136+
'Sets the upper fence values.',
137+
'There should be as many items as the number of boxes desired.',
138+
'This attribute has effect only under the q1/median/q3 signature.',
139+
'If `upperfence` is not provided but a sample (in `y` or `x`) is set,',
140+
'we compute the lower as the last sample point above 1.5 times the IQR.'
96141
].join(' ')
97142
},
143+
98144
notched: {
99145
valType: 'boolean',
100-
role: 'style',
146+
role: 'info',
101147
editType: 'calc',
102148
description: [
103149
'Determines whether or not notches are drawn.',
104150
'Notches displays a confidence interval around the median.',
105-
'We compute the confidence interval as median +/- 1.57 / IQR * sqrt(N),',
151+
'We compute the confidence interval as median +/- 1.57 * IQR / sqrt(N),',
106152
'where IQR is the interquartile range and N is the sample size.',
107153
'If two boxes\' notches do not overlap there is 95% confidence their medians differ.',
108-
'See https://sites.google.com/site/davidsstatistics/home/notched-box-plots for more info.'
154+
'See https://sites.google.com/site/davidsstatistics/home/notched-box-plots for more info.',
155+
'Defaults to *false* unless `notchwidth` or `notchspan` is set.'
109156
].join(' ')
110157
},
111158
notchwidth: {
@@ -121,10 +168,28 @@ module.exports = {
121168
'For example, with 0, the notches are as wide as the box(es).'
122169
].join(' ')
123170
},
171+
notchspan: {
172+
valType: 'data_array',
173+
role: 'info',
174+
editType: 'calc',
175+
description: [
176+
'Sets the notch span from the boxes\' `median` values.',
177+
'There should be as many items as the number of boxes desired.',
178+
'This attribute has effect only under the q1/median/q3 signature.',
179+
'If `notchspan` is not provided but a sample (in `y` or `x`) is set,',
180+
'we compute it as 1.57 * IQR / sqrt(N),',
181+
'where N is the sample size.'
182+
].join(' ')
183+
},
184+
185+
// TODO
186+
// maybe add
187+
// - loweroutlierbound / upperoutlierbound
188+
// - lowersuspectedoutlierbound / uppersuspectedoutlierbound
189+
124190
boxpoints: {
125191
valType: 'enumerated',
126192
values: ['all', 'outliers', 'suspectedoutliers', false],
127-
dflt: 'outliers',
128193
role: 'style',
129194
editType: 'calc',
130195
description: [
@@ -134,19 +199,11 @@ module.exports = {
134199
'points either less than 4*Q1-3*Q3 or greater than 4*Q3-3*Q1',
135200
'are highlighted (see `outliercolor`)',
136201
'If *all*, all sample points are shown',
137-
'If *false*, only the box(es) are shown with no sample points'
138-
].join(' ')
139-
},
140-
boxmean: {
141-
valType: 'enumerated',
142-
values: [true, 'sd', false],
143-
dflt: false,
144-
role: 'style',
145-
editType: 'calc',
146-
description: [
147-
'If *true*, the mean of the box(es)\' underlying distribution is',
148-
'drawn as a dashed line inside the box(es).',
149-
'If *sd* the standard deviation is also drawn.'
202+
'If *false*, only the box(es) are shown with no sample points',
203+
'Defaults to *suspectedoutliers* when `marker.outliercolor` or',
204+
'`marker.line.outliercolor` is set.',
205+
'Defaults to *all* under the q1/median/q3 signature.',
206+
'Otherwise defaults to *outliers*.',
150207
].join(' ')
151208
},
152209
jitter: {
@@ -175,6 +232,46 @@ module.exports = {
175232
'right (left) for vertical boxes and above (below) for horizontal boxes'
176233
].join(' ')
177234
},
235+
236+
boxmean: {
237+
valType: 'enumerated',
238+
values: [true, 'sd', false],
239+
role: 'style',
240+
editType: 'calc',
241+
description: [
242+
'If *true*, the mean of the box(es)\' underlying distribution is',
243+
'drawn as a dashed line inside the box(es).',
244+
'If *sd* the standard deviation is also drawn.',
245+
'Defaults to *true* when `mean` is set.',
246+
'Defaults to *sd* when `sd` is set',
247+
'Otherwise defaults to *false*.'
248+
].join(' ')
249+
},
250+
mean: {
251+
valType: 'data_array',
252+
role: 'info',
253+
editType: 'calc',
254+
description: [
255+
'Sets the mean values.',
256+
'There should be as many items as the number of boxes desired.',
257+
'This attribute has effect only under the q1/median/q3 signature.',
258+
'If `mean` is not provided but a sample (in `y` or `x`) is set,',
259+
'we compute the mean for each box using the sample values.'
260+
].join(' ')
261+
},
262+
sd: {
263+
valType: 'data_array',
264+
role: 'info',
265+
editType: 'calc',
266+
description: [
267+
'Sets the standard deviation values.',
268+
'There should be as many items as the number of boxes desired.',
269+
'This attribute has effect only under the q1/median/q3 signature.',
270+
'If `sd` is not provided but a sample (in `y` or `x`) is set,',
271+
'we compute the standard deviation for each box using the sample values.'
272+
].join(' ')
273+
},
274+
178275
orientation: {
179276
valType: 'enumerated',
180277
values: ['v', 'h'],
@@ -187,6 +284,30 @@ module.exports = {
187284
].join(' ')
188285
},
189286

287+
quartilemethod: {
288+
valType: 'enumerated',
289+
values: ['linear', 'exclusive', 'inclusive'],
290+
dflt: 'linear',
291+
role: 'info',
292+
editType: 'calc',
293+
description: [
294+
'Sets the method used to compute the sample\'s Q1 and Q3 quartiles.',
295+
296+
'The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3',
297+
'as computed using method #10 (listed on http://www.amstat.org/publications/jse/v14n3/langford.html).',
298+
299+
'The *exclusive* method uses the median to divide the ordered dataset into two halves',
300+
'if the sample is odd, it does not include the median in either half -',
301+
'Q1 is then the median of the lower half and',
302+
'Q3 the median of the upper half.',
303+
304+
'The *inclusive* method also uses the median to divide the ordered dataset into two halves',
305+
'but if the sample is odd, it includes the median in both halves -',
306+
'Q1 is then the median of the lower half and',
307+
'Q3 the median of the upper half.'
308+
].join(' ')
309+
},
310+
190311
width: {
191312
valType: 'number',
192313
min: 0,
@@ -246,6 +367,7 @@ module.exports = {
246367
},
247368
editType: 'plot'
248369
},
370+
249371
line: {
250372
color: {
251373
valType: 'color',
@@ -263,8 +385,23 @@ module.exports = {
263385
},
264386
editType: 'plot'
265387
},
388+
266389
fillcolor: scatterAttrs.fillcolor,
267390

391+
whiskerwidth: {
392+
valType: 'number',
393+
min: 0,
394+
max: 1,
395+
dflt: 0.5,
396+
role: 'style',
397+
editType: 'calc',
398+
description: [
399+
'Sets the width of the whiskers relative to',
400+
'the box\' width.',
401+
'For example, with 1, the whiskers are as wide as the box(es).'
402+
].join(' ')
403+
},
404+
268405
offsetgroup: barAttrs.offsetgroup,
269406
alignmentgroup: barAttrs.alignmentgroup,
270407

@@ -276,6 +413,26 @@ module.exports = {
276413
marker: scatterAttrs.unselected.marker,
277414
editType: 'style'
278415
},
416+
417+
text: extendFlat({}, scatterAttrs.text, {
418+
description: [
419+
'Sets the text elements associated with each sample value.',
420+
'If a single string, the same string appears over',
421+
'all the data points.',
422+
'If an array of string, the items are mapped in order to the',
423+
'this trace\'s (x,y) coordinates.',
424+
'To be seen, trace `hoverinfo` must contain a *text* flag.'
425+
].join(' ')
426+
}),
427+
hovertext: extendFlat({}, scatterAttrs.hovertext, {
428+
description: 'Same as `text`.'
429+
}),
430+
hovertemplate: hovertemplateAttrs({
431+
description: [
432+
'N.B. This only has an effect when hovering on points.'
433+
].join(' ')
434+
}),
435+
279436
hoveron: {
280437
valType: 'flaglist',
281438
flags: ['boxes', 'points'],

0 commit comments

Comments
 (0)