Skip to content

Box pre-computed q1/median/q3 input signature + more quartile-computing methods #4432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,9 @@ lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) {
for(var i = 0; i < selectedpoints.length; i++) {
var ptIndex = selectedpoints[i];

if(lib.isIndex(ptIndex)) {
if(lib.isIndex(ptIndex) ||
(lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1]))
) {
var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex;
var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber;

Expand Down
12 changes: 10 additions & 2 deletions src/plots/cartesian/type_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function setAutoType(ax, data) {

var id = ax._id;
var axLetter = id.charAt(0);
var i;

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

var calAttr = axLetter + 'calendar';
var calendar = d0[calAttr];
var opts = {noMultiCategory: !traceIs(d0, 'cartesian') || traceIs(d0, 'noMultiCategory')};
var i;

// To not confuse 2D x/y used for per-box sample points for multicategory coordinates
if(d0.type === 'box' && d0._hasPreCompStats &&
axLetter === {h: 'x', v: 'y'}[d0.orientation || 'v']
) {
opts.noMultiCategory = true;
}

// check all boxes on this x axis to see
// if they're dates, numbers, or categories
Expand Down
241 changes: 199 additions & 42 deletions src/traces/box/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ module.exports = {
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the x coordinate of the box.',
'Sets the x coordinate for single-box traces',
'or the starting coordinate for multi-box traces',
'set using q1/median/q3.',
'See overview for more info.'
].join(' ')
},
Expand All @@ -48,10 +50,32 @@ module.exports = {
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the y coordinate of the box.',
'Sets the y coordinate for single-box traces',
'or the starting coordinate for multi-box traces',
'set using q1/median/q3.',
'See overview for more info.'
].join(' ')
},

dx: {
valType: 'number',
role: 'info',
editType: 'calc',
description: [
'Sets the x coordinate step for multi-box traces',
'set using q1/median/q3.'
].join(' ')
},
dy: {
valType: 'number',
role: 'info',
editType: 'calc',
description: [
'Sets the y coordinate step for multi-box traces',
'set using q1/median/q3.'
].join(' ')
},

name: {
valType: 'string',
role: 'info',
Expand All @@ -64,48 +88,71 @@ module.exports = {
'missing and the position axis is categorical'
].join(' ')
},
text: extendFlat({}, scatterAttrs.text, {

q1: {
valType: 'data_array',
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the text elements associated with each sample value.',
'If a single string, the same string appears over',
'all the data points.',
'If an array of string, the items are mapped in order to the',
'this trace\'s (x,y) coordinates.',
'To be seen, trace `hoverinfo` must contain a *text* flag.'
'Sets the Quartile 1 values.',
'There should be as many items as the number of boxes desired.',
].join(' ')
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: 'Same as `text`.'
}),
hovertemplate: hovertemplateAttrs({
},
median: {
valType: 'data_array',
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'N.B. This only has an effect when hovering on points.'
'Sets the median values.',
'There should be as many items as the number of boxes desired.',
].join(' ')
}),
whiskerwidth: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.5,
role: 'style',
},
q3: {
valType: 'data_array',
role: 'info',
editType: 'calc+clearAxisTypes',
description: [
'Sets the Quartile 3 values.',
'There should be as many items as the number of boxes desired.',
].join(' ')
},
lowerfence: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets the width of the whiskers relative to',
'the box\' width.',
'For example, with 1, the whiskers are as wide as the box(es).'
'Sets the lower fence values.',
'There should be as many items as the number of boxes desired.',
'This attribute has effect only under the q1/median/q3 signature.',
'If `lowerfence` is not provided but a sample (in `y` or `x`) is set,',
'we compute the lower as the last sample point below 1.5 times the IQR.'
].join(' ')
},
upperfence: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets the upper fence values.',
'There should be as many items as the number of boxes desired.',
'This attribute has effect only under the q1/median/q3 signature.',
'If `upperfence` is not provided but a sample (in `y` or `x`) is set,',
'we compute the lower as the last sample point above 1.5 times the IQR.'
].join(' ')
},

notched: {
valType: 'boolean',
role: 'style',
role: 'info',
editType: 'calc',
description: [
'Determines whether or not notches are drawn.',
'Notches displays a confidence interval around the median.',
'We compute the confidence interval as median +/- 1.57 / IQR * sqrt(N),',
'We compute the confidence interval as median +/- 1.57 * IQR / sqrt(N),',
'where IQR is the interquartile range and N is the sample size.',
'If two boxes\' notches do not overlap there is 95% confidence their medians differ.',
'See https://sites.google.com/site/davidsstatistics/home/notched-box-plots for more info.'
'See https://sites.google.com/site/davidsstatistics/home/notched-box-plots for more info.',
'Defaults to *false* unless `notchwidth` or `notchspan` is set.'
].join(' ')
},
notchwidth: {
Expand All @@ -121,10 +168,28 @@ module.exports = {
'For example, with 0, the notches are as wide as the box(es).'
].join(' ')
},
notchspan: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets the notch span from the boxes\' `median` values.',
'There should be as many items as the number of boxes desired.',
'This attribute has effect only under the q1/median/q3 signature.',
'If `notchspan` is not provided but a sample (in `y` or `x`) is set,',
'we compute it as 1.57 * IQR / sqrt(N),',
'where N is the sample size.'
].join(' ')
},

