All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.traces.box.plot.js Maven / Gradle / Ivy

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
};




© 2015 - 2024 Weber Informatics LLC | Privacy Policy