Skip to content

Adding box plots drawn on std-deviation instead of quartiles #6698

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 21 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ npm start

This command bundles up the source files and opens up a tab in your browser.

#### Step 6: Open up the console and start developing
#### Step 6a: Open up the console and start developing

A typical workflow is to make some modifications to the source, update the
test dashboard, inspect and debug the changes, then repeat. The test dashboard
Expand Down Expand Up @@ -146,6 +146,12 @@ Three additional helpers exist that are refreshed every second:
There is also a search bar in the top right of the dashboard. This fuzzy-searches
image mocks based on their file name and trace type.

#### Step 6b: Create a mock to test new features
Create a new JSON inside
`test\image\mocks\`
which you'll then be able to search from the test dashboard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In several cases adding new image tests are not necessary.
So this step should be optional and it could be part of Step 6.
You may revert changes in CONTRIBUTING.md and possibly submit a separate PR to suggest improvements as well.
Thank you!



#### Step 7: Regenerate plot-schema in "test" folder then review & commit potential changes

```bash
Expand Down
1 change: 1 addition & 0 deletions draftlogs/6697_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add [1-6]-sigma (std deviations) box plots as an alternative to quartiles [[#6697](https://github.com/plotly/plotly.js/issues/6697)]
13 changes: 12 additions & 1 deletion src/traces/box/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,14 @@ module.exports = {

boxmean: {
valType: 'enumerated',
values: [true, 'sd', false],
values: [true, 'sd', '1sigma', '2sigma', '3sigma', '4sigma', '5sigma', '6sigma', false],
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 1sigma the box is drawn between mean+-sigma, instead of median+-quartile',
'1sigma, 2sigma, ... , 6sigma are availabe',
'Defaults to *true* when `mean` is set.',
'Defaults to *sd* when `sd` is set',
'Otherwise defaults to *false*.'
Expand Down Expand Up @@ -378,6 +380,15 @@ module.exports = {
].join(' ')
},

whiskerdisable: {
valType: 'boolean',
dflt: false,
editType: 'calc',
description: [
'Determines whether or not whiskers are visible'
].join(' ')
},

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

Expand Down
1 change: 1 addition & 0 deletions src/traces/box/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
coerce('boxmean', boxmeanDflt);

coerce('whiskerwidth');
coerce('whiskerdisable');
coerce('width');
coerce('quartilemethod');

Expand Down
2 changes: 1 addition & 1 deletion src/traces/box/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
// clicked point from a box during click-to-select
pointData2.hoverOnBox = true;

if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
if(attr === 'mean' && ('sd' in di) && ((trace.boxmean === 'sd') || (trace.boxmean === '1sigma') || (trace.boxmean === '2sigma') || (trace.boxmean === '3sigma') || (trace.boxmean === '4sigma') || (trace.boxmean === '5sigma') || (trace.boxmean === '6sigma'))) {
pointData2[vLetter + 'err'] = di.sd;
}

Expand Down
47 changes: 36 additions & 11 deletions src/traces/box/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function plotBoxAndWhiskers(sel, axes, trace, t, isStatic) {
var wdPos = t.wdPos || 0;
var bPosPxOffset = t.bPosPxOffset || 0;
var whiskerWidth = trace.whiskerwidth || 0;
var whiskerDisable = trace.whiskerdisable || false;
var notched = trace.notched || false;
var nw = notched ? 1 - 2 * trace.notchwidth : 1;

Expand Down Expand Up @@ -94,12 +95,30 @@ function plotBoxAndWhiskers(sel, axes, trace, t, isStatic) {

var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
var q1 = valAxis.c2p(d.q1, true);
var q3 = valAxis.c2p(d.q3, true);
var q1 = trace.boxmean === '1sigma' ? valAxis.c2p(d.mean - 1 * d.sd, true) :
trace.boxmean === '2sigma' ? valAxis.c2p(d.mean - 2 * d.sd, true) :
trace.boxmean === '3sigma' ? valAxis.c2p(d.mean - 3 * d.sd, true) :
trace.boxmean === '4sigma' ? valAxis.c2p(d.mean - 4 * d.sd, true) :
trace.boxmean === '5sigma' ? valAxis.c2p(d.mean - 5 * d.sd, true) :
trace.boxmean === '6sigma' ? valAxis.c2p(d.mean - 6 * d.sd, true) :
valAxis.c2p(d.q1, true);
var q3 = trace.boxmean === '1sigma' ? valAxis.c2p(d.mean + 1 * d.sd, true) :
trace.boxmean === '2sigma' ? valAxis.c2p(d.mean + 2 * d.sd, true) :
trace.boxmean === '3sigma' ? valAxis.c2p(d.mean + 3 * d.sd, true) :
trace.boxmean === '4sigma' ? valAxis.c2p(d.mean + 4 * d.sd, true) :
trace.boxmean === '5sigma' ? valAxis.c2p(d.mean + 5 * d.sd, true) :
trace.boxmean === '6sigma' ? valAxis.c2p(d.mean + 6 * d.sd, true) :
valAxis.c2p(d.q3, true);
// make sure median isn't identical to either of the
// quartiles, so we can see it
var m = Lib.constrain(
valAxis.c2p(d.med, true),
trace.boxmean === '1sigma' ? valAxis.c2p(d.mean, true) :
trace.boxmean === '2sigma' ? valAxis.c2p(d.mean, true) :
trace.boxmean === '3sigma' ? valAxis.c2p(d.mean, true) :
trace.boxmean === '4sigma' ? valAxis.c2p(d.mean, true) :
trace.boxmean === '5sigma' ? valAxis.c2p(d.mean, true) :
trace.boxmean === '6sigma' ? valAxis.c2p(d.mean, true) :
valAxis.c2p(d.med, true),
Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
);

Expand Down Expand Up @@ -127,10 +146,13 @@ function plotBoxAndWhiskers(sel, axes, trace, t, isStatic) {
'V' + pos0 + // right edge
(notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
'Z' + // end of the box
'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
(whiskerWidth === 0 ?
'' : // whisker caps
'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1
(!whiskerDisable ?
'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
(whiskerWidth === 0 ?
'' : // whisker caps
'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1
) :
''
)
);
} else {
Expand All @@ -148,10 +170,13 @@ function plotBoxAndWhiskers(sel, axes, trace, t, isStatic) {
''
) + // notched left edge
'Z' + // end of the box
'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
(whiskerWidth === 0 ?
'' : // whisker caps
'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1
(!whiskerDisable ?
'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
(whiskerWidth === 0 ?
'' : // whisker caps
'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1
) :
''
)
);
}
Expand Down
133 changes: 133 additions & 0 deletions test/image/mocks/box_3sigma.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"data": [
{
"y": [
0.4995, 0.3786, 0.5188, 0.4505, 0.5805, 0.5539, 0.7341, 0.3303, 0.6202, 0.4754, 0.4814, 0.6103, 0.5157, 0.3841, 0.6166, 0.4917, 0.5646, 0.4532, 0.5983,
0.5079, 0.5227, 0.5483, 0.3081, 0.5172, 0.4127, 0.3871, 0.4703, 0.5331, 0.4573, 0.6187, 0.5718, 0.4474, 0.5349, 0.4614, 0.3096, 0.4411, 0.7143, 0.5956,
0.5171, 0.5515, 0.4234, 0.4589, 0.4289, 0.417, 0.4229, 0.5039, 0.4726, 0.5382, 0.261, 0.5326, 0.4472, 0.54, 0.4998, 0.5513, 0.3891, 0.4821, 0.5006,
0.4362, 0.4184, 0.3632, 0.5182, 0.5229, 0.5493, 0.507, 0.4528, 0.4836, 0.5009, 0.49, 0.5757, 0.5408, 0.4246, 0.511, 0.5216, 0.3817, 0.4471, 0.4935,
0.5668, 0.3187, 0.3633, 0.5964, 0.637, 0.4701, 0.4292, 0.4921, 0.439, 0.5846, 0.4502, 0.44, 0.3057, 0.4756, 0.4867, 0.502, 0.438, 0.6819, 0.2607,
0.5233, 0.3291, 0.5555, 0.5397, 0.5068
],
"line": {
"color": "#1c9099"
},
"type": "box",
"boxpoints": "all",
"pointpos": 0,
"name": "1-sigma",
"boxmean": "1sigma",
"whiskerdisable": true
},
{
"y": [
0.4995, 0.3786, 0.5188, 0.4505, 0.5805, 0.5539, 0.7341, 0.3303, 0.6202, 0.4754, 0.4814, 0.6103, 0.5157, 0.3841, 0.6166, 0.4917, 0.5646, 0.4532, 0.5983,
0.5079, 0.5227, 0.5483, 0.3081, 0.5172, 0.4127, 0.3871, 0.4703, 0.5331, 0.4573, 0.6187, 0.5718, 0.4474, 0.5349, 0.4614, 0.3096, 0.4411, 0.7143, 0.5956,
0.5171, 0.5515, 0.4234, 0.4589, 0.4289, 0.417, 0.4229, 0.5039, 0.4726, 0.5382, 0.261, 0.5326, 0.4472, 0.54, 0.4998, 0.5513, 0.3891, 0.4821, 0.5006,
0.4362, 0.4184, 0.3632, 0.5182, 0.5229, 0.5493, 0.507, 0.4528, 0.4836, 0.5009, 0.49, 0.5757, 0.5408, 0.4246, 0.511, 0.5216, 0.3817, 0.4471, 0.4935,
0.5668, 0.3187, 0.3633, 0.5964, 0.637, 0.4701, 0.4292, 0.4921, 0.439, 0.5846, 0.4502, 0.44, 0.3057, 0.4756, 0.4867, 0.502, 0.438, 0.6819, 0.2607,
0.5233, 0.3291, 0.5555, 0.5397, 0.5068
],
"line": {
"color": "#1c9099"
},
"boxpoints": "all",
"pointpos": 0,
"type": "box",
"name": "2-sigma",
"boxmean": "2sigma",
"whiskerdisable": true
},
{
"y": [
0.4995, 0.3786, 0.5188, 0.4505, 0.5805, 0.5539, 0.7341, 0.3303, 0.6202, 0.4754, 0.4814, 0.6103, 0.5157, 0.3841, 0.6166, 0.4917, 0.5646, 0.4532, 0.5983,
0.5079, 0.5227, 0.5483, 0.3081, 0.5172, 0.4127, 0.3871, 0.4703, 0.5331, 0.4573, 0.6187, 0.5718, 0.4474, 0.5349, 0.4614, 0.3096, 0.4411, 0.7143, 0.5956,
0.5171, 0.5515, 0.4234, 0.4589, 0.4289, 0.417, 0.4229, 0.5039, 0.4726, 0.5382, 0.261, 0.5326, 0.4472, 0.54, 0.4998, 0.5513, 0.3891, 0.4821, 0.5006,
0.4362, 0.4184, 0.3632, 0.5182, 0.5229, 0.5493, 0.507, 0.4528, 0.4836, 0.5009, 0.49, 0.5757, 0.5408, 0.4246, 0.511, 0.5216, 0.3817, 0.4471, 0.4935,
0.5668, 0.3187, 0.3633, 0.5964, 0.637, 0.4701, 0.4292, 0.4921, 0.439, 0.5846, 0.4502, 0.44, 0.3057, 0.4756, 0.4867, 0.502, 0.438, 0.6819, 0.2607,
0.5233, 0.3291, 0.5555, 0.5397, 0.5068
],
"line": {
"color": "#1c9099"
},
"type": "box",
"boxpoints": "all",
"pointpos": 0,
"name": "3-sigma",
"boxmean": "3sigma",
"whiskerdisable": true
},
{
"y": [
0.4995, 0.3786, 0.5188, 0.4505, 0.5805, 0.5539, 0.7341, 0.3303, 0.6202, 0.4754, 0.4814, 0.6103, 0.5157, 0.3841, 0.6166, 0.4917, 0.5646, 0.4532, 0.5983,
0.5079, 0.5227, 0.5483, 0.3081, 0.5172, 0.4127, 0.3871, 0.4703, 0.5331, 0.4573, 0.6187, 0.5718, 0.4474, 0.5349, 0.4614, 0.3096, 0.4411, 0.7143, 0.5956,
0.5171, 0.5515, 0.4234, 0.4589, 0.4289, 0.417, 0.4229, 0.5039, 0.4726, 0.5382, 0.261, 0.5326, 0.4472, 0.54, 0.4998, 0.5513, 0.3891, 0.4821, 0.5006,
0.4362, 0.4184, 0.3632, 0.5182, 0.5229, 0.5493, 0.507, 0.4528, 0.4836, 0.5009, 0.49, 0.5757, 0.5408, 0.4246, 0.511, 0.5216, 0.3817, 0.4471, 0.4935,
0.5668, 0.3187, 0.3633, 0.5964, 0.637, 0.4701, 0.4292, 0.4921, 0.439, 0.5846, 0.4502, 0.44, 0.3057, 0.4756, 0.4867, 0.502, 0.438, 0.6819, 0.2607,
0.5233, 0.3291, 0.5555, 0.5397, 0.5068
],
"line": {
"color": "#1c9099"
},
"type": "box",
"boxpoints": "all",
"pointpos": 0,
"name": "4-sigma",
"boxmean": "4sigma",
"whiskerdisable": true
},
{
"y": [
0.4995, 0.3786, 0.5188, 0.4505, 0.5805, 0.5539, 0.7341, 0.3303, 0.6202, 0.4754, 0.4814, 0.6103, 0.5157, 0.3841, 0.6166, 0.4917, 0.5646, 0.4532, 0.5983,
0.5079, 0.5227, 0.5483, 0.3081, 0.5172, 0.4127, 0.3871, 0.4703, 0.5331, 0.4573, 0.6187, 0.5718, 0.4474, 0.5349, 0.4614, 0.3096, 0.4411, 0.7143, 0.5956,
0.5171, 0.5515, 0.4234, 0.4589, 0.4289, 0.417, 0.4229, 0.5039, 0.4726, 0.5382, 0.261, 0.5326, 0.4472, 0.54, 0.4998, 0.5513, 0.3891, 0.4821, 0.5006,
0.4362, 0.4184, 0.3632, 0.5182, 0.5229, 0.5493, 0.507, 0.4528, 0.4836, 0.5009, 0.49, 0.5757, 0.5408, 0.4246, 0.511, 0.5216, 0.3817, 0.4471, 0.4935,
0.5668, 0.3187, 0.3633, 0.5964, 0.637, 0.4701, 0.4292, 0.4921, 0.439, 0.5846, 0.4502, 0.44, 0.3057, 0.4756, 0.4867, 0.502, 0.438, 0.6819, 0.2607,
0.5233, 0.3291, 0.5555, 0.5397, 0.5068
],
"line": {
"color": "#1c9099"
},
"boxpoints": "all",
"pointpos": 0,
"type": "box",
"name": "5-sigma",
"boxmean": "5sigma",
"whiskerdisable": true
},
{
"y": [
0.4995, 0.3786, 0.5188, 0.4505, 0.5805, 0.5539, 0.7341, 0.3303, 0.6202, 0.4754, 0.4814, 0.6103, 0.5157, 0.3841, 0.6166, 0.4917, 0.5646, 0.4532, 0.5983,
0.5079, 0.5227, 0.5483, 0.3081, 0.5172, 0.4127, 0.3871, 0.4703, 0.5331, 0.4573, 0.6187, 0.5718, 0.4474, 0.5349, 0.4614, 0.3096, 0.4411, 0.7143, 0.5956,
0.5171, 0.5515, 0.4234, 0.4589, 0.4289, 0.417, 0.4229, 0.5039, 0.4726, 0.5382, 0.261, 0.5326, 0.4472, 0.54, 0.4998, 0.5513, 0.3891, 0.4821, 0.5006,
0.4362, 0.4184, 0.3632, 0.5182, 0.5229, 0.5493, 0.507, 0.4528, 0.4836, 0.5009, 0.49, 0.5757, 0.5408, 0.4246, 0.511, 0.5216, 0.3817, 0.4471, 0.4935,
0.5668, 0.3187, 0.3633, 0.5964, 0.637, 0.4701, 0.4292, 0.4921, 0.439, 0.5846, 0.4502, 0.44, 0.3057, 0.4756, 0.4867, 0.502, 0.438, 0.6819, 0.2607,
0.5233, 0.3291, 0.5555, 0.5397, 0.5068
],
"line": {
"color": "#1c9099"
},
"boxpoints": "all",
"pointpos": 0,
"type": "box",
"name": "6-sigma",
"boxmean": "6sigma",
"whiskerdisable": true
}
],
"layout": {
"showlegend": false,
"yaxis": {
"title": { "text": "Random Variable" },
"type": "linear",
"range":[-0.2, 1.2]
},
"title": { "text": "Box plots drawn on std deviation instead of quartiles" },
"xaxis": {
"type": "category"
},
"height": 598,
"width": 1080,
"autosize": true
}
}
14 changes: 13 additions & 1 deletion test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15607,12 +15607,18 @@
"valType": "string"
},
"boxmean": {
"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*.",
"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 1sigma the box is drawn between mean+-sigma, instead of median+-quartile 1sigma, 2sigma, ... , 6sigma are availabe Defaults to *true* when `mean` is set. Defaults to *sd* when `sd` is set Otherwise defaults to *false*.",
"editType": "calc",
"valType": "enumerated",
"values": [
true,
"sd",
"1sigma",
"2sigma",
"3sigma",
"4sigma",
"5sigma",
"6sigma",
false
]
},
Expand Down Expand Up @@ -16735,6 +16741,12 @@
"legendonly"
]
},
"whiskerdisable": {
"description": "Determines whether or not whiskers are visible",
"dflt": false,
"editType": "calc",
"valType": "boolean"
},
"whiskerwidth": {
"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).",
"dflt": 0.5,
Expand Down