text: /**
  * Copyright 2012-2017, Plotly, Inc.
  * All rights reserved.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
  
  'use strict';
  
  var d3 = require('d3');
  
  var Lib = require('../../lib');
  var Drawing = require('../../components/drawing');
  
  // repeatable pseudorandom generator
  var randSeed = 2000000000;
  
  function seed() {
      randSeed = 2000000000;
  }
  
  function rand() {
      var lastVal = randSeed;
      randSeed = (69069 * randSeed + 1) % 4294967296;
      // don't let consecutive vals be too close together
      // gets away from really trying to be random, in favor of better local uniformity
      if(Math.abs(randSeed - lastVal) < 429496729) return rand();
      return randSeed / 4294967296;
  }
  
  // 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) {
      var fullLayout = gd._fullLayout;
      var xa = plotinfo.xaxis;
      var ya = plotinfo.yaxis;
  
      var boxtraces = plotinfo.plot.select('.boxlayer')
          .selectAll('g.trace.boxes')
              .data(cdbox)
        .enter().append('g')
          .attr('class', 'trace boxes');
  
      boxtraces.each(function(d) {
          var cd0 = d[0];
          var t = cd0.t;
          var trace = cd0.trace;
          var sel = cd0.node3 = d3.select(this);
          var numBoxes = fullLayout._numBoxes;
  
          var group = (fullLayout.boxmode === 'group' && numBoxes > 1);
          // box half width
          var bdPos = t.dPos * (1 - fullLayout.boxgap) * (1 - fullLayout.boxgroupgap) / (group ? numBoxes : 1);
          // box center offset
          var bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numBoxes) * (1 - fullLayout.boxgap) : 0;
          // whisker width
          var wdPos = bdPos * trace.whiskerwidth;
  
          if(trace.visible !== true || t.empty) {
              d3.select(this).remove();
              return;
          }
  
          var posAxis, valAxis;
  
          if(trace.orientation === 'h') {
              posAxis = ya;
              valAxis = xa;
          } else {
              posAxis = xa;
              valAxis = ya;
          }
  
          // save the box size and box position for use by hover
          t.bPos = bPos;
          t.bdPos = bdPos;
          t.wdPos = wdPos;
  
          // boxes and whiskers
          plotBoxAndWhiskers(sel, {pos: posAxis, val: valAxis}, trace, t);
  
          // draw points, if desired
          if(trace.boxpoints) {
              plotPoints(sel, {x: xa, y: ya}, trace, t);
          }
  
          // draw mean (and stdev diamond) if desired
          if(trace.boxmean) {
              plotBoxMean(sel, {pos: posAxis, val: valAxis}, trace, t);
          }
      });
  }
  
  function plotBoxAndWhiskers(sel, axes, trace, t) {
      var posAxis = axes.pos;
      var valAxis = axes.val;
      var bPos = t.bPos;
      var wdPos = t.wdPos || 0;
      var bPosPxOffset = t.bPosPxOffset || 0;
      var whiskerWidth = trace.whiskerwidth || 0;
  
      // 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;
      }
  
      sel.selectAll('path.box')
          .data(Lib.identity)
          .enter().append('path')
          .style('vector-effect', 'non-scaling-stroke')
          .attr('class', 'box')
          .each(function(d) {
              var pos = d.pos;
              var posc = posAxis.c2p(pos + bPos, true) + bPosPxOffset;
              var pos0 = posAxis.c2p(pos + bPos - bdPos0, true) + bPosPxOffset;
              var pos1 = posAxis.c2p(pos + bPos + bdPos1, true) + bPosPxOffset;
              var posw0 = posAxis.c2p(pos + bPos - wdPos, true) + bPosPxOffset;
              var posw1 = posAxis.c2p(pos + bPos + wdPos, true) + bPosPxOffset;
              var q1 = valAxis.c2p(d.q1, true);
              var q3 = 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(
                  valAxis.c2p(d.med, true),
                  Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
              );
              var lf = valAxis.c2p(trace.boxpoints === false ? d.min : d.lf, true);
              var uf = valAxis.c2p(trace.boxpoints === false ? d.max : d.uf, true);
  
              if(trace.orientation === 'h') {
                  d3.select(this).attr('d',
                      'M' + m + ',' + pos0 + 'V' + pos1 + // median line
                      'M' + q1 + ',' + pos0 + 'V' + pos1 + 'H' + q3 + 'V' + pos0 + 'Z' + // box
                      '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' + pos0 + ',' + m + 'H' + pos1 + // median line
                      'M' + pos0 + ',' + q1 + 'H' + pos1 + 'V' + q3 + 'H' + pos0 + 'Z' + // box
                      '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 pseudorandom number generator
      seed();
  
      sel.selectAll('g.points')
          // since box plot points get an extra level of nesting, each
          // box needs the trace styling info
          .data(function(d) {
              d.forEach(function(v) {
                  v.t = t;
                  v.trace = trace;
              });
              return d;
          })
          .enter().append('g')
          .attr('class', 'points')
        .selectAll('path')
          .data(function(d) {
              var i;
  
              var pts = mode === 'all' ?
                  d.pts :
                  d.pts.filter(function(pt) { return (pt.v < d.lf || pt.v > d.uf); });
  
              // 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;
              }
  
              // 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] * (rand() - 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;
          })
          .enter().append('path')
          .classed('point', true)
          .call(Drawing.translatePoints, xa, ya);
  }
  
  function plotBoxMean(sel, axes, trace, t) {
      var posAxis = axes.pos;
      var valAxis = axes.val;
      var bPos = t.bPos;
      var bPosPxOffset = t.bPosPxOffset || 0;
  
      // 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;
      }
  
      sel.selectAll('path.mean')
          .data(Lib.identity)
          .enter().append('path')
          .attr('class', 'mean')
          .style({
              fill: 'none',
              'vector-effect': 'non-scaling-stroke'
          })
          .each(function(d) {
              var posc = posAxis.c2p(d.pos + bPos, true) + bPosPxOffset;
              var pos0 = posAxis.c2p(d.pos + bPos - bdPos0, true) + bPosPxOffset;
              var pos1 = posAxis.c2p(d.pos + bPos + bdPos1, true) + 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 +
                      (trace.boxmean === 'sd' ?
                          'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z' :
                          '')
                  );
              } else {
                  d3.select(this).attr('d',
                      'M' + pos0 + ',' + m + 'H' + pos1 +
                      (trace.boxmean === 'sd' ?
                          'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z' :
                          '')
                  );
              }
          });
  }
  
  module.exports = {
      plot: plot,
      plotBoxAndWhiskers: plotBoxAndWhiskers,
      plotPoints: plotPoints,
      plotBoxMean: plotBoxMean
  };
  