Skip to content

Commit a234668

Browse files
committed
Implement notched box plots, closes #2286
1 parent fdf20cb commit a234668

File tree

8 files changed

+622
-4
lines changed

8 files changed

+622
-4
lines changed

src/traces/box/attributes.js

+22
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,28 @@ module.exports = {
8585
'For example, with 1, the whiskers are as wide as the box(es).'
8686
].join(' ')
8787
},
88+
notched: {
89+
valType: 'boolean',
90+
dflt: false,
91+
role: 'style',
92+
editType: 'calcIfAutorange',
93+
description: [
94+
'Determines whether or not notches should be drawn.'
95+
].join(' ')
96+
},
97+
notchwidth: {
98+
valType: 'number',
99+
min: 0,
100+
max: 1,
101+
dflt: 0.5,
102+
role: 'style',
103+
editType: 'calcIfAutorange',
104+
description: [
105+
'Sets the width of the notches relative to',
106+
'the box\' width.',
107+
'For example, with 1, the whiskers are as wide as the box(es).'
108+
].join(' ')
109+
},
88110
boxpoints: {
89111
valType: 'enumerated',
90112
values: ['all', 'outliers', 'suspectedoutliers', false],

src/traces/box/calc.js

+7
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ module.exports = function calc(gd, trace) {
111111
cdi.lo = 4 * cdi.q1 - 3 * cdi.q3;
112112
cdi.uo = 4 * cdi.q3 - 3 * cdi.q1;
113113

114+
115+
// lower and upper notches ~95% Confidence Intervals for median
116+
var iqr = cdi.q3 - cdi.q1;
117+
var mci = 1.57 * iqr / Math.sqrt(bvLen);
118+
cdi.ln = cdi.med - mci;
119+
cdi.un = cdi.med + mci;
120+
114121
cd.push(cdi);
115122
}
116123
}

src/traces/box/defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
2929
coerce('whiskerwidth');
3030
coerce('boxmean');
3131

32+
coerce('notched');
33+
coerce('notchwidth');
34+
3235
handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
3336
}
3437

src/traces/box/plot.js

+20-4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
101101
var wdPos = t.wdPos || 0;
102102
var bPosPxOffset = t.bPosPxOffset || 0;
103103
var whiskerWidth = trace.whiskerwidth || 0;
104+
var notched = trace.notched || false;
105+
var nw = notched ? trace.notchwidth : 1;
104106

105107
// to support for one-sided box
106108
var bdPos0;
@@ -125,6 +127,8 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
125127
var pos1 = posAxis.c2p(pos + bPos + bdPos1, true) + bPosPxOffset;
126128
var posw0 = posAxis.c2p(pos + bPos - wdPos, true) + bPosPxOffset;
127129
var posw1 = posAxis.c2p(pos + bPos + wdPos, true) + bPosPxOffset;
130+
var posm0 = posAxis.c2p(pos + bPos - bdPos0 * nw, true) + bPosPxOffset;
131+
var posm1 = posAxis.c2p(pos + bPos + bdPos1 * nw, true) + bPosPxOffset;
128132
var q1 = valAxis.c2p(d.q1, true);
129133
var q3 = valAxis.c2p(d.q3, true);
130134
// make sure median isn't identical to either of the
@@ -135,18 +139,30 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
135139
);
136140
var lf = valAxis.c2p(trace.boxpoints === false ? d.min : d.lf, true);
137141
var uf = valAxis.c2p(trace.boxpoints === false ? d.max : d.uf, true);
142+
var ln = valAxis.c2p(d.ln, true);
143+
var un = valAxis.c2p(d.un, true);
138144

