Skip to content

Commit 7236922

Browse files
committed
Merge pull request #158 from plotly/box-trace-cleaning
Box trace directory split into multiple files
2 parents 5208bb3 + d9a6948 commit 7236922

File tree

9 files changed

+713
-614
lines changed

9 files changed

+713
-614
lines changed

src/traces/box/calc.js

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var isNumeric = require('fast-isnumeric');
12+
13+
var Plotly = require('../../plotly');
14+
var Lib = require('../../lib');
15+
16+
module.exports = function calc(gd, trace) {
17+
// outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
18+
var xa = Plotly.Axes.getFromId(gd, trace.xaxis||'x'),
19+
ya = Plotly.Axes.getFromId(gd, trace.yaxis||'y'),
20+
orientation = trace.orientation,
21+
cd = [],
22+
valAxis, valLetter, val, valBinned,
23+
posAxis, posLetter, pos, posDistinct, dPos;
24+
25+
// Set value (val) and position (pos) keys via orientation
26+
if (orientation==='h') {
27+
valAxis = xa;
28+
valLetter = 'x';
29+
posAxis = ya;
30+
posLetter = 'y';
31+
} else {
32+
valAxis = ya;
33+
valLetter = 'y';
34+
posAxis = xa;
35+
posLetter = 'x';
36+
}
37+
38+
val = valAxis.makeCalcdata(trace, valLetter); // get val
39+
40+
// size autorange based on all source points
41+
// position happens afterward when we know all the pos
42+
Plotly.Axes.expand(valAxis, val, {padded: true});
43+
44+
// In vertical (horizontal) box plots:
45+
// if no x (y) data, use x0 (y0), or name
46+
// so if you want one box
47+
// per trace, set x0 (y0) to the x (y) value or category for this trace
48+
// (or set x (y) to a constant array matching y (x))
49+
function getPos (gd, trace, posLetter, posAxis, val) {
50+
var pos0;
51+
if (posLetter in trace) pos = posAxis.makeCalcdata(trace, posLetter);
52+
else {
53+
if (posLetter+'0' in trace) pos0 = trace[posLetter+'0'];
54+
else if ('name' in trace && (
55+
posAxis.type==='category' ||
56+
(isNumeric(trace.name) &&
57+
['linear','log'].indexOf(posAxis.type)!==-1) ||
58+
(Lib.isDateTime(trace.name) &&
59+
posAxis.type==='date')
60+
)) {
61+
pos0 = trace.name;
62+
}
63+
else pos0 = gd.numboxes;
64+
pos0 = posAxis.d2c(pos0);
65+
pos = val.map(function(){ return pos0; });
66+
}
67+
return pos;
68+
}
69+
70+
pos = getPos(gd, trace, posLetter, posAxis, val);
71+
72+
// get distinct positions and min difference
73+
var dv = Lib.distinctVals(pos);
74+
posDistinct = dv.vals;
75+
dPos = dv.minDiff/2;
76+
77+
function binVal (cd, val, pos, posDistinct, dPos) {
78+
var posDistinctLength = posDistinct.length,
79+
valLength = val.length,
80+
valBinned = [],
81+
bins = [],
82+
i, p, n, v;
83+
84+
// store distinct pos in cd, find bins, init. valBinned
85+
for (i = 0; i < posDistinctLength; ++i) {
86+
p = posDistinct[i];
87+
cd[i] = {pos: p};
88+
bins[i] = p - dPos;
89+
valBinned[i] = [];
90+
}
91+
bins.push(posDistinct[posDistinctLength-1] + dPos);
92+
93+
// bin the values
94+
for (i = 0; i < valLength; ++i) {
95+
v = val[i];
96+
if(!isNumeric(v)) continue;
97+
n = Lib.findBin(pos[i], bins);
98+
if(n>=0 && n<valLength) valBinned[n].push(v);
99+
}
100+
101+
return valBinned;
102+
}
103+
104+
valBinned = binVal(cd, val, pos, posDistinct, dPos);
105+
106+
// sort the bins and calculate the stats
107+
function calculateStats (cd, valBinned) {
108+
var v, l, cdi, i;
109+
110+
for (i = 0; i < valBinned.length; ++i) {
111+
v = valBinned[i].sort(Lib.sorterAsc);
112+
l = v.length;
113+
cdi = cd[i];
114+
115+
cdi.val = v; // put all values into calcdata
116+
cdi.min = v[0];
117+
cdi.max = v[l-1];
118+
cdi.mean = Lib.mean(v,l);
119+
cdi.sd = Lib.stdev(v,l,cdi.mean);
120+
cdi.q1 = Lib.interp(v, 0.25); // first quartile
121+
cdi.med = Lib.interp(v, 0.5); // median
122+
cdi.q3 = Lib.interp(v, 0.75); // third quartile
123+
// lower and upper fences - last point inside
124+
// 1.5 interquartile ranges from quartiles
125+
cdi.lf = Math.min(cdi.q1, v[
126+
Math.min(Lib.findBin(2.5*cdi.q1-1.5*cdi.q3,v,true)+1, l-1)]);
127+
cdi.uf = Math.max(cdi.q3,v[
128+
Math.max(Lib.findBin(2.5*cdi.q3-1.5*cdi.q1,v), 0)]);
129+
// lower and upper outliers - 3 IQR out (don't clip to max/min,
130+
// this is only for discriminating suspected & far outliers)
131+
cdi.lo = 4*cdi.q1-3*cdi.q3;
132+
cdi.uo = 4*cdi.q3-3*cdi.q1;
133+
}
134+
}
135+
136+
calculateStats(cd, valBinned);
137+
138+
// remove empty bins
139+
cd = cd.filter(function(cdi){ return cdi.val && cdi.val.length; });
140+
if(!cd.length) return [{t: {emptybox: true}}];
141+
142+
// add numboxes and dPos to cd
143+
cd[0].t = {boxnum: gd.numboxes, dPos: dPos};
144+
gd.numboxes++;
145+
return cd;
146+
};

