function findExtremes(ax, data, opts) {
    if(!opts) opts = {};
    if(!ax._m) ax.setScale();

    var minArray = [];
    var maxArray = [];

    var len = data.length;
    var extrapad = opts.padded || false;
    var tozero = opts.tozero && (ax.type === 'linear' || ax.type === '-');
    var isLog = ax.type === 'log';
    var hasArrayOption = false;
    var vpadLinearized = opts.vpadLinearized || false;
    var i, v, di, dmin, dmax, ppadiplus, ppadiminus, vmin, vmax;

    function makePadAccessor(item) {
        if(Array.isArray(item)) {
            hasArrayOption = true;
            return function(i) { return Math.max(Number(item[i]||0), 0); };
        } else {
            var v = Math.max(Number(item||0), 0);
            return function() { return v; };
        }
    }

    var ppadplus = makePadAccessor((ax._m > 0 ?
        opts.ppadplus : opts.ppadminus) || opts.ppad || 0);
    var ppadminus = makePadAccessor((ax._m > 0 ?
        opts.ppadminus : opts.ppadplus) || opts.ppad || 0);
    var vpadplus = makePadAccessor(opts.vpadplus || opts.vpad);
    var vpadminus = makePadAccessor(opts.vpadminus || opts.vpad);

    if(!hasArrayOption) {
        // with no arrays other than `data` we don't need to consider
        // every point, only the extreme data points
        vmin = Infinity;
        vmax = -Infinity;

        if(isLog) {
            for(i = 0; i < len; i++) {
                v = data[i];
                // data is not linearized yet so we still have to filter out negative logs
                if(v < vmin && v > 0) vmin = v;
                if(v > vmax && v < FP_SAFE) vmax = v;
            }
        } else {
            for(i = 0; i < len; i++) {
                v = data[i];
                if(v < vmin && v > -FP_SAFE) vmin = v;
                if(v > vmax && v < FP_SAFE) vmax = v;
            }
        }

        data = [vmin, vmax];
        len = 2;
    }

    var collapseOpts = {tozero: tozero, extrapad: extrapad};

    function addItem(i) {
        di = data[i];
        if(!isNumeric(di)) return;
        ppadiplus = ppadplus(i);
        ppadiminus = ppadminus(i);

        if(vpadLinearized) {
            dmin = ax.c2l(di) - vpadminus(i);
            dmax = ax.c2l(di) + vpadplus(i);
        } else {
            vmin = di - vpadminus(i);
            vmax = di + vpadplus(i);
            // special case for log axes: if vpad makes this object span
            // more than an order of mag, clip it to one order. This is so
            // we don't have non-positive errors or absurdly large lower
            // range due to rounding errors
            if(isLog && vmin < vmax / 10) vmin = vmax / 10;

            dmin = ax.c2l(vmin);
            dmax = ax.c2l(vmax);
        }

        if(tozero) {
            dmin = Math.min(0, dmin);
            dmax = Math.max(0, dmax);
        }
        if(goodNumber(dmin)) {
            collapseMinArray(minArray, dmin, ppadiminus, collapseOpts);
        }
        if(goodNumber(dmax)) {
            collapseMaxArray(maxArray, dmax, ppadiplus, collapseOpts);
        }
    }

    // For efficiency covering monotonic or near-monotonic data,
    // check a few points at both ends first and then sweep
    // through the middle
    var iMax = Math.min(6, len);
    for(i = 0; i < iMax; i++) addItem(i);
    for(i = len - 1; i >= iMax; i--) addItem(i);

    return {
        min: minArray,
        max: maxArray,
        opts: opts
    };
}
findExtremes

Find min/max extremes of an array of coordinates on a given axis.

Note that findExtremes is called during `calc`, when we don't yet know the axis
length; all the inputs should be based solely on the trace data, nothing
about the axis layout.

Note that `ppad` and `vpad` as well as their asymmetric variants refer to
the before and after padding of the passed `data` array, not to the whole axis.

@param {object} ax: full axis object
  relies on
  - ax.type
  - ax._m (just its sign)
  - ax.d2l
@param {array} data:
 array of numbers (i.e. already run though ax.d2c)
@param {object} opts:
 available keys are:
     vpad: (number or number array) pad values (data value +-vpad)
     ppad: (number or number array) pad pixels (pixel location +-ppad)
     ppadplus, ppadminus, vpadplus, vpadminus:
         separate padding for each side, overrides symmetric
     padded: (boolean) add 5% padding to both ends
         (unless one end is overridden by tozero)
     tozero: (boolean) make sure to include zero if axis is linear,
         and make it a tight bound if possible
     vpadLinearized: (boolean) whether or not vpad (or vpadplus/vpadminus)
         is linearized (for log scale axes)

@return {object}
 - min {array of objects}
 - max {array of objects}
 each object item has fields:
   - val {number}
   - pad {number}
   - extrappad {number}
 - opts {object}: a ref to the passed "options" object