// TODO
// maybe add
// - loweroutlierbound / upperoutlierbound
// - lowersuspectedoutlierbound / uppersuspectedoutlierbound

boxpoints: {
valType: 'enumerated',
values: ['all', 'outliers', 'suspectedoutliers', false],
dflt: 'outliers',
role: 'style',
editType: 'calc',
description: [
Expand All @@ -134,19 +199,11 @@ module.exports = {
'points either less than 4*Q1-3*Q3 or greater than 4*Q3-3*Q1',
'are highlighted (see `outliercolor`)',
'If *all*, all sample points are shown',
'If *false*, only the box(es) are shown with no sample points'
].join(' ')
},
boxmean: {
valType: 'enumerated',
values: [true, 'sd', false],
dflt: false,
role: 'style',
editType: 'calc',
description: [
'If *true*, the mean of the box(es)\' underlying distribution is',
'drawn as a dashed line inside the box(es).',
'If *sd* the standard deviation is also drawn.'
'If *false*, only the box(es) are shown with no sample points',
'Defaults to *suspectedoutliers* when `marker.outliercolor` or',
'`marker.line.outliercolor` is set.',
'Defaults to *all* under the q1/median/q3 signature.',
'Otherwise defaults to *outliers*.',
].join(' ')
},
jitter: {
Expand Down Expand Up @@ -175,6 +232,46 @@ module.exports = {
'right (left) for vertical boxes and above (below) for horizontal boxes'
].join(' ')
},

boxmean: {
valType: 'enumerated',
values: [true, 'sd', false],
role: 'style',
editType: 'calc',
description: [
'If *true*, the mean of the box(es)\' underlying distribution is',
'drawn as a dashed line inside the box(es).',
'If *sd* the standard deviation is also drawn.',
'Defaults to *true* when `mean` is set.',
'Defaults to *sd* when `sd` is set',
'Otherwise defaults to *false*.'
].join(' ')
},
mean: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets the mean values.',
'There should be as many items as the number of boxes desired.',
'This attribute has effect only under the q1/median/q3 signature.',
'If `mean` is not provided but a sample (in `y` or `x`) is set,',
'we compute the mean for each box using the sample values.'
].join(' ')
},
sd: {
valType: 'data_array',
role: 'info',
editType: 'calc',
description: [
'Sets the standard deviation values.',
'There should be as many items as the number of boxes desired.',
'This attribute has effect only under the q1/median/q3 signature.',
'If `sd` is not provided but a sample (in `y` or `x`) is set,',
'we compute the standard deviation for each box using the sample values.'
].join(' ')
},

orientation: {
valType: 'enumerated',
values: ['v', 'h'],
Expand All @@ -187,6 +284,30 @@ module.exports = {
].join(' ')
},

quartilemethod: {
valType: 'enumerated',
values: ['linear', 'exclusive', 'inclusive'],
dflt: 'linear',
role: 'info',
editType: 'calc',
description: [
'Sets the method used to compute the sample\'s Q1 and Q3 quartiles.',

'The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3',
'as computed using method #10 (listed on http://www.amstat.org/publications/jse/v14n3/langford.html).',

'The *exclusive* method uses the median to divide the ordered dataset into two halves',
'if the sample is odd, it does not include the median in either half -',
'Q1 is then the median of the lower half and',
'Q3 the median of the upper half.',

'The *inclusive* method also uses the median to divide the ordered dataset into two halves',
'but if the sample is odd, it includes the median in both halves -',
'Q1 is then the median of the lower half and',
'Q3 the median of the upper half.'
].join(' ')
},

width: {
valType: 'number',
min: 0,
Expand Down Expand Up @@ -246,6 +367,7 @@ module.exports = {
},
editType: 'plot'
},

line: {
color: {
valType: 'color',
Expand All @@ -263,8 +385,23 @@ module.exports = {
},
editType: 'plot'
},

fillcolor: scatterAttrs.fillcolor,

whiskerwidth: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.5,
role: 'style',
editType: 'calc',
description: [
'Sets the width of the whiskers relative to',
'the box\' width.',
'For example, with 1, the whiskers are as wide as the box(es).'
].join(' ')
},

offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup,

Expand All @@ -276,6 +413,26 @@ module.exports = {
marker: scatterAttrs.unselected.marker,
editType: 'style'
},

text: extendFlat({}, scatterAttrs.text, {
description: [
'Sets the text elements associated with each sample value.',
'If a single string, the same string appears over',
'all the data points.',
'If an array of string, the items are mapped in order to the',
'this trace\'s (x,y) coordinates.',
'To be seen, trace `hoverinfo` must contain a *text* flag.'
].join(' ')
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
description: 'Same as `text`.'
}),
hovertemplate: hovertemplateAttrs({
description: [
'N.B. This only has an effect when hovering on points.'
].join(' ')
}),

hoveron: {
valType: 'flaglist',
flags: ['boxes', 'points'],
Expand Down
Loading