package.src.traces.box.plot.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plotly.js Show documentation
Show all versions of plotly.js Show documentation
The open source javascript graphing library that powers plotly
The newest version!
'use strict';
var d3 = require('@plotly/d3');
var Lib = require('../../lib');
var Drawing = require('../../components/drawing');
// constants for dynamic jitter (ie less jitter for sparser points)
var JITTERCOUNT = 5; // points either side of this to include
var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense"
function plot(gd, plotinfo, cdbox, boxLayer) {
var isStatic = gd._context.staticPlot;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var t = cd0.t;
var trace = cd0.trace;
// whisker width
t.wdPos = t.bdPos * trace.whiskerwidth;
if(trace.visible !== true || t.empty) {
plotGroup.remove();
return;
}
var posAxis, valAxis;
if(trace.orientation === 'h') {
posAxis = ya;
valAxis = xa;
} else {
posAxis = xa;
valAxis = ya;
}
plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t, isStatic);
plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
});
}
function plotBoxAndWhiskers(sel, axes, trace, t, isStatic) {
var isHorizontal = trace.orientation === 'h';
var valAxis = axes.val;
var posAxis = axes.pos;
var posHasRangeBreaks = !!posAxis.rangebreaks;
var bPos = t.bPos;
var wdPos = t.wdPos || 0;
var bPosPxOffset = t.bPosPxOffset || 0;
var whiskerWidth = trace.whiskerwidth || 0;
var showWhiskers = (trace.showwhiskers !== false);
var notched = trace.notched || false;
var nw = notched ? 1 - 2 * trace.notchwidth : 1;
// to support for one-sided box
var bdPos0;
var bdPos1;
if(Array.isArray(t.bdPos)) {
bdPos0 = t.bdPos[0];
bdPos1 = t.bdPos[1];
} else {
bdPos0 = t.bdPos;
bdPos1 = t.bdPos;
}
var paths = sel.selectAll('path.box').data((
trace.type !== 'violin' ||
trace.box.visible
) ? Lib.identity : []);
paths.enter().append('path')
.style('vector-effect', isStatic ? 'none' : 'non-scaling-stroke')
.attr('class', 'box');
paths.exit().remove();
paths.each(function(d) {
if(d.empty) return d3.select(this).attr('d', 'M0,0Z');
var lcenter = posAxis.c2l(d.pos + bPos, true);
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
var posc = posHasRangeBreaks ? (pos0 + pos1) / 2 : posAxis.l2p(lcenter) + bPosPxOffset;
var r = trace.whiskerwidth;
var posw0 = posHasRangeBreaks ? pos0 * r + (1 - r) * posc : posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
var posw1 = posHasRangeBreaks ? pos1 * r + (1 - r) * posc : posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
var sdmode = trace.sizemode === 'sd';
var q1 = valAxis.c2p(sdmode ? d.mean - d.sd : d.q1, true);
var q3 = sdmode ? valAxis.c2p(d.mean + 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(
sdmode ? valAxis.c2p(d.mean, true) :
valAxis.c2p(d.med, true),
Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
);
// for compatibility with box, violin, and candlestick
// perhaps we should put this into cd0.t instead so it's more explicit,
// but what we have now is:
// - box always has d.lf, but boxpoints can be anything
// - violin has d.lf and should always use it (boxpoints is undefined)
// - candlestick has only min/max
var useExtremes = (d.lf === undefined) || (trace.boxpoints === false) || sdmode;
var lf = valAxis.c2p(useExtremes ? d.min : d.lf, true);
var uf = valAxis.c2p(useExtremes ? d.max : d.uf, true);
var ln = valAxis.c2p(d.ln, true);
var un = valAxis.c2p(d.un, true);
if(isHorizontal) {
d3.select(this).attr('d',
'M' + m + ',' + posm0 + 'V' + posm1 + // median line
'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge
(notched ?
'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 :
''
) + // top notched edge
'H' + q3 + // end of the top edge
'V' + pos0 + // right edge
(notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
'Z' + // end of the box
(showWhiskers ?
'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 {
d3.select(this).attr('d',
'M' + posm0 + ',' + m + 'H' + posm1 + // median line
'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box
(notched ?
'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un :
''
) + // notched right edge
'V' + q3 + // end of the right edge
'H' + pos0 + // bottom of the box
(notched ?
'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln :
''
) + // notched left edge
'Z' + // end of the box
(showWhiskers ?
'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
) :
''
)
);
}
});
}
function plotPoints(sel, axes, trace, t) {
var xa = axes.x;
var ya = axes.y;
var bdPos = t.bdPos;
var bPos = t.bPos;
// to support violin points
var mode = trace.boxpoints || trace.points;
// repeatable pseudo-random number generator
Lib.seedPseudoRandom();
// since box plot points get an extra level of nesting, each
// box needs the trace styling info
var fn = function(d) {
d.forEach(function(v) {
v.t = t;
v.trace = trace;
});
return d;
};
var gPoints = sel.selectAll('g.points')
.data(mode ? fn : []);
gPoints.enter().append('g')
.attr('class', 'points');
gPoints.exit().remove();
var paths = gPoints.selectAll('path')
.data(function(d) {
var i;
var pts = d.pts2;
// normally use IQR, but if this is 0 or too small, use max-min
var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1);
var minSpread = typicalSpread * 1e-9;
var spreadLimit = typicalSpread * JITTERSPREAD;
var jitterFactors = [];
var maxJitterFactor = 0;
var newJitter;
// dynamic jitter
if(trace.jitter) {
if(typicalSpread === 0) {
// edge case of no spread at all: fall back to max jitter
maxJitterFactor = 1;
jitterFactors = new Array(pts.length);
for(i = 0; i < pts.length; i++) {
jitterFactors[i] = 1;
}
} else {
for(i = 0; i < pts.length; i++) {
var i0 = Math.max(0, i - JITTERCOUNT);
var pmin = pts[i0].v;
var i1 = Math.min(pts.length - 1, i + JITTERCOUNT);
var pmax = pts[i1].v;
if(mode !== 'all') {
if(pts[i].v < d.lf) pmax = Math.min(pmax, d.lf);
else pmin = Math.max(pmin, d.uf);
}
var jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0;
jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1);
jitterFactors.push(jitterFactor);
maxJitterFactor = Math.max(jitterFactor, maxJitterFactor);
}
}
newJitter = trace.jitter * 2 / (maxJitterFactor || 1);
}
// fills in 'x' and 'y' in calcdata 'pts' item
for(i = 0; i < pts.length; i++) {
var pt = pts[i];
var v = pt.v;
var jitterOffset = trace.jitter ?
(newJitter * jitterFactors[i] * (Lib.pseudoRandom() - 0.5)) :
0;
var posPx = d.pos + bPos + bdPos * (trace.pointpos + jitterOffset);
if(trace.orientation === 'h') {
pt.y = posPx;
pt.x = v;
} else {
pt.x = posPx;
pt.y = v;
}
// tag suspected outliers
if(mode === 'suspectedoutliers' && v < d.uo && v > d.lo) {
pt.so = true;
}
}
return pts;
});
paths.enter().append('path')
.classed('point', true);
paths.exit().remove();
paths.call(Drawing.translatePoints, xa, ya);
}
function plotBoxMean(sel, axes, trace, t) {
var valAxis = axes.val;
var posAxis = axes.pos;
var posHasRangeBreaks = !!posAxis.rangebreaks;
var bPos = t.bPos;
var bPosPxOffset = t.bPosPxOffset || 0;
// to support violin mean lines
var mode = trace.boxmean || (trace.meanline || {}).visible;
// to support for one-sided box
var bdPos0;
var bdPos1;
if(Array.isArray(t.bdPos)) {
bdPos0 = t.bdPos[0];
bdPos1 = t.bdPos[1];
} else {
bdPos0 = t.bdPos;
bdPos1 = t.bdPos;
}
var paths = sel.selectAll('path.mean').data((
(trace.type === 'box' && trace.boxmean) ||
(trace.type === 'violin' && trace.box.visible && trace.meanline.visible)
) ? Lib.identity : []);
paths.enter().append('path')
.attr('class', 'mean')
.style({
fill: 'none',
'vector-effect': 'non-scaling-stroke'
});
paths.exit().remove();
paths.each(function(d) {
var lcenter = posAxis.c2l(d.pos + bPos, true);
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
var posc = posHasRangeBreaks ? (pos0 + pos1) / 2 : posAxis.l2p(lcenter) + bPosPxOffset;
var m = valAxis.c2p(d.mean, true);
var sl = valAxis.c2p(d.mean - d.sd, true);
var sh = valAxis.c2p(d.mean + d.sd, true);
if(trace.orientation === 'h') {
d3.select(this).attr('d',
'M' + m + ',' + pos0 + 'V' + pos1 +
(mode === 'sd' ?
'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z' :
'')
);
} else {
d3.select(this).attr('d',
'M' + pos0 + ',' + m + 'H' + pos1 +
(mode === 'sd' ?
'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z' :
'')
);
}
});
}
module.exports = {
plot: plot,
plotBoxAndWhiskers: plotBoxAndWhiskers,
plotPoints: plotPoints,
plotBoxMean: plotBoxMean
};