Skip to content
66 changes: 66 additions & 0 deletions src/components/errorbars/calc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright 2012-2015, 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 isNumeric = require('fast-isnumeric');

var Plots = require('../../plots/plots');
var Axes = require('../../plots/cartesian/axes');

var compureError = require('./compute_error');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

computeError perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch. Thanks



module.exports = function calc(gd) {
var calcdata = gd.calcdata;

for(var i = 0; i < calcdata.length; i++) {
var calcTrace = calcdata[i],
trace = calcTrace[0].trace;

if(!Plots.traceIs(trace, 'errorBarsOK')) continue;

var xObj = trace.error_x || {},
yObj = trace.error_y || {},
xa = Axes.getFromId(gd, trace.xaxis),
ya = Axes.getFromId(gd, trace.yaxis),
xVis = xObj.visible && ['linear', 'log'].indexOf(xa.type)!==-1,
yVis = yObj.visible && ['linear', 'log'].indexOf(ya.type)!==-1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing between !== and 1 for readability.


if(!xVis && !yVis) continue;

var xVals = [],
yVals = [];

for(var j = 0; j < calcTrace.length; j++) {
var calcPt = calcTrace[j],
calcX = calcPt.x,
calcY = calcPt.y;

if(!isNumeric(ya.c2l(calcY)) || !isNumeric(xa.c2l(calcX))) continue;

var errorY = compureError(calcY, j, yObj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compureError here too

if(isNumeric(errorY[0]) && isNumeric(errorY[1])) {
calcPt.ys = calcY - errorY[0];
calcPt.yh = calcY + errorY[1];
yVals.push(calcPt.ys, calcPt.yh);
}

var errorX = compureError(calcX, j, xObj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

if(isNumeric(errorX[0]) && isNumeric(errorX[1])) {
calcPt.xs = calcX - errorX[0];
calcPt.xh = calcX + errorX[1];
xVals.push(calcPt.xs, calcPt.xh);
}
}

Axes.expand(ya, yVals, {padded: true});
Axes.expand(xa, xVals, {padded: true});
}
};
53 changes: 53 additions & 0 deletions src/components/errorbars/compute_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright 2012-2015, 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';


/**
* Compute the positive and negative error bar magnitudes.
*
* N.B. This function does not clean the dataPt entries, non-numeric
* entries result in undefined *error*
*
* @param {numeric} dataPt data point from where to compute the error magnitue
* @param {number} index index of dataPt in its corresponding data array
* @param {object} opts error bar attributes
*
* @return {array of two numbers}
* - error[0] : error magnitude in the negative direction
* - error[1] : " " " " positive "
*/
module.exports = function computeError(dataPt, index, opts) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reusable error bar data/constant/sqrt/percent logic

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Don't need to do it now, but it occurs to me we could boost performance by just evaluating opts once and returning a more minimal function to evaluate in the loop:

function computeError(opts) {
    if(type === 'data') {
        if(opts.symmetric || opts.arrayminus === undefined) {
            var array = opts.array
            return function(dataPt, index) {
                var val = +(array[index]);
                return [val, val];
            }
        }
        // etc...
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all about the 🐎

var type = opts.type,
error = new Array(2);

if(type === 'data') {
error[1] = +(opts.array[index]);
error[0] = (opts.symmetric || opts.arrayminus === undefined) ?
error[1] :
+(opts.arrayminus[index]);
}
else {
error[1] = getErrorVal(type, dataPt, opts.value);
error[0] = (opts.symmetric || opts.valueminus === undefined) ?
error[1] :
getErrorVal(type, dataPt, opts.valueminus);
}


return error;
};

// size the error bar itself (for all types except data)
function getErrorVal(type, dataPt, value) {
if(type === 'percent') return Math.abs(dataPt * value / 100);
if(type === 'constant') return Math.abs(value);
if(type === 'sqrt') return Math.sqrt(Math.abs(dataPt));
}
75 changes: 75 additions & 0 deletions src/components/errorbars/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright 2012-2015, 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 isNumeric = require('fast-isnumeric');

var Plots = require('../../plots/plots');
var Lib = require('../../lib');

var attributes = require('./attributes');


module.exports = function(traceIn, traceOut, defaultColor, opts) {
var objName = 'error_' + opts.axis,
containerOut = traceOut[objName] = {},
containerIn = traceIn[objName] || {};

function coerce (attr, dflt) {
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
}

var hasErrorBars = (
containerIn.array !== undefined ||
containerIn.value !== undefined ||
containerIn.type === 'sqrt'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

);

var visible = coerce('visible', hasErrorBars);

if(visible === false) return;

var type = coerce('type', 'array' in containerIn ? 'data' : 'percent'),
symmetric = true;

if(type !== 'sqrt') {
symmetric = coerce('symmetric',
!((type === 'data' ? 'arrayminus' : 'valueminus') in containerIn));
}

if(type === 'data') {
var array = coerce('array');
if(!array) containerOut.array = [];
coerce('traceref');
if(!symmetric) {
var arrayminus = coerce('arrayminus');
if(!arrayminus) containerOut.arrayminus = [];
coerce('tracerefminus');
}
}
else if(type==='percent' || type==='constant') {
coerce('value');
if(!symmetric) coerce('valueminus');
}

var copyAttr = 'copy_'+opts.inherit+'style';
if(opts.inherit) {
var inheritObj = traceOut['error_' + opts.inherit];
if((inheritObj||{}).visible) {
coerce(copyAttr, !(containerIn.color ||
isNumeric(containerIn.thickness) ||
isNumeric(containerIn.width)));
}
}
if(!opts.inherit || !containerOut[copyAttr]) {
coerce('color', defaultColor);
coerce('thickness');
coerce('width', Plots.traceIs(traceOut, 'gl3d') ? 0 : 4);
}
};
160 changes: 2 additions & 158 deletions src/components/errorbars/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,165 +17,9 @@ var errorBars = module.exports = {};

errorBars.attributes = require('./attributes');

errorBars.supplyDefaults = function(traceIn, traceOut, defaultColor, opts) {
var objName = 'error_' + opts.axis,
containerOut = traceOut[objName] = {},
containerIn = traceIn[objName] || {};
errorBars.supplyDefaults = require('./defaults');

function coerce (attr, dflt) {
return Plotly.Lib.coerce(containerIn, containerOut, errorBars.attributes, attr, dflt);
}

var visible = coerce('visible', 'array' in containerIn || 'value' in containerIn);
if(visible) {
var type = coerce('type', 'array' in containerIn ? 'data' : 'percent'),
symmetric = true;
if(type!=='sqrt') {
symmetric = coerce('symmetric',
!((type==='data' ? 'arrayminus' : 'valueminus') in containerIn));
}

if(type==='data') {
var array = coerce('array');
if(!array) containerOut.array = [];
coerce('traceref');
if(!symmetric) {
var arrayminus = coerce('arrayminus');
if(!arrayminus) containerOut.arrayminus = [];
coerce('tracerefminus');
}
}
else if(type==='percent' || type==='constant') {
coerce('value');
if(!symmetric) coerce('valueminus');
}

var copyAttr = 'copy_'+opts.inherit+'style';
if(opts.inherit) {
var inheritObj = traceOut['error_' + opts.inherit];
if((inheritObj||{}).visible) {
coerce(copyAttr, !(containerIn.color ||
isNumeric(containerIn.thickness) ||
isNumeric(containerIn.width)));
}
}
if(!opts.inherit || !containerOut[copyAttr]) {
coerce('color', defaultColor);
coerce('thickness');
coerce('width', Plotly.Plots.traceIs(traceOut, 'gl3d') ? 0 : 4);
}
}
};

errorBars.pushRef2GDC = function(gd, selCurve, astr, val){
// Copy the error bar data into gdc
// This is called from the style-box, where
// either the reference trace was selected.
// This function copies the data from the referenced trace
// into the gdc object
// selCurve: the selected curve (i.e. gdc = gd[selCurve])
// astr: the string that was modified
var iRef,
various = false,
parts = astr.split('.'),
container = parts[0],
attr = parts[1],
letter = container.charAt(container.length-1);
if(attr==='type'){
if(selCurve==='various'){
various = true;
selCurve = 0;
}
// if the 'trace' type was just selected
iRef = Number(gd.calcdata[Number(selCurve)][0].trace['error_' + letter].traceref)||0;
}
else if(attr==='traceref' || attr==='tracerefminus'){
if(selCurve==='various') various = true;
// if the trace reference was just modified
iRef = Number(val)||0;
}

// now copy the appropriate referenced error bar data into gdc
// TODO: do this through restyle so we can undo it
// the error bar data that we're referencing
var newdata = gd.data[iRef][letter].map(Number);

function setarrays(i) {
var eb = gd.data[i][container];
eb[attr==='tracerefminus' ? 'arrayminus' : 'array'] = newdata;
}

if(!various) setarrays(Number(selCurve));
else{
// copy all of the data
// TODO: this won't work right if we just select some traces, right?
for(var i=0; i<gd.data.length; i++){ setarrays(i); }
}
};

// size the error bar itself (for all types except data)
function errorval(type, dataval, errval) {
if(type === 'percent') return Math.abs(dataval * errval / 100);
if(type === 'constant') return Math.abs(errval);
if(type === 'sqrt') return Math.sqrt(Math.abs(dataval));

return 0;
}

errorBars.calc = function(gd) {
(gd.calcdata||[]).forEach(function(cdi){
var trace = cdi[0].trace;

if(!Plotly.Plots.traceIs(trace, 'errorBarsOK')) return;

var xObj = trace.error_x || {},
yObj = trace.error_y || {},
xa = Plotly.Axes.getFromId(gd, trace.xaxis),
ya = Plotly.Axes.getFromId(gd, trace.yaxis),
xvis = xObj.visible && ['linear', 'log'].indexOf(xa.type)!==-1,
yvis = yObj.visible && ['linear', 'log'].indexOf(ya.type)!==-1;

if(!xvis && !yvis) return;

var xvals = [],
yvals = [];

cdi.forEach(function(d,j) {
try {
if(isNumeric(ya.c2l(d.y)) && isNumeric(xa.c2l(d.x))){
[
{letter:'x', obj: xObj, visible: xvis, vals: xvals},
{letter:'y', obj: yObj, visible: yvis, vals: yvals}
].forEach(function(o){
if(o.visible) {
var dataVal = d[o.letter],
obj = o.obj,
ep, en;
if(o.obj.type==='data') {
ep = Number(obj.array[j]);
en = (obj.symmetric || !('arrayminus' in obj)) ?
ep : Number(obj.arrayminus[j]);
}
else {
ep = errorval(obj.type, dataVal, obj.value);
en = (obj.symmetric || !('valueminus' in obj)) ?
ep : errorval(obj.type, dataVal, obj.valueminus);
}
if(isNumeric(ep) && isNumeric(en)) {
var shoe = d[o.letter + 'h'] = dataVal + ep;
var hat = d[o.letter + 's'] = dataVal - en;
o.vals.push(shoe, hat);
}
}
});
}
}
catch(e) { console.log(e); }
});
Plotly.Axes.expand(ya, yvals, {padded: true});
Plotly.Axes.expand(xa, xvals, {padded: true});
});
};
errorBars.calc = require('./calc');

errorBars.calcFromTrace = function(trace, layout) {
var x = trace.x || [],
Expand Down
Loading