Skip to content

Commit db3540f

Browse files
committed
Box trace directory split into multiple files
1 parent cb3d535 commit db3540f

File tree

8 files changed

+658
-610
lines changed

8 files changed

+658
-610
lines changed

src/traces/box/calc.js

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

src/traces/box/defaults.js

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

src/traces/box/hover.js

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

0 commit comments

Comments
 (0)