139145
if(trace.orientation === 'h') {
140146
d3.select(this).attr('d',
141-
'M' + m + ',' + pos0 + 'V' + pos1 + // median line
142-
'M' + q1 + ',' + pos0 + 'V' + pos1 + 'H' + q3 + 'V' + pos0 + 'Z' + // box
147+
'M' + m + ',' + posm0 + 'V' + posm1 + // median line
148+
'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge
149+
(((notched === true)) ? 'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 : '') + // top notched edge
150+
'H' + q3 + // end of the top edge
151+
'V' + pos0 + // right edge
152+
(((notched === true)) ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
153+
'Z' + // end of the box
143154
'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
144155
((whiskerWidth === 0) ? '' : // whisker caps
145156
'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1));
146157
} else {
147158
d3.select(this).attr('d',
148-
'M' + pos0 + ',' + m + 'H' + pos1 + // median line
149-
'M' + pos0 + ',' + q1 + 'H' + pos1 + 'V' + q3 + 'H' + pos0 + 'Z' + // box
159+
'M' + posm0 + ',' + m + 'H' + posm1 + // median line
160+
'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box
161+
(((notched === true)) ? 'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un : '') + // notched right edge
162+
'V' + q3 + // end of the right edge
163+
'H' + pos0 + // bottom of the box
164+
(((notched === true)) ? 'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln : '') + // notched left edge
165+
'Z' + // end of the box
150166
'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
151167
((whiskerWidth === 0) ? '' : // whisker caps
152168
'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1));
30.3 KB
Loading

test/image/baselines/box_notched.png

28.5 KB
Loading
+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
{
2+
"data":[
3+
{
4+
"x":[
5+
90,
6+
88,
7+
55,
8+
88,
9+
72,
10+
100,
11+
88,
12+
25,
13+
92,
14+
100,
15+
82,
16+
82,
17+
90,
18+
68,
19+
85,
20+
82,
21+
40,
22+
100,
23+
92,
24+
82,
25+
55,
26+
62,
27+
85,
28+
100,
29+
75,
30+
88,
31+
78,
32+
80,
33+
92,
34+
100,
35+
88,
36+
72,
37+
95,
38+
80,
39+
90,
40+
72,
41+
100,
42+
100,
43+
75,
44+
82,
45+
60,
46+
90,
47+
85,
48+
90,
49+
38,
50+
78,
51+
82,
52+
100,
53+
90,
54+
80,
55+
80,
56+
100,
57+
70,
58+
100,
59+
82,
60+
62,
61+
92,
62+
100,
63+
80,
64+
100,
65+
88,
66+
85
67+
],
68+
"line":{
69+
"color":"#1c9099"
70+
},
71+
"type":"box",
72+
"name":"Notched = True",
73+
"orientation": "h",
74+
"notched":true
75+
},
76+
{
77+
"x":[
78+
70,
79+
65,
80+
85,
81+
75,
82+
72,
83+
75,
84+
90,
85+
88,
86+
85,
87+
80,
88+
92,
89+
85,
90+
85,
91+
75,
92+
72,
93+
80,
94+
42,
95+
80,
96+
95,
97+
90,
98+
62,
99+
65,
100+
65,
101+
82,
102+
68,
103+
48,
104+
57,
105+
95,
106+
70,
107+
100,
108+
80,
109+
95,
110+
78,
111+
80,
112+
80,
113+
85,
114+
90,
115+
100,
116+
52,
117+
85,
118+
72,
119+
70,
120+
45,
121+
75,
122+
85,
123+
95,
124+
65,
125+
70,
126+
85,
127+
70,
128+
85,
129+
35,
130+
90,
131+
95,
132+
95,
133+
65,
134+
62,
135+
48,
136+
60,
137+
85,
138+
85,
139+
90,
140+
70,
141+
68
142+
],
143+
"line":{
144+
"color":"#1c9099"
145+
},
146+
"type":"box",
147+
"name":"Notch width = 0.8",
148+
"notched":true,
149+
"orientation": "h",
150+
"notchwidth":0.8
151+
},
152+
{
153+
"x":[
154+
95,
155+
75,
156+
70,
157+
72,
158+
52,
159+
70,
160+
82,
161+
90,
162+
95,
163+
80,
164+
68,
165+
88,
166+
82,
167+
52,
168+
80,
169+
78,
170+
57,
171+
88,
172+
88,
173+
100,
174+
50,
175+
65,
176+
78,
177+
92,
178+
65,
179+
50,
180+
60,
181+
88,
182+
100,
183+
50,
184+
90,
185+
70,
186+
60,
187+
72,
188+
75,
189+
95,
190+
100,
191+
45,
192+
68,
193+
72,
194+
45,
195+
60,
196+
78,
197+
85,
198+
92,
199+
45,
200+
68,
201+
70,
202+
85,
203+
82,
204+
62,
205+
75,
206+
100,
207+
80,
208+
65,
209+
52,
210+
48,
211+
57,
212+
100,
213+
72,
214+
100,
215+
80,
216+
65
217+
],
218+
"line":{
219+
"color":"#1c9099"
220+
},
221+
"type":"box",
222+
"name":"Notch width = 1",
223+
"notched":true,
224+
"orientation": "h",
225+
"notchwidth":1
226+
},
227+
{
228+
"x":[
229+
86,
230+
64,
231+
86.2,
232+
91.1,
233+
86,
234+
85.3,
235+
85.30000000000001,
236+
63.7,
237+
47.400000000000006,
238+
89.2,
239+
91.3,
240+
91.6,
241+
55.099999999999994,
242+
64.1,
243+
76.2,
244+
91.4,
245+
68.9,
246+
60.8,
247+
64.5,
248+
87.7,
249+
21,
250+
97.6,
251+
90.9,
252+
86.6,
253+
100,
254+
33.6,
255+
82.7,
256+
63.599999999999994,
257+
55.5,
258+
80.7,
259+
85,
260+
92.30000000000001,
261+
48.9,
262+
85
263+
],
264+
"line":{
265+
"color":"#1c9099"
266+
},
267+
"type":"box",
268+
"name":"Outside of hinges",
269+
"orientation": "h",
270+
"notched":true
271+
}
272+
],
273+
"layout":{
274+
"showlegend":false,
275+
"xaxis":{
276+
"title":"Grade [%]",
277+
"type":"linear"
278+
},
279+
"title":"Based on Fig 4.4a: Course Grade Distributions",
280+
"yaxis":{
281+
"type":"category"
282+
},
283+
"height":598,
284+
"width":1080,
285+
"autosize":true
286+
}
287+
}

0 commit comments

Comments
 (0)