src/traces/box/defaults.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var Color = require('../../components/color');
13+
14+
var attributes = require('./attributes');
15+
16+
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor) {
17+
function coerce(attr, dflt) {
18+
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
19+
}
20+
21+
var y = coerce('y'),
22+
x = coerce('x'),
23+
defaultOrientation;
24+
25+
if (y && y.length) {
26+
defaultOrientation = 'v';
27+
if (!x) coerce('x0');
28+
} else if (x && x.length) {
29+
defaultOrientation = 'h';
30+
coerce('y0');
31+
} else {
32+
traceOut.visible = false;
33+
return;
34+
}
35+
36+
coerce('orientation', defaultOrientation);
37+
38+
coerce('line.color', (traceIn.marker||{}).color || defaultColor);
39+
coerce('line.width', 2);
40+
coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
41+
42+
coerce('whiskerwidth');
43+
coerce('boxmean');
44+
45+
var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor'),
46+
lineoutliercolor = coerce('marker.line.outliercolor'),
47+
boxpoints = outlierColorDflt ||
48+
lineoutliercolor ? coerce('boxpoints', 'suspectedoutliers') :
49+
coerce('boxpoints');
50+
51+
if(boxpoints) {
52+
coerce('jitter', boxpoints==='all' ? 0.3 : 0);
53+
coerce('pointpos', boxpoints==='all' ? -1.5 : 0);
54+
55+
coerce('marker.symbol');
56+
coerce('marker.opacity');
57+
coerce('marker.size');
58+
coerce('marker.color', traceOut.line.color);
59+
coerce('marker.line.color');
60+
coerce('marker.line.width');
61+
62+
if(boxpoints==='suspectedoutliers') {
63+
coerce('marker.line.outliercolor', traceOut.marker.color);
64+
coerce('marker.line.outlierwidth');
65+
}
66+
}
67+
};

src/traces/box/hover.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Plotly = require('../../plotly');
12+
var Color = require('../../components/color');
13+
14+
module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
15+
// closest mode: handicap box plots a little relative to others
16+
var cd = pointData.cd,
17+
trace = cd[0].trace,
18+
t = cd[0].t,
19+
xa = pointData.xa,
20+
ya = pointData.ya,
21+
closeData = [],
22+
dx, dy, distfn, boxDelta,
23+
posLetter, posAxis, posText,
24+
val, valLetter, valAxis;
25+
26+
// adjust inbox w.r.t. to calculate box size
27+
boxDelta = (hovermode==='closest') ? 2.5*t.bdPos : t.bdPos;
28+
29+
if (trace.orientation==='h') {
30+
dx = function(di){
31+
return Plotly.Fx.inbox(di.min - xval, di.max - xval);
32+
};
33+
dy = function(di){
34+
var pos = di.pos + t.bPos - yval;
35+
return Plotly.Fx.inbox(pos - boxDelta, pos + boxDelta);
36+
};
37+
posLetter = 'y';
38+
posAxis = ya;
39+
valLetter = 'x';
40+
valAxis = xa;
41+
} else {
42+
dx = function(di){
43+
var pos = di.pos + t.bPos - xval;
44+
return Plotly.Fx.inbox(pos - boxDelta, pos + boxDelta);
45+
};
46+
dy = function(di){
47+
return Plotly.Fx.inbox(di.min - yval, di.max - yval);
48+
};
49+
posLetter = 'x';
50+
posAxis = xa;
51+
valLetter = 'y';
52+
valAxis = ya;
53+
}
54+
55+
distfn = Plotly.Fx.getDistanceFunction(hovermode, dx, dy);
56+
Plotly.Fx.getClosest(cd, distfn, pointData);
57+
58+
// skip the rest (for this trace) if we didn't find a close point
59+
if(pointData.index===false) return;
60+
61+
// create the item(s) in closedata for this point
62+
63+
// the closest data point
64+
var di = cd[pointData.index],
65+
lc = trace.line.color,
66+
mc = (trace.marker||{}).color;
67+
if(Color.opacity(lc) && trace.line.width) pointData.color = lc;
68+
else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
69+
else pointData.color = trace.fillcolor;
70+
71+
pointData[posLetter+'0'] = posAxis.c2p(di.pos + t.bPos - t.bdPos, true);
72+
pointData[posLetter+'1'] = posAxis.c2p(di.pos + t.bPos + t.bdPos, true);
73+
74+
posText = Plotly.Axes.tickText(posAxis, posAxis.c2l(di.pos), 'hover').text;
75+
pointData[posLetter+'LabelVal'] = di.pos;
76+
77+
// box plots: each "point" gets many labels
78+
var usedVals = {},
79+
attrs = ['med','min','q1','q3','max'],
80+
attr,
81+
pointData2;
82+
if(trace.boxmean) attrs.push('mean');
83+
if(trace.boxpoints) [].push.apply(attrs,['lf', 'uf']);
84+
85+
for (var i=0; i<attrs.length; i++) {
86+
attr = attrs[i];
87+
88+
if(!(attr in di) || (di[attr] in usedVals)) continue;
89+
usedVals[di[attr]] = true;
90+
91+
// copy out to a new object for each value to label
92+
val = valAxis.c2p(di[attr], true);
93+
pointData2 = Plotly.Lib.extendFlat({}, pointData);
94+
pointData2[valLetter+'0'] = pointData2[valLetter+'1'] = val;
95+
pointData2[valLetter+'LabelVal'] = di[attr];
96+
pointData2.attr = attr;
97+
98+
if(attr==='mean' && ('sd' in di) && trace.boxmean==='sd') {
99+
pointData2[valLetter+'err'] = di.sd;
100+
}
101+
pointData.name = ''; // only keep name on the first item (median)
102+
closeData.push(pointData2);
103+
}
104+
return closeData;
105+
};

0 commit comments

Comments
 (0)