var my = {};
// start with some general-purpose utilities
// L means Lookup the ID;
// return (a pointer to) the specified object:
my.L = function(ID){
return document.getElementById(ID);
}
// N means grab the Numerical value of the
// named by the ID.
my.N = function(ID){
return Number(document.getElementById(ID).value);
}
// Avoid using log10, because IE11 does not support it:
my.log10 = function(x) {
return Math.log(x)/Math.log(10);
}
// Gaussian cumulative probability distribution:
// aka normalized error function
my.gauss_cume = function(x, mu, sigma) {
return 0.5 + 0.5 * my.erf((x - mu)/sigma/Math.sqrt(2));
}
// Gaussian probability density distribution:
my.gauss_density = function(x, mu, sigma) {
var disp = (x - mu)/sigma;
return Math.exp(-disp*disp/2)/Math.sqrt(2*Math.PI)/sigma;
}
// Meld zero or more objects by shallow copying (depth=1).
// That means if the object contains pointers,
// the pointers are only copied, not cloned.
// http://stackoverflow.com/questions/728360/
my.meld = function(){
rslt = {};
for (var ii = 0; ii < arguments.length; ii++) {
for (attr in arguments[ii]) {
rslt[attr] = arguments[ii][attr];
}
}
return rslt;
}
// Just like indexing into an array,
// but accepts /non-integer/ indices.
// Useful when computing medians or other percentiles.
// We require (xx >=0) and (xx <= arr.length-1).
my.interp = function(arr, xx){
if ('undefined' === typeof xx) return undefined;
if (xx < 0) return undefined;
if (xx > arr.length-1) return undefined;
var lo = arr[Math.floor(xx)];
var hi = arr[Math.ceil (xx)];
var frac = xx - Math.floor(xx);
return (1-frac)*lo + frac*hi;
}
// This is the inverse of my.interp :
// given an array and a value, returns an
// index (likely not an integer) into the array.
//
// If numerical, extrap mode means out-of-range values are OK;
// return index of last element if too big;
// return extrap itself if too small.
// The only values that make sense are
// -1, 0, and non-numerical.
my.inv_interp = function(arr, xx, extrap){
if ('undefined' === typeof xx) return undefined;
var toolow = undefined;
var toohigh = undefined;
if ('number' === typeof xx) {
toolow = extrap;
toohigh = arr.length-1;
}
var where = my.binarySearch(arr, xx);
if (where >= 0) return where; // direct hit
if (where === -1) return toolow; // (-where) - 2 would not exist
if (-where > arr.length) return toohigh; // (-where) - 1 would not exist
// these indices should bracket the needle:
var loguy = (-where) - 2;
var higuy = (-where) - 1;
var delta = arr[higuy] - arr[loguy];
if (0 == delta) return higuy; // or loguy, since they're the same
var frac = (xx - arr[loguy]) / delta;
return (1-frac)*loguy + frac*higuy;
}
// for testing the interpolators
bar = [1, 3, 5, 10, 20, 21];
intest = function(xx){
yy = my.inv_interp(bar, xx, 0);
xnew = my.interp(bar, yy);
console.log("-->", xx, yy, xnew);
return '';
}
// Schedule something to happen in the future.
// The func can be either a simple function,
// or a list [my, object, function] representing my.object.function
// The _env will be the "this" pointer when the func is called.
//
my.schedule = function(dt, _func, _env, _arglist){
var cb = (function (){ // define a callback function
var func = _func; // simple function, not a list
if ('object' === typeof _func) {
func = window;
for (var ii = 0; ii < _func.length; ii++){
func = func[_func[ii]];
}
}
var env = _env;
var arglist = _arglist;
return function() {
return func.apply(env, arglist);
}
})();
setTimeout(cb, dt);
}
// fortran behavior, i.e. rounding toward zero,
// which is actually what we want when formatting numerals
my.maground = function(foo){
var bar = Math.round(Math.abs(foo));
if (foo < 0) return -bar;
return bar;
}
// Round a number to the specified number of decimal places
// i.e. places after the decimal point.
// Places can be positive, negative or zero.
// It needs to be an integer.
my.rndnumpl = function(val, places){
if (places > 12) return val;
var factor = Math.pow(10, -places);
return my.maground(val/factor)*factor;
}
// Same as above, but returns a formatted string,
// possibly in e-format.
my.rndFmt = function(val, places){
return my.numFmt(my.rndnumpl(val, places));
}
my.numFmt = function(arg) {
var nn = arg > 1e5 ? arg.toExponential(10) : arg.toPrecision(10);
nn = nn.split(/e/i);
if (nn[0].search(/[.]/) >= 0) {
nn[0] = nn[0].replace(/0*$/, "");
nn[0] = nn[0].replace(/[.]$/, "");
}
if (nn.length > 1) {
nn[1] = nn[1].replace(/[+]/, "");
}
return nn.join("e");
}
my.BoxMuller = (function() {
var vals = [];
function calc() {
var theta = (1 - Math.random()) * 2 * Math.PI,
x = (1 - Math.random()),
r = Math.sqrt(-2 * Math.log(x));
return [
r * Math.sin(theta),
r * Math.cos(theta)
];
}
return function() {
if (vals.length == 0) vals = calc();
return vals.pop();
}
})();
my.binarySearch = function(ar, el, greater) {
if ('undefined' === typeof greater)
greater = function(a,b){return a-b};
var m = 0;
var n = ar.length - 1;
while (m <= n) {
var k = (n + m) >> 1;
var cmp = greater(el, ar[k]);
if (cmp > 0) {
m = k + 1;
} else if(cmp < 0) {
n = k - 1;
} else {
return k;
}
}
return -m - 1;
}
my.erf = function(x) {
// constants
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var p = 0.3275911;
// Save the sign of x
var sign = 1;
if (x < 0) {
sign = -1;
}
x = Math.abs(x);
// A&S formula 7.1.26
var t = 1.0/(1.0 + p*x);
var y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-x*x);
return sign*y;
}
// A useful constant.
my.erf1sigma = .5 + .5*my.erf(1/Math.sqrt(2));
// A quasi-generalization of the 'typeof' operator.
// However, thing has to be defined.
// Otherwise you won't even be able to call what(garbage),
// so we won't even get control.
what = function(thing){
var str = Object.prototype.toString.call(thing);
str = str.replace(/^\[object /, "");
str = str.replace(/\]$/, "");
return str;
}
/////////////////////////
// now for some stuff specific to this application
my.keyToFancy = [[],[]];
my.fancyToPlain = {};
(function() {
var setup = function(key, fancy, plain, shift){
shift = shift || 0;
my.keyToFancy[shift][key.charCodeAt()] = fancy;
if (plain !== 0)
my.fancyToPlain[fancy] = [new RegExp(fancy, "g"), plain];
}
setup('0', "°", "*degree" );
setup('8', "∞", "Infinity" );
setup('p', "π", 0 ); // doesn't get replaced
setup('x', "×", "*" );
setup('.', "⋅", "*" );
setup('/', "÷", "/" );
setup('[', "⌊", "(" );
setup(']', "⌋", ")" );
// shifted key "=" is the same as char "+"
// shifted + is the same as any other +:
setup('+', "⊕", "^", 1 ); // exclusive or
setup('=', "⊕", "^", 1 ); // exclusive or
// shifted keys "[]" are the same as chars "{}"
// shifted {} are the same as any other {}:
setup('[', "⌈", "(", 1 );
setup(']', "⌉", ")", 1 );
setup('{', "⌈", "(", 1 );
setup('}', "⌉", ")", 1 );
// apple gives us a code modified by Ctrl (when possible),
// but unmodified by Shift.
// linux does just the reverse.
})();
// Called on any keypress in any of the input windows.
// Check for "=" and for
my.commonkey = function(elem, ev, eqmode){
if (!ev) { ev = window.event; }
var code = ev.keyCode || ev.charCode;
var str = "";
if (ev.ctrlKey) str += "Ctrl-";
if (ev.altKey) str += "Alt-";
if (ev.shiftKey) str += "Shift-";
str += String.fromCharCode(code) +":";
str += ev.keyCode +"|"+ ev.charCode +"="+ code;
if (1) my.L("sec-special").value = str;
if (code == 13) {
my.crunch({clearplot: !ev.shiftKey});
return;
}
if (code == 61) {
var id = ev.target.id;
if (eqmode === 'eqok') return;
if (eqmode !== '=') {
//!!! console.log("unacceptable '=' in input box:", id);
ev.preventDefault();
return;
}
my.lop(id);
ev.preventDefault();
return;
}
if (ev.ctrlKey && ev.altKey) {
if (code < 32) code += 96; // convert Ctrl-X to x
var newch = my.keyToFancy[ev.shiftKey?1:0][code];
if ('undefined' !== typeof newch)
my.insertAtCursor(elem, newch);
ev.preventDefault(); // absorb all Ctrl-Alt events
return;
}
}
my.insertAtCursor = function(box, text) {
var start = 0;
var end = 0;
// we are not checking for ff in particular;
// we only care whether it provides the relevant features
var br = (box.selectionStart >= 0) ? "ff-like" :
document.selection ? "ie" : "other";
if (br == "ff-like") {
start = box.selectionStart;
end = box.selectionEnd;
} else if (br == "ie") {
box.focus();
var range = document.selection.createRange();
var len = range.text.length;
range.moveStart ('character', -box.value.length);
start = range.text.length;
end = start + len;
} else {
console.log("no selection support for this browser");
if ('undefined' !== typeof box.value)
box.value += text; // stick text at end and hope for the best
else if ('undefined' !== typeof box.innerHTML)
box.innerHTML += text;
else {} // ran out of ideas.
return;
}
var front = (box.value).substring(0, start);
var back = (box.value).substring(end, box.value.length);
box.value = front + text + back;
// new end value:
end = start + text.length;
if (br == "ff-like") {
box.selectionStart = end;
box.selectionEnd = end;
box.focus();
} else {
box.focus();
var ieRange = document.selection.createRange();
ieRange.moveStart ('character', -box.value.length);
ieRange.moveStart ('character', end);
ieRange.moveEnd ('character', 0);
ieRange.select();
}
}
// Given an input numeral (in text form), convert it to a number.
// It may be specified
// -- in absolute terms, or
// -- in relative terms, i.e. as a percentage or ppm of the value.
//
my.get_abs_rel = function(nom, bar){
var value;
do { // easy way to implement "goto end"
var where;
where = bar.search(/% *$/); // percentage
if (where >= 0) {
value = nom * Number(bar.substr(0, where)) / 100;
break;
}
where = bar.search(/ppm *$/i); // ppm
if (where >= 0) {
value = nom * Number(bar.substr(0, where)) / 1e6;
break;
}
// otherwise, it must be absolute
value = Number(bar);
break;
} while (0);
if (isNaN(value)) return 0;
return value;
}
// Constructor for "distro" objects.
// There is one such object for each input variable.
//
// The xmkr argument is "moniker:alias"
// We rely on the moniker for all internal operations.
// The alias is what we show the user.
// (Right now there are no aliases.)
// If no explicit alias, than the moniker serves as the alias also.
my.distro = function(my, xmkr, active, sticky){
// decode the xmkr argument
var parse = xmkr.split(":");
var mkr = parse[0]; // the moniker
var newalias = this.mkr = mkr;
if (parse.length > 1) newalias = parse[1];
// item[0] in this list should correspond to the
// built-in shape in the HTML tag:
this.shapelist = ["./img48/rectangular-distro.png",
"./img48/gaussian-distro.png"];
this.active = active || 0;
this.sticky = sticky || 0;
this.idx = -1;
var _alias = my.L(mkr + "-alias");
if (_alias) {
_alias.style.fontSize = my.normalsize;
_alias.innerHTML = newalias;
}
//++ Choose what type of distribution to use for this input
// (rectangular or Gaussian).
this.set_shape = function(sh){
var _shape = my.L(mkr + "-shape")
if (!_shape) return;
//// console.log(mkr, "called with", sh, this.shape);
shape = this.shape || 0; // clean up initial state
if ('undefined' !== typeof sh) shape = sh;
shape %= this.shapelist.length;
my.maybeSetSrc(_shape, my.mc_mode ?
this.shapelist[shape] : '');
this.shape = shape;
}
//++ Load nominal value and error bars:
this.sync = function(){
this.raw_nominal = my.L(mkr + "-nom").value;
this.nominal = Number(this.raw_nominal);
this.raw_hibar = my.L(mkr + "-hibar").value;
this.hibar = my.get_abs_rel(this.nominal, this.raw_hibar);
var _lorow = my.L(mkr + "-lorow");
if (_lorow) {
if (_lorow.style.visibility == "visible") {
//xx console.log(mkr, "is visible");
this.raw_lobar = my.L(mkr + "-lobar").value
this.lobar = my.get_abs_rel(this.nominal, this.raw_lobar);
} else {
//xx console.log(mkr, "is not visible");
this.raw_lobar = this.raw_hibar;
this.lobar = this.hibar;
// defend against weird things happening if somebody reloads the page:
my.L(mkr + "-lobar").value = my.L(mkr + "-hibar").value;
}
}
if (this.active) {
this.set_shape();
}
}
//++
this.curpoint = function(){
if (this.idx < 0) return this.nominal + this.lobar*this.idx;
return this.nominal + this.hibar*this.idx;
}
this.alias = function(){
var _alias = my.L(mkr + "-alias");
var alias = _alias ? _alias.innerHTML : this.mkr;
alias = alias.replace(/ $/, ""); // work around disgusting firefox bug
//// console.log("alias: ", mkr, "->", alias);
return alias;
}
//++ Return a snippet of code (suitable for "new Function()")
// to set this variable.
this.ucode = function(){
return "var " + this.alias() + "=xmy.map['" + mkr + "'].curpoint();";
}
//////////////////////
// functions have been defined; now do some actual work
// call this *after* setting the active variable:
this.sync(); // set the initial shape, among other things
my.map[this.mkr] = this;
}
// Show or hide the secondary error-bar row.
// That includes deciding whether to
// (a) make it invisible in place, or
// (b) make it take up no room at all.
//
// If showme is undefined, show it if and only if the values are unequal.
//
// Called from my.lop and from elsewhere.
//
my.minus_showhide_1 = function(mkr, showme){
var _lobar = my.L(mkr + "-lobar");
if (!_lobar) return; // easy: no secondary bar at all
var _hibar = my.L(mkr + "-hibar");
var equal = _hibar.value === _lobar.value;
if ('undefined' === typeof showme) {
showme = !equal;
}
var sticky = my.map[mkr] ? my.map[mkr].sticky : 0;
// sticky&2 means requires mc_mode
var possible = my.mc_mode || !(sticky & 2);
var vis = possible ? "visible" : "hidden";
var row = possible ? "table-row" : "none";
if (!showme) {
// make the low row invisible:
my.L(mkr + "-plus").innerHTML="±";
my.L(mkr + "-lorow").style.visibility = "hidden";
var _warn = my.L(mkr + "-warning");
if (_warn) _warn.style.visibility = "hidden";
// should it take up any room at all?
var xrow = (sticky && possible) ? "table-row" : "none";
// impossible takes precedence over sticky
my.L(mkr + "-lorow").style.display = xrow;
} else {
// make the low row visible, unless that is impossible:
my.L(mkr + "-plus").innerHTML="+";
my.L(mkr + "-lorow").style.visibility = vis;
my.L(mkr + "-lorow").style.display = row;
// The row can be visible even if the bars are equal,
// e.g. if the user toggles the visibility.
var _warn = my.L(mkr + "-warning");
if (_warn) _warn.style.visibility = !equal ? "visible" : "hidden";
}
}
// Show or hide *all* the secondary error bars
my.minus_showhide = function(){
for (var ii = 0; ii < my.allvar.length; ii++) {
// strange to pass the moniker rather than the object...
my.minus_showhide_1(my.allvar[ii].mkr);
}
}
// Format and output a percentage into the specified box.
// Will not report anything bigger than 500%
// The box is a or some such with an innerHTML
// (as opposed to an with a value).
my.set_pct = function(nominal, val, box){
if (isNaN(nominal)
|| isNaN(val)
|| 'number' !== typeof nominal
|| 'number' !== typeof val
|| nominal == 0
|| Math.abs(nominal) <= 0.2*val){
my.L(box).innerHTML = "";
return;
}
var fudge = 2;
var pct = val / Math.abs(nominal);
if (pct < .001) fudge = 6; // ppm
pct_mag = Math.floor(my.log10(Math.abs(pct)));
var places = 1 - pct_mag - fudge;
pct = my.rndFmt(Math.pow(10,fudge)*pct, places);
var flag = (fudge==2) ? "%" : "ppm";
my.L(box).innerHTML = "(" + pct + " " + flag + ")"
}
// Calculate error bars in 2^N+1 mode (corner mode).
// The "payload" section is performed at each corner of the hypercube.
my.corner_bars = function(nominal, onepoint) {
for (var ii = 0; ii < my.invar.length; ii++) {
my.invar[ii].idx = -1;
}
var rmin = nominal;
var rmax = nominal;
// Implement a counter, with arbitrarily many "digits"
// Each digit is a js number unto itself.
// The while-loop is executed exponentialy many times
// (exponential in the number of "digits")
while (1) { // loop over all corners of the hypercube
var ii = my.invar.length - 1;
if (0){ // debug initial state
var word = "";
for (var jj = 0; jj < my.invar.length; jj++) {
if (my.invar[jj].idx < 0) word += "-";
if (my.invar[jj].idx == 0) word += "0";
if (my.invar[jj].idx > 0) word += "+";
}
console.log(word, ii);
}
// Payload, i.e. what to do with each value.
var tmp = onepoint(); // evaluate this corner
rmin = Math.min(rmin, tmp);
rmax = Math.max(rmax, tmp);
// End of payload
// Increment the counter
var carryout;
// Loop over all possible carries.
// This loop is executed linearly many times per corner.
for (; ii >= 0; ii--) {
carryout = (my.invar[ii].idx *= -1) <= 0;
if (!carryout) break;
}
if (ii <= 0 && carryout) break;
}
return [nominal - rmin, rmax - nominal, nominal];
}
// calculate error bars in 2N+1 mode (axis mode)
my.axis_bars = function(nominal, onepoint) {
for (var ii = 0; ii < my.invar.length; ii++) {
my.invar[ii].idx = 0;
}
var rmin = nominal;
var rmax = nominal;
for (var ii = 0; ii < my.invar.length; ii++) {
var tmp;
my.invar[ii].idx = -1;
// Payload for lower bar
tmp = onepoint();
rmin = Math.min(rmin, tmp);
rmax = Math.max(rmax, tmp);
// End of lower payload
my.invar[ii].idx = 1;
// Payload for upper bar
tmp = onepoint();
rmin = Math.min(rmin, tmp);
rmax = Math.max(rmax, tmp);
// End of upper payload
my.invar[ii].idx = 0; // put away our toys
}
return [nominal - rmin, rmax - nominal, nominal];
}
// Set an input variable in the gui.
// Optionally also set its error bars and distribution-shape.
my.setv = function(xmkr, val, hibar, lobar, shape){
var parse = xmkr.split(":");
var mkr = parse[0];
//xx if (mkr == "tform") console.log("setv>", xmkr, val);
var alias = parse.length > 1 ? parse[1] : parse[0];
var _alias = my.L(mkr + "-alias");
if (_alias && _alias.getAttribute("contenteditable")) {
_alias.innerHTML = alias;
}
if ('undefined' !== typeof val) {
var _nom = my.L(mkr + "-nom")
if (_nom) _nom.value = val;
}
if ('undefined' !== typeof hibar) {
my.L(mkr + "-hibar").value = hibar;
var _hisigma = my.L(mkr + "-hisigma")
if (_hisigma) _hisigma.value = hibar;
if ('undefined' === typeof lobar || '=' === lobar) lobar = hibar;
var _lobar = my.L(mkr + "-lobar");
if (_lobar) _lobar.value = lobar;
var _losigma = my.L(mkr + "-losigma")
if (_losigma) _losigma.value = lobar;
}
var mapped = my.map[mkr];
if (mapped) mapped.set_shape(shape || 0);
}
my.set_tristate_1 = function(hard, mkr, _canon) {
if (!my[mkr]) {
my[mkr] = {elem: my.L(mkr), saved: _canon, canon: _canon};
// The current value will be cur.value,
// so let's find the appropriate cur object,
// or create one if necessary:
my[mkr].cur = ('undefined' !== typeof my[mkr].elem.value) ?
my[mkr].elem : {};
}
my.maybeSave(mkr); // see if user wants to set the value
if (hard || '' === my[mkr].saved) {
//uu console.log("hardsetting", mkr, _canon);
my[mkr].saved = _canon; // reset the saved value itself
}
// here if hard or soft:
my[mkr].cur.value = my[mkr].saved; // set the current value to the saved value
}
// Reset each tristate variable to either
// its saved value or its built-in canonical value.
my.set_tristates = function(hard) {
my.set_tristate_1(hard, "mc-npts", "1e6");
my.set_tristate_1(hard, "histo-nbins", 150);
my.set_tristate_1(hard, "diaspo-button", 0);
my.set_tristate_1(hard, "refs-button", 0);
my.set_tristate_1(hard, "cornax-mode", 0);
my.set_refs(my['refs-button'].cur.value);
my.set_diaspo(my['diaspo-button'].cur.value);
my.set_cornax(my['cornax-mode'].cur.value);
}
// Invoked by Reset button.
// For tristate inputs, soft means reset them to their saved value,
// whereas hard means reset them to their canonical value.
my.reset_inputs = function(hard) {
"use strict";
my.set_tristates(hard);
if (hard) {
my.miscOpt = {};
}
// here if hard or soft:
my.allbars = [];
my.weak_diaspo(0);
var box = my.L("sec-special");
box.style.display = "none";
box.value = "";
for (var ii = 0; ii < my.allvar.length; ii++){
var obj = my.allvar[ii];
var mkr = obj.mkr;
my.setv(mkr, "", "");
var _alias, _pct;
_alias = my.L(mkr + "-alias");
if (_alias) _alias.innerHTML = obj.alias();
_pct = my.L(mkr + "-hipct");
if (_pct) _pct.innerHTML = "";
_pct = my.L(mkr + "-lopct");
if (_pct) _pct.innerHTML = "";
}
my.setv("tform", "");
my.minus_showhide();
my.setv("tails", "", "");
my.L("show-timing").value = "";
}
//////////////////////////////////////////////////////////////////////
// In all these examples,
// the opt argument should be considered a kludge,
// suitable for quick&dirty debugging.
//
// In the long run, it is better to write a new example function.
my.x = function(ev, opt, more){
if ('undefined' === typeof more) more = {};
my.crunch(my.meld({clearplot: !(ev && ev.shiftKey)}, more, opt));
}
// {{{{ start group of examples
my.example_delta0 = function(ev, opt) {
my.reset_inputs();
my.setv("tform", "0");
my.x(ev, opt, {peak: 1.1});
}
my.example_roundoff = function(ev, opt) {
my.reset_inputs();
my.setv("x", 16.6, 0.2, "=", 1);
my.setv("tform", "x-round(x)");
my.x(ev, opt);
}
my.example_ratfun = function(ev, opt) {
my.reset_inputs();
my.setv("x", 2, .1);
my.setv("y", 1, .2);
my.setv("tform", "x/y");
my.x(ev, opt);
}
// magnesium mass
my.example_mg_mass = function(ev, opt) {
my.reset_inputs();
my.setv("x", 0.12663, 0.00006, "=", 1);
my.setv("y", 0.13932, 0.00013, "=", 1);
my.setv("z", "", "");
my.setv("tform", "mg24=1/(1+x+y); mg25=x*mg24; mg26=y*mg24;"
+"mass=23.9850423*mg24 + 24.9858374*mg25 + 25.9825937*mg26");
my.x(ev, opt);
}
my.example_triangle = function(ev, opt) {
my.reset_inputs();
my.setv("x", 1, "0.1%");
my.setv("y", "2", "500 ppm");
my.setv("z", "", "");
my.setv("tform", "x+y");
my.x(ev, opt);
}
my.example_speed = function(ev, opt) {
my.reset_inputs();
my.setv("x:accel", 10, "1000 ppm");
my.setv("y:dist", "3", "5 %");
my.setv("tform", "temp = 2 * accel * dist; speed = sqrt(temp)");
my.x(ev, opt);
}
my.example_bad_time = function(ev, opt) {
my.setv("tform", "time = dist / speed");
my.x(ev, opt);
}
my.example_good_time = function(ev, opt) {
my.reset_inputs();
my.setv("x:accel", 10, "1000 ppm");
my.setv("y:dist", "3", "5 %");
my.setv("tform", "speed = sqrt(2 * accel * dist); time = dist/speed");
my.x(ev, opt);
}
my.example_pow = function(ev, opt) {
my.reset_inputs();
my.setv("tform", "pow(2, 5)");
my.x(ev, opt);
}
my.example_log = function(ev, opt) {
my.reset_inputs();
my.setv("tform", "ln(e) - log(e)");
my.x(ev, opt);
}
my.example_atan2 = function(ev, opt) {
my.reset_inputs();
my.setv("tform", "atan2(1, -1)*180/π");
my.x(ev, opt);
}
my.example_gauss = function(ev, opt) {
my.reset_inputs();
my.setv("x", 10, 3, "=", 1);
my.setv("tform", "x");
my.x(ev, opt);
}
my.example_2gauss = function(ev, opt) {
my.reset_inputs();
my.setv("x", 2, 3, "=", 1);
my.setv("y", 3, 4, "=", 1);
my.setv("tform", "x+y");
my.x(ev, opt);
}
my.six_point = function(ev, opt) {
my.reset_inputs();
my.setv("x", 10, 3, "=");
my.setv("tform", "x");
my.L("mc-npts").value= 6;
my.x(ev, opt, {peak: 1.1});
}
my.example_pointless = function(ev, opt) {
my.reset_inputs();
my.setv("tform", "0");
my.L("mc-npts").value= 0;
my.x(ev, opt);
}
my.example_constant = function(ev, opt) {
my.reset_inputs();
my.setv("tform", "13");
my.x(ev, opt);
}
my.example_rectangle = function(ev, opt) {
my.reset_inputs();
my.setv("x", 10, 3, "=");
my.setv("tform", "x");
my.x(ev, opt);
}
my.example_alea = function(ev, opt) {
my.reset_inputs();
my.setv("x", 3.5, 3, "=");
my.setv("tform", "round(x)");
my.x(ev, opt, {peak: 1.1});
}
my.example_aleae = function(ev, opt) {
my.reset_inputs();
my.setv("x", 3.5, 3, "=");
my.setv("y", 3.499999, 3, "=");
my.setv("tform", "round(x)+round(y)");
my.x(ev, opt, {lineColor: "green",
peak: 1.1});
}
my.example_more_mixture = function(ev, opt) {
my.reset_inputs();
my.setv("x", 0.5, 0.5, "=");
my.setv("y", 0, 0.75, "=", 1);
my.setv("z", 0, 1, "=", 1);
my.setv("tform", "x < 0.8 ? y : round(2*z)/2");
my.x(ev, opt, {peak: 1.1});
}
my.example_mixture = function(ev, opt) {
my.reset_inputs();
my.setv("x", 0.5, 0.5, "=");
my.setv("y", 0, 0.75, "=", 1);
my.setv("z", 0, 1, "=", 1);
my.setv("tform", "x < 0.8 ? y : round(z)");
my.x(ev, opt, {peak: 1.1});
}
my.example_coarse = function(ev, opt) {
my.example_mixture(ev, my.meld({nbins: 15}, opt));
}
my.example_max_boltz_1d = function(ev, opt) {
my.reset_inputs();
my.setv("x:Vx", 0, 1, "=", 1);
my.setv("tform", "m=1; energy = .5 * m * Vx*Vx");
my.x(ev, opt);
}
my.example_max_boltz_2d = function(ev, opt) {
my.reset_inputs();
my.setv("x:Vx", 0, 1, "=", 1);
my.setv("y:Vy", 0, 1, "=", 1);
my.setv("tform", "m=1; energy = .5 * m * (Vx*Vx + Vy*Vy)");
my.x(ev, opt, {rmax: 3.5});
}
my.example_max_boltz_3d = function(ev, opt) {
my.reset_inputs();
my.setv("x:Vx", 0, 1, "=", 1);
my.setv("y:Vy", 0, 1, "=", 1);
my.setv("z:Vz", 0, 1, "=", 1);
my.setv("tform", "m=1; energy = .5 * m * (Vx*Vx + Vy*Vy + Vz*Vz)");
my.x(ev, opt, {rmax: 4.5});
}
my.example_wacky = function(ev, opt) {
my.reset_inputs();
my.setv("x", 1, Math.sqrt(3));
my.setv("tform", "1/x/x");
my.x(ev, opt, {peak: 1.1});
}
my.example_deceptive = function(ev, opt) {
my.reset_inputs();
my.setv("x", 1, 1, "=", 1);
my.setv("tform", "x + x*(x-1)*(x-2)/(x*x+1.5)");
my.x(ev, opt, {peak: 1.1});
}
my.example_TPTcar = function(ev, opt) {
my.reset_inputs();
my.setv("x:mass", .089, .001, "=", 0);
my.setv("y:drop", .0934, .0001, "=", 0);
my.setv("tform", "speed=sqrt(2*9.812988*drop)");
my.x(ev, opt, {diaspo: 2});
}
// }}}} end of examples
my.getParameterByName = function(name, url) {
if (!url) {
url = window.location.href;
}
// protect funny characters that are allowed in the name,
// namely "[" and "]" :
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)");
var results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
my.handle_query_string = function() {
var url = window.location.href;
var urlparts = url.split('?', 2);
if (urlparts.length < 2) return;
// strip off the anchor tag, if any:
var qstring = urlparts[1].replace(/#.*$/, "");
var parse = qstring.split('&');
var area = my.L("sec-special");
var did_reset = 0;
for (var ii = 0; ii < parse.length; ii++){
var verb = parse[ii];
var rest = "";
var where = verb.search("=");
if (where >= 0) {
rest = verb.substr(1+where);
verb = verb.substr(0, where);
}
var nope = verb.split(/[_-]/);
if (verb.toLowerCase() === "go") {
my.crunch();
} else if (verb.toLowerCase() === "ex") {
my["example_" + rest](null, {});
} else if (verb.toLowerCase() === "setv") {
var stuff = rest.split(',');
if (!did_reset++) my.reset_inputs();
try {
// decode each of the arguments separately
stuff = stuff.map(decodeURIComponent);
my.setv.apply(null, stuff);
} catch (err) {
console.log("query string error:", err.message);
return;
}
} else if (verb.toLowerCase() === "s") {
// handled much earlier
} else if (verb.toLowerCase() === "simple") {
// handled much earlier
} else if (verb === "") {
// shouldn't happen, but harmless
} else if (nope[0] === "no" && nope.length > 1) {
delete my.miscOpt[nope[1]];
} else if (verb === "mc_npts") {
my.L("mc-npts").value = rest;
} else if (verb === "nbins") {
my.L("histo-nbins").value = rest;
} else if (verb === "diaspo") {
my.weak_diaspo(rest);
} else if (verb === "refs") {
my.set_refs(rest);
} else {
// any other verb;
// hope it is an option that carries a value such as:
// &rmin=-1
// &rmax=2
// &memo
// &no-memo or equivalently &no_memo
// &lineColor=green
my.miscOpt[verb] = rest;
}
}
}
/////////////////////////////////////////////////
// plotting stuff
my.plot_supported = function(cvname) {
var cv = my.L(cvname);
if (!cv) return 0; // no element at all?
if (!cv.getContext) return 0; // element doesn't have a context
var cx = cv.getContext("2d");
if (!cx) return 0; // doesn't support 2d graphics
return 1;
}
my.clearplot = function(mkr){
my[mkr + "_scale"].clear();
}
// constructor for scaled plotting
my.plotscaler = function(cvname, _opt) {
this.opt = _opt ? _opt : {};
this.sys_setup = function(cvname) {
// first, set up system-level plotting stuff
// defend against browsers that don't support plotting
if (!my.plot_mode) return;
if (!my.plot_supported(cvname)) return;
this.cv = my.L(cvname);
this.cx = this.cv.getContext("2d");
this.frameHeight = this.opt.frameHeight ?
this.opt.frameHeight : this.cv.height;
if (!this.cx.established) {
this.clear();
this.cx.established = 1;
}
}
// Set up stuff specific to this plot.
// Call with sw and ne describing a bounding box
// around the data (or the part of the data you care about).
this.setup = function(sw, ne, opt){
this.fontHeight = opt.fontHeight ? Number(opt.fontHeight) : 16;
this.cx.font = this.fontHeight + "px Arial";
this.factor = []; // scale factor
this.factor.push((this.cv.width - this.margin.l - this.margin.r) /
(ne[0] - sw[0]));
this.factor.push((this.frameHeight - this.margin.t - this.margin.b) /
(ne[1] - sw[1]));
this.sw = sw;
this.ne = ne;
}
this.clear = function(background) {
if (!this.cx) return;
if ('undefined' === typeof background)
background = this.background;
this.cx.clearRect(0,0, this.cv.width, this.cv.height);
// this.cx.fillStyle = "rgb(255,200,200)";
// this.cx.fillRect(0,0, this.cv.width, this.cv.height);
this.cx.fillStyle = background;
var m = this.margin;
this.cx.fillRect(m.l, m.t, this.cv.width - m.l - m.r,
this.frameHeight - m.t - m.b);
this.gridded = 0;
}
this.map = function(xy){
var xx = (xy[0] - this.sw[0]) * this.factor[0] + this.margin.l;
var yy = (this.ne[1] - xy[1]) * this.factor[1] + this.margin.t;
// If this.clip is undefined, then both < and > return false:
if (xx > this.clip) xx = this.clip;
if (xx < -this.clip) xx = -this.clip;
if (yy > this.clip) yy = this.clip;
if (yy < -this.clip) yy = -this.clip;
return [Math.round(xx), Math.round(yy)];
}
this.moveTo = function(xy){
var uv = this.map(xy);
this.cx.moveTo(uv[0], uv[1]);
}
this.lineTo = function(xy){
var uv = this.map(xy);
if (uv[0] === this.lastpt[0] && uv[1] === this.lastpt[1]) return;
this.cx.lineTo(uv[0], uv[1]);
this.lastpt = uv;
}
this.plot_xy = function(pairs, opt){
if (!opt) opt = {};
if (pairs.length === 0) return;
this.beginPath();
this.moveTo(pairs[0]);
for (var ii = 1; ii < pairs.length; ii++){
this.lineTo(pairs[ii]);
}
this.cx.stroke();
if ('undefined' !== typeof opt.fillTo) {
this.lineTo([pairs[pairs.length-1][0], opt.fillTo]);
this.lineTo([pairs[0][0], opt.fillTo]);
this.cx.fill();
}
}
////////// Application-specific plotting stuff.....
this.beginPath = function(){
this.lastpt = ["", ""];
this.cx.beginPath();
}
// Calculate some reasonable number of grid lines,
// and then draw them.
this.xgrid = function() {
var bottom = this.sw[0];
var top = this.ne[0];
var span = top - bottom;
if (span <= 0) span = 1; // kludge
// first guess (and lower bound) as to how many divisions:
var dx0 = span / 5;
var ldx = my.log10(dx0);
var ccc = Math.floor(ldx);
var mant = Math.pow(10, ldx - ccc);
if (mant >= 5) mant = 5;
else if (mant >= 2) mant = 2;
else mant = 1;
this.dx = mant * Math.pow(10, ccc);
this.first = Math.ceil(bottom/this.dx + .05);
this.last = Math.floor( top/this.dx - .05);
if (0) my.L("sec-special").value =
"bottom: " + bottom + ", " + this.first +
" top: " + top + ", " + this.last +
" dx: " + this.dx;
this.cx.strokeStyle = "white";
if (1) {
this.beginPath();
for (var ii = this.first; ii <= this.last; ii++) {
this.moveTo([ii*this.dx, this.sw[1]]);
this.lineTo([ii*this.dx, this.ne[1]]);
}
this.cx.stroke();
}
// yellow contour of constant x=0
if (0) {
this.cx.save();
this.cx.strokeStyle = "yellow";
this.beginPath();
this.moveTo([0, this.sw[1]]);
this.lineTo([0, this.ne[1]]);
this.cx.stroke();
this.cx.restore();
}
}
this.rticklabels = function() {
this.cx.save();
this.cx.textAlign="center";
var touched = 0; // a u-coordinate
var pitch = 0.75 * this.fontHeight;
for (var ii = this.first; ii <= this.last; ii++) {
var [u,v] = this.map([ii*this.dx, 0]);
this.cx.fillStyle = "black";
var txt = my.numFmt(ii*this.dx);
var room = u - touched;
var half = txt.length * pitch / 2;
if (half > room) continue;
if (u + half > this.cv.width) continue;
this.cx.fillText(txt, u, this.frameHeight + this.fontHeight);
touched = u + half;
}
this.cx.restore();
}
this.ygrid = function() {
this.cx.strokeStyle = "white";
this.beginPath();
this.moveTo([this.sw[0], 0.5]);
this.lineTo([this.ne[0], 0.5]);
this.cx.stroke();
this.beginPath();
this.moveTo([this.sw[0], my.erf1sigma]);
this.lineTo([this.ne[0], my.erf1sigma]);
this.cx.stroke();
this.beginPath();
this.moveTo([this.sw[0], 1-my.erf1sigma]);
this.lineTo([this.ne[0], 1-my.erf1sigma]);
this.cx.stroke();
}
// The black frame box surrounding the active part of the plot
this.frame = function() {
this.cx.save()
this.cx.strokeStyle = "black";
///// this.cx.setLineDash([10, 5]);
this.beginPath();
this.moveTo(this.sw);
this.lineTo([this.sw[0], this.ne[1]]);
this.lineTo(this.ne);
this.lineTo([this.ne[0], this.sw[1]]);
this.lineTo(this.sw);
this.cx.stroke();
this.cx.restore();
}
this.ref_gauss_c = function(nom, pbar) {
// reference curve, gaussian, cumulative
this.cx.strokeStyle = "magenta";
this.beginPath();
var xx=this.sw[0];
this.moveTo([xx, my.gauss_cume(xx, nom, pbar)]);
for (var ii = 0; ii <= 40; ii++) {
xx = this.sw[0] + ii*(this.ne[0] - this.sw[0]) / 40;
this.lineTo([xx, my.gauss_cume(xx, nom, pbar)]);
}
this.cx.stroke();
}
this.ref_line_c = function(nom, pbar) {
// reference curve, straight line:
this.cx.strokeStyle = "magenta";
this.beginPath();
this.cx.setLineDash([10, 5]);
this.moveTo([nom-pbar, 0]);
this.lineTo([nom+pbar, 1]);
this.cx.stroke();
this.cx.setLineDash([]);
}
////////////////////////////////////////
// end of function declarations; do some actual work
this.background = this.opt.background ? this.opt.background :
"rgba(220,220,220,1)"; // light gray
this.margin = {t: 1, b: 1, l: 1, r: 1};
this.lastpt = ["", ""];
this.cvname = cvname;
this.sys_setup(cvname);
if (!this.cx) return; // probably not plot mode, or no canvas
this.clip = 1e4; // undefine this if you don't want clipping
this.gridded = 0;
}
// exercise the plotting system, including infinities
my.inf_test = function(scale, nom, bigbar) {
"use strict";
my.beginPath();
my.cx.strokeStyle = "red";
var kx = Number.NEGATIVE_INFINITY;
var ky = Number.POSITIVE_INFINITY;
scale.moveTo([kx, ky]);
kx = nom-2*bigbar;
ky = 0.5;
scale.lineTo([kx, ky]); // incoming 45 degree diagonal
kx += bigbar/2;
scale.lineTo([kx, ky]); // horiz
ky = 0.75;
scale.lineTo([kx, ky]); // vert
kx += bigbar/2;
scale.lineTo([kx, ky]); // horiz
ky = 0.5;
scale.lineTo([kx, ky]); // vert
kx += bigbar/2;
scale.lineTo([kx, ky]); // horiz
ky = Number.POSITIVE_INFINITY; // plotscaler now defends this
kx += bigbar/2;
scale.lineTo([kx, ky]); // diagonal way up, some over
ky = 0.5;
scale.lineTo([kx, ky]); // vert back down
kx += bigbar/2;
scale.lineTo([kx, ky]); // horiz
ky = Number.NEGATIVE_INFINITY;
scale.lineTo([kx, ky]); // vert
kx += bigbar/2;
scale.lineTo([kx, ky]); // horiz
ky = 0.5;
scale.lineTo([kx, ky]); // vert back up
kx += bigbar/2;
scale.lineTo([kx, ky]); // horiz
kx = Number.POSITIVE_INFINITY;
ky = Number.NEGATIVE_INFINITY;
scale.lineTo([kx, ky]); // outgoing diagonal
my.cx.stroke();
}
my.x_setup = function(center, bigbar,
nom, allbars, mean, stdev, opt){
// pbar is the spacing between grid lines
var pbar = bigbar ? bigbar : 1;
var west = center - 2.5*pbar;
var east = center + 2.5*pbar;
var p84 = allbars[2][2] + allbars[2][1];
if (p84 > east) east = p84;
var p16 = allbars[2][2] - allbars[2][0];
if (p16 < west) west = p16;
if ('undefined' !== typeof opt.rmax) east = Number(opt.rmax);
if ('undefined' !== typeof opt.rmin) west = Number(opt.rmin);
return [pbar, west, east];
}
// Plot the probability density distribution.
// Also plot the diaspo dots on their own layer.
// xort must already be sorted .........
my.plot_density = function(xraw, xort, center, bigbar,
nom, allbars, mean, stdev, opt) {
"use strict";
if (!opt) opt = {};
var defaults = {
title: 1,
rlabels: 1,
xprefix: ''
}
opt = my.meld(defaults, opt);
var npts = xort.length;
var scale = my.density_scale; // convenient synonym
var dsc = my.diaspo_scale; // convenient synonym
var rgsc = my.refs_d_scale; // convenient synonym
var nbins = my.maybeSave("histo-nbins")
var [pbar, west, east] = my.x_setup(center, bigbar,
nom, allbars, mean, stdev, opt);
var span = east - west;
var binsep = span / nbins;
var binwid = binsep;
// avoid issues with points falling right on bin-boundaries:
var eps = binwid * 1e-7;
var bottom = west;
bottom += eps*2;
var peak = 0;
var density = [];
for (var ii = 0; ii <= nbins; ii++) {
var midpoint = binsep * ii + bottom;
var left = my.inv_interp(xort, midpoint - binwid/2 , -1);
var right = my.inv_interp(xort, midpoint + binwid/2 - eps, -1);
var height = (Math.floor(right) - Math.floor(left))/ npts / binwid;
density.push([midpoint, height]);
peak = Math.max(peak, height);
}
var dd = density.length;
var rhomax;
if (npts == 0 || stdev == 0) {
mean = center;
stdev = pbar;
} else {
var otherbar = Math.max(allbars[1][0], allbars[1][1],
allbars[2][0], allbars[2][1]);
// special hack for distribution with excessive (e.g. infinite) mean
if (otherbar > 0 && stdev > 10*otherbar) stdev = otherbar/2;
}
if (npts == 0){
peak = my.gauss_density(0,0,pbar);
}
if (opt.peak) {
rhomax = peak*opt.peak;
} else {
rhomax = Math.max(1.5*my.gauss_density(0,0,stdev),
Math.min(1.1*peak, 6*my.gauss_density(0,0,stdev)));
}
if (opt.rhomax) {
rhomax = opt.rhomax;
}
scale.setup([west, 0], [east, rhomax], opt);
dsc.setup( [west, 0], [east, rhomax], opt);
rgsc.setup( [west, 0], [east, rhomax], opt);
// compute the reference Gaussian
var ref = [];
if (stdev) for (var ii = 0; ii <= 200; ii++) {
var xx = bottom + span * ii / 200;
var yy = my.gauss_density(xx, mean, stdev);
ref.push([xx,yy]);
}
if (opt.clearplot) {
scale.clear();
dsc.clear();
rgsc.clear();
}
if (!scale.gridded) { // x grid for density
scale.gridded++;
scale.xgrid();
if (Number(opt.rlabels)) {
scale.rticklabels();
}
scale.frame() // comes after grid lines
// http://stackoverflow.com/questions/3167928/
// drawing-rotated-text-on-a-html5-canvas
// title for this plot:
scale.cx.fillStyle = "black";
scale.cx.textAlign="start";
var ypos = scale.fontHeight*1.25;
var yskip = scale.fontHeight + 2; /// allow some baselineskip
if (Number(opt.title)) { // beware that '0' is not falsy
scale.cx.fillText("Probability Density", 10, ypos);
ypos += yskip;
}
// memo
if ('undefined' !== typeof opt.memo) {
// the text of the memo serves as a subtitle:
scale.cx.fillText(opt.memo, 10, ypos);
// reposition to right-hand column:
scale.cx.textAlign="end";
var ypos = scale.fontHeight*1.25;
// x input distribution:
scale.cx.fillStyle = "green";
var str = my.L("x-nom").value;
if (str.length) {
str = opt.xprefix + str + " ± " + my.L("x-hibar").value;
scale.cx.fillText(str, scale.cv.width - 10, ypos);
ypos += yskip;
}
// transformation formula:
scale.cx.fillStyle = "blue";
str = my.L("tform-nom").value;
str = str.replace(/round⌊/g, "⌊");
if (str.length) {
scale.cx.fillText(str, scale.cv.width - 10, ypos);
ypos += yskip;
}
// mean and stdev of result distribution:
scale.cx.fillStyle = "magenta";
var str = my.L("mean-nom").value;
if (str.length) {
str += " ± " + my.L("mean-hibar").value;
scale.cx.fillText(str, scale.cv.width - 10, ypos);
ypos += yskip;
}
}
// reference curve:
rgsc.cx.strokeStyle = "magenta";
rgsc.cx.fillStyle = opacify("magenta", 16);
rgsc.plot_xy(ref, {fillTo: 0});
} else {
//xx console.log("density gridded not needed");
}
// actual data curve:
var lineColor = ('undefined' !== typeof opt.lineColor) ?
opt.lineColor : "blue";
scale.cx.strokeStyle = lineColor;
scale.cx.fillStyle = opacify(lineColor, 32);
if (0 && opt.dash) {
scale.cx.setLineDash([10, 10]);
} else {
scale.cx.setLineDash([]);
}
// Plot the pseudo-histogram.
// A discrete distribution will be represented as a set
// of triangular spikes (not rectangles).
// The area of the triangle is the same as the area of
// the corresponding rectangle.
// The bottom of the triangle is twice as wide as the
// rectangle would be, since it extends to the middle
// of the neighbor's interval.
// Two adjacent spikes of the same height merge to form
// a trapezoid that is one unit wide at the top and three
// units wide at the bottom, so once again the area is correct.
scale.plot_xy(density, {fillTo: 0});
// This will fail if the first todo points are not
// a random sample.
if (1) {
var todo = Math.min(1000, xraw.length);
var unpack_x = density.map(function(xy){
return xy[0];
});
var unpack_y = density.map(function(xy){
return xy[1];
});
for (var ii = 0; ii < todo; ii++){
var xx = xraw[ii];
var idx = my.inv_interp(unpack_x, xx);
var yy = my.interp(unpack_y, idx);
yy *= Math.random();
var [uu,vv] = dsc.map([xx, yy]);
dsc.beginPath();
dsc.cx.strokeStyle = "black";
dsc.cx.arc(uu, vv, 2.5, 0, 2*Math.PI);
dsc.cx.stroke();
}
}
} // end of plot_density
// Plot the cumulative probability distribution,
// and some informational markers.
// xort must already be sorted .........
my.plot_cume = function(xraw, xort, center, bigbar,
nom, allbars, mean, stdev, opt) {
"use strict";
if (!opt) opt = {};
var [pbar, west, east] = my.x_setup(center, bigbar,
nom, allbars, mean, stdev, opt);
var scale = my.cume_scale; // convenient synonym
scale.setup([west, 0], [east, 1], opt);
var rscale = my.refs_c_scale; // convenient synonym
rscale.setup([west, 0], [east, 1], opt);
if (opt.clearplot) scale.clear();
var doplot = !scale.gridded;
if (doplot) {
scale.xgrid();
scale.ygrid();
scale.frame();
scale.cx.fillStyle = "black";
scale.cx.textAlign="start";
var ypos = scale.fontHeight*1.25;
scale.cx.fillText("Cumulative Probability", 10, ypos);
scale.gridded++;
}
if (doplot) {
rscale.ref_line_c(center, pbar);
rscale.ref_gauss_c(center, pbar);
}
// my.inf_test(scale, nom, bigbar);
scale.cx.strokeStyle = ('undefined' !== typeof opt.lineColor) ?
opt.lineColor : "blue";
if (opt.dash) {
scale.cx.setLineDash([10, 10]);
} else {
scale.cx.setLineDash([]);
}
var stairs = [];
var npts = xort.length;
var ii;
for (ii = 0; ii < npts; ii++){
stairs.push([xort[ii], ii/npts]); // bottom of riser
stairs.push([xort[ii], (1+ii)/npts]); // top of riser
}
// initial tread; does not carry any probability:
if (xort[0] > scale.sw[0]) {
stairs.unshift([scale.sw[0], 0]);
}
// final tread; does not carry any probability:
if (xort[npts-1] < scale.ne[0]) {
stairs.push([scale.ne[0], 1]);
}
scale.plot_xy(stairs);
scale.cx.setLineDash([]);
if ('undefined' !== typeof mean){
var doit = function(len, slant, xxx) {
var where = my.inv_interp(xort, xxx, 0);
//xx console.log("...>", where);
scale.beginPath();
scale.moveTo([xxx - slant*bigbar, where/npts - len]);
scale.lineTo([xxx + slant*bigbar, where/npts + len]);
scale.cx.stroke();
}
// show the mean and standard deviation vertical bars
scale.cx.strokeStyle = "black";
doit(.03, 0, mean);
doit(.03, 0, mean+stdev);
doit(.03, 0, mean-stdev);
// red tick mark showing the C3T center
if (0) {
var yspan = scale.ne[1] - scale.sw[1];
scale.cx.save()
scale.cx.strokeStyle = "red";
scale.beginPath();
scale.moveTo([center, scale.ne[1]]);
scale.lineTo([center, scale.ne[1] - yspan/20]);
scale.cx.stroke();
scale.cx.restore()
}
// Show all the C3T error-bar slashes.
// Do the green set last,
// so they cover up the red ones, if they overlap.
scale.cx.strokeStyle = "red";
doit(.02, -0.04, nom - allbars[1][0]);
doit(.02, -0.04, nom + allbars[1][1]);
scale.cx.strokeStyle = "green";
doit(.02, -0.04, nom - allbars[0][0]);
doit(.02, -0.04, nom + allbars[0][1]);
doit(.02, -0.04, nom + 0); // the central slash
}
}
// The main entry point
my.loaded = function() {
"use strict";
my.L("show-version").innerHTML = "1.1.55";
// Check for ?s in the URL
var simple = 'string' === typeof my.getParameterByName("s") ||
'string' === typeof my.getParameterByName("simple");
my.mc_mode = !simple;
// Nowadays we show the plots if and only if we did the MC calculation.
my.plot_mode = my.mc_mode;
var pltopt = {
frameHeight: my.L("cume-canvas").height
};
my.cume_scale = new my.plotscaler("cume-canvas", pltopt);
my.density_scale = new my.plotscaler("density-canvas", pltopt);
pltopt.background = "transparent";
my.refs_c_scale = new my.plotscaler("refs-c-canvas", pltopt);
my.diaspo_scale = new my.plotscaler("diaspo-canvas", pltopt);
my.refs_d_scale = new my.plotscaler("refs-d-canvas", pltopt);
my.set_tristates(1); // hard reset of tristate variables
var fixup = function(mkr) {
var elem = my.L(mkr);
if (!elem) return;
var absolute = elem.protocol + "//" + elem.host +
elem.pathname + elem.search + elem.hash;
elem.innerHTML = absolute;
}
fixup("to-doc");
fixup("to-fancy");
// get the font-size from some generic snippet of text:
var el = my.L("tform-alias");
my.normalsize = window.getComputedStyle(el).getPropertyValue('font-size');
///???? might be helpful for textarea, but not needed for input box:
///???? my.L("tform-nom").style.fontSize = my.normalsize;
//-- var el2 = my.L("sec-overview");
//-- var bodysize = window.getComputedStyle(el2).getPropertyValue('font-size');
//-- console.log(my.normalsize, bodysize);
my.map = []; // associative map, not an array
my.allvar = []; // an array
// arguments are: (my, xmkr, active, sticky)
// sticky&2 means requires mc_mode
my.allvar.push(new my.distro(my, "x", 1));
my.allvar.push(new my.distro(my, "y", 1));
my.allvar.push(new my.distro(my, "z", 1));
my.allvar.push(new my.distro(my, "result", 2, 1));
my.allvar.push(new my.distro(my, "mean", 0, 0));
my.allvar.push(new my.distro(my, "median", 0, 3));
// a lot of other inputs such as the /formula/ are not considered variables
my.invar = []; // an array
for (var ii = 0; ii < my.allvar.length; ii++){
if (my.allvar[ii].active) my.invar.push(my.allvar[ii]);
}
my.reset_inputs(1); // hard reset of user-interface fields
my.mc_showhide(); // must come after my.map is set up
my.plot_showhide(); // includes a plot_setup if needed
my.minus_showhide();
my.ready = 1;
my.handle_query_string();
}
// Here when either of the axis/corner mode buttons gets clicked,
// with arg = html object, namely the button that got clicked.
//
// Alternatively, call here with an integer,
// namely an index into the list.
//
// The mode argument is likely to be the old value, which is
// likely to be represented as a string.
my.set_cornax = function(mode){
if ('undefined' === typeof mode || mode === null || isNaN(mode)){
// toggle the old value
//uu console.log("toggling cornax-mode from", typeof mode, mode);
mode = Number(my["cornax-mode"].saved) ? 0 : 1;
}
my["cornax-mode"].saved = mode;
my["cornax-mode"].cur.value = mode;
var btn = my.L("cornax-mode");
if (Number(mode)) {
btn.innerHTML="Axis Corner";
} else {
btn.innerHTML="Axis Corner";
}
// let user see the selected result:
my.set_result_bars(my.nominal, my.allbars);
}
// link color 218 165 32 = goldenrod, a dark bronze color
my.copy_keypress = function(elem, ev) {
var event = ev;
if (!event) { event = window.event; }
var code = ev.keyCode || ev.charCode;
if (code === 27)
elem.style.display = "none";
}
my.copy_to_clipboard = function(ev) {
var div = my.L("sec-special");
// shift-click: just open or close
// without recomputing anything
// and without modifying the clipboard
if (ev.shiftKey) {
div.style.display = (div.style.display == "none") ?
"block" : "none";
return;
}
var str = window.location.href.split(/[?]/)[0];
var didsome = 0;
for (var ii = 0; ii < my.invar.length; ii++) {
args = [];
var input = my.invar[ii];
// the "2" bit means it's a kludge
if ((input.active & 2)) continue;
input.sync();
args[0] = input.mkr;
var alias = input.alias();
if (input.mkr !== alias) args[0] += ":" + alias;
args[1] = input.raw_nominal;
args[2] = input.raw_hibar;
args[3] = input.raw_lobar;
args[4] = input.shape || 0;
do {
if (Number(args[4]) !== 0) break;
args.pop();
if (args[3] !== "" && args[3] !== args[2]) break;
args.pop();
if (args[2] !== "") break;
args.pop();
if (args[1] !== "") break;
args.pop()
} while (0);
if (args.length <= 1) continue;
// encode each of the arguments separately
args = args.map(encodeURIComponent);
// here if we are committed to doing some output
str += didsome++ ? "&" : "?";
str += "setv=" + args[0];
for (var jj = 1; jj < args.length; jj++){
str += "," + args[jj];
}
} // end loop over input variables
// the formula is an input, but not an input _variable_
var obj = my.L("tform-nom");
var formula = obj.value;
if (formula !== "") {
str += didsome++ ? "&" : "?";
str += "setv=tform," + encodeURIComponent(formula);
}
var diaspo = my["diaspo-button"].cur.value;
if (Number(diaspo)) {
str += didsome++ ? "&" : "?";
str += "diaspo=" + diaspo;
}
var refs = my["refs-button"].cur.value;
if (Number(refs)) {
str += didsome++ ? "&" : "?";
str += "refs=" + refs;
}
var cornax = my["cornax-mode"].cur.value;
if (Number(cornax)) {
str += didsome++ ? "&" : "?";
str += "cornax=" + cornax;
}
var npts = my["mc-npts"].cur.value;
if (npts != Number(my["mc-npts"].canon)) {
str += didsome++ ? "&" : "?";
// It's opt.mc_npts even though it's my["mc-npts"]. Sorry.
str += "mc_npts=" + npts;
}
var nbins = my["histo-nbins"].cur.value;
if (nbins != Number(my["histo-nbins"].canon)) {
str += didsome++ ? "&" : "?";
// It's opt.npts even though it's my["histo-npts"]. Sorry.
str += "nbins=" + nbins;
}
if (didsome) {
str += didsome++ ? "&" : "?";
str += "go";
}
div.value = str;
// undo the display:none,
// because select apparently doesn't work if it's not visible.
div.style.display = "block";
div.style.fontSize = my.normalsize;
div.select();
document.execCommand("copy");
}
my.shape_click = function(mkr){
var obj = my.map[mkr];
obj.set_shape(1 + obj.shape);
}
// if no argument, toggle the mode
my.mc_click = function(newmode){
my.mc_mode = ('undefined' !== typeof newmode) ? newmode : ! my.mc_mode;
my.plot_mode = my.mc_mode
my.mc_showhide();
my.plot_showhide();
// if it's not an /input/ variable it doesn't have a shape:
for (var ii = 0; ii < my.invar.length; ii++) {
my.invar[ii].set_shape();
}
}
my.mc_showhide = function(){
var row = my.mc_mode ? "table-row" : "none";
my.L("mean-hirow").style.display = row;
// there is no mean-lorow
my.L("median-hirow").style.display = row;
// take care of the median-lorow:
my.minus_showhide_1("median");
my.L("tails-header").style.display = row;
my.L("tails-higroup").style.display = row;
my.L("tails-logroup").style.display = row;
}
my.plot_showhide = function(){
var _plot = my.L("sec-plot");
if (_plot) _plot.style.display = my.plot_mode ? "block" : "none";
// plotscalers are handled elsewhere:
// new my.plotscaler("cume-canvas");
// new my.plotscaler("density-canvas");
// new my.plotscaler("refs-d-canvas");
}
my.maybeSetSrc = function(obj, val){
if (val === '') {
obj.style.opacity=0;
return;
}
//xx console.log("mSS begins", obj.id, obj.src, "-->", val);
var foo = obj.src.replace(/.*\//, "");
var bar = val.replace(/.*\//, "");
if (foo !== bar) {
obj.src = val;
}
obj.style.opacity=1;
}
my.weak_diaspo = function(newval){
var mode = my["diaspo-button"].cur.value;
mode = Number(mode);
if (mode === 2) return;
my.set_diaspo(newval);
}
// set (or toggle) showing the reference gaussian
my.set_refs = function(newval){
var mode = my["refs-button"].cur.value;
mode = Number(mode);
if ('undefined' === typeof newval) mode = 1-mode;
else mode = Number(newval);
my["refs-button"].cur.value = mode;
my["refs-button"].saved = mode; // so it survives reset
if (mode)
my.L("refs-button").style.border = "2px solid blue";
else
my.L("refs-button").style.border = "1px solid gray";
if (mode) {
my.L("refs-c-canvas").style.visibility = "visible";
my.L("refs-d-canvas").style.visibility = "visible";
} else {
my.L("refs-c-canvas").style.visibility = "hidden";
my.L("refs-d-canvas").style.visibility = "hidden";
}
}
// set (or toggle) disapogram mode
my.set_diaspo = function(newval){
var mode = my["diaspo-button"].cur.value;
mode = Number(mode);
if ('undefined' === typeof newval) mode = mode > 1 ? 0 : 2;
else mode = Number(newval);
mode %= 3;
my["diaspo-button"].cur.value = mode;
my["diaspo-button"].saved = mode; // so it survives reset
if (mode === 2)
my.L("diaspo-button").style.border = "2px solid blue";
else if (mode)
my.L("diaspo-button").style.border = "2px dashed blue";
else
my.L("diaspo-button").style.border = "1px solid gray";
if (mode) {
my.L("diaspo-canvas").style.visibility = "visible";
} else {
my.L("diaspo-canvas").style.visibility = "hidden";
}
}
// Toggle the lopsidedness.
// called by "=" keypress,
// or by clicking on the + or - or ± symbols
my.lop = function(which){
var parse = which.split("-");
if (parse.length <= 1) {
return;
}
var mkr = parse[0];
var obj = my.L(mkr + "-lorow")
if (!obj) {
console.log("my.lop: no such object:", mkr + "-lorow");
return;
}
// make lopsided
if (obj.style.visibility != "visible") {
my.minus_showhide_1(mkr, 1);
return;
}
// make unlopsided
// set both values to match the one we wish to keep:
var value = my.L(which).value;
my.L(mkr + "-hibar").value = value;
my.L(mkr + "-lobar").value = value;
my.minus_showhide_1(mkr, 0);
}
// Get the value from the control
// Also, save it if user says "!".
my.maybeSave = function(control) {
var val = my[control].cur.value;
var where = val.search(/! *$/);
if (where >= 0) {
//uu console.log("saving", control, val)
return my[control].saved = Number(val.substr(0, where));
}
return Number(val);
}
// Set the appropriate error bar values into the GUI.
// Return the number of decimal places used.
my.set_result_bars = function(nominal, allbars){
// Could get called very early, with no valid results yet.
if (!my.map) return;
// Allbars could be wiped out by reloading the page.
if (!allbars || !allbars.length) {
var xhi = "";
var xlo = "";
} else {
var bar_mag = -1000;
// unpack the error bars the user wants to see:
var cornax_mode = my["cornax-mode"].cur.value;
var lo = allbars[cornax_mode][0];
var hi = allbars[cornax_mode][1];
if (hi != 0 && !isNaN(hi)) {
bar_mag = Math.floor(my.log10(Math.abs(hi)));
}
if (lo != 0 && !isNaN(lo)) {
bar_mag = Math.min(bar_mag, Math.floor(my.log10(Math.abs(lo))));
}
var places = 1 - bar_mag;
var xhi = "" + my.rndFmt(hi, places);
var xlo = "" + my.rndFmt(lo, places);
}
my.L("result-hibar").value = xhi;
my.L("result-lobar").value = xlo;
my.minus_showhide_1("result");
// display the percentage
if ('number' === typeof nominal && nominal) {
my.set_pct(nominal, hi, "result-hipct");
my.set_pct(nominal, lo, "result-lopct");
} else {
my.L("result-lopct").innerHTML = "";
my.L("result-hipct").innerHTML = "";
}
return places; // used for formatting the /nominal/ result also
}
my.dumpObj = function(obj){
var rslt = "";
for (var item in obj) {
if (rslt !== "") rslt += ", ";
rslt += item + ": " + obj[item];
}
return "{" + rslt + "}";
}
my.jsWords = {
"abstract": 1,
"arguments": 1,
"await": 1,
"boolean": 1,
"break": 1,
"byte": 1,
"case": 1,
"catch": 1,
"char": 1,
"class": 1,
"const": 1,
"continue": 1,
"debugger": 1,
"default": 1,
"delete": 1,
"do": 1,
"double": 1,
"else": 1,
"enum": 1,
"eval": 1,
"export": 1,
"extends": 1,
"false": 1,
"final": 1,
"finally": 1,
"float": 1,
"for": 1,
"function": 1,
"goto": 1,
"if": 1,
"implements": 1,
"import": 1,
"in": 1,
"instanceof": 1,
"int": 1,
"interface": 1,
"let": 1,
"long": 1,
"native": 1,
"new": 1,
"null": 1,
"package": 1,
"private": 1,
"protected": 1,
"public": 1,
"return": 1,
"short": 1,
"static": 1,
"super": 1,
"switch": 1,
"synchronized": 1,
"this": 1,
"throw": 1,
"throws": 1,
"transient": 1,
"true": 1,
"try": 1,
"typeof": 1,
"var": 1,
"void": 1,
"volatile": 1,
"while": 1,
"with": 1,
"yield": 1,
}
my.badresult = function(aaa, bbb) {
my.L("result-nom").style.color = "red";
my.L("result-nom").value = aaa;
if ('undefined' === typeof bbb) return;
my.L("result-hibar").style.color = "rgb(200,0,200)";
my.L("result-hibar").value = bbb;
}
my.crunch_1 = function(opt){
"use strict";
if ('undefined' === typeof opt) opt = {};
var _rnom = my.L("result-nom");
var _rhibar = my.L("result-hibar");
_rnom.style.color = "black";
_rhibar.style.color = "black";
if (0) {
// Process options before error checking.
// It's less confusing to the user,
// especially when debugging the URL interface.
if ('undefined' !== typeof opt.mc_npts)
my.L("mc-npts").value = opt.mc_npts;
if ('undefined' !== typeof opt.nbins)
my.L("histo-nbins").value = opt.nbins;
if ('undefined' !== typeof opt.diaspo)
my.weak_diaspo(opt.diaspo);
}
var tform = my.L("tform-nom").value;
if (tform.length == 0) return my.badresult("need a formula");
if (tform.search("\\^") >= 0)
return my.badresult("^ is disallowed", "use pow( , ) or ⊕");
// Start building the mathlift code. Mathlift is important because:
// 1: Convenience: The user can type sqrt() instead of Math.sqrt().
// 2: Protection: The user cannot clobber elements of the Math object.
var mathlift = "";
var declared = {};
var stuff = Object.getOwnPropertyNames(Math).filter(function (p) {
return typeof Math[p] === 'function';
});
for (var ii = 0; ii < stuff.length; ii++) {
var item = stuff[ii];
if (item === "random") continue; // not a mathematical function
mathlift += "var " + item + "= xMath." + item + ";";
declared[item] = 1;
}
// declare a few convenience functions and constants:
var handy_var = {
"e": "xMath.E",
"π": "xMath.PI",
"pi": "xMath.PI",
"degree": "xMath.PI / 180",
"ln": "xMath.log",
"log10": "xmy.log10",
"atanyx": "xMath.atan2",
"atanen": "xMath.atan2"
}
for (item in handy_var) {
mathlift += "var " + item + "=" + handy_var[item] + ";";
declared[item] = 2;
}
var setcode = "";
// set all the user variables to the proper values:
for (var ii = 0; ii < my.invar.length; ii++){
var item = my.invar[ii];
setcode += item.ucode();
declared[item.alias()] = 3;
}
var tform1 = tform; // for debugging
// replace fancy characters with plain version
for (var fancy in my.fancyToPlain) {
var tmp = my.fancyToPlain[fancy]; // [regexp, plaintext]
tform = tform.replace(tmp[0], tmp[1]);
}
// Create a jail for the user formula to run in.
// Every identifier the formula mention is declared as a local var.
// Reasons for this include:
// 1: The user can conveniently write
// foo=123; foo+2
// without having to declare var foo by hand.
// 2: For our protection, so the user cannot clobber global variables.
// 3: To ensure that the formula is a true function of known variables.
// In contrast, if the user could create a functor with persisent
// storage, the whole scheme for calculating error bars would go
// out the window. Math.random() is disallowed for the same reason.
//
// We protect "this" by calling "new setprog" (rather than just "setprog")
// That's important, because otherwise "this" would point to global scope,
// and the user would be able to clobber this.Number and this. Object etc.
// Also it makes the final this.result different from the scrach var result.
var punctuation = new RegExp("[[\\](){}.,?:;~!=<>%^&*/ \t+-]+");
var floating = new RegExp("^([+-]?([0-9]+([.][0-9]*)?)|[.][0-9]+)$");
var atoms = tform.split(punctuation);
var scratch = "";
// Each atom should either be a number or a
// a scratch variable. If scratch, declare it now.
for (var ii = 0; ii < atoms.length; ii++){
var atom = atoms[ii];
if (atom === "") continue;
if (atom.search(floating) >= 0) continue;
// There is not much harm in allowing reserved words such
// as "if" ... except that it leads to some peculiar error messages.
// We can't declare them scratch var; OTOH they can't easily be
// used to trash the global namespace.
if (my.jsWords[atom]) continue; // can't be declared
if (declared[atom]) continue; // don't declare it again
scratch += "var " + atom + ";";
declared[atom] = 4;
}
// Split the formula into individual statments
// Parse simply using ; as the separator, based on the assumption
// that there are no quoted strings or any such ugliness.
var parse = tform.split(';');
var where = parse.length - 1;
var where;
var expr;
for (where = parse.length - 1; where >= 0; where--) {
expr = parse[where];
expr.replace(/ /g, "");
// skip the empties:
if (expr == "") continue;
if (expr == "{}") continue;
break;
}
var rebuilt = "";
for (var ii = 0; ii < where; ii++) {
rebuilt += parse[ii] + ";";
}
rebuilt += "this.result= " + expr + ";";
var doit = "";
//==== strict mode is useful here for testing,
//==== but makes it hard for naive
//==== users to assign to named variables in their formula.
//==== Furthermore, users would still be allowed to clobber globals.
//==== doit += '"use strict";';
doit += setcode + "\n"; // line 1
doit += mathlift + "\n"; // line 2
doit += scratch + "\n"; // line 3
doit += rebuilt; // line 4
if (0) { // for debugging
var y = function(x){return x};
var n = function(x){return ""};
my.L("sec-special").innerHTML =
n("setcode: " + setcode + "") +
n("rebuilt: " + rebuilt + "") +
n("declared: " + my.dumpObj(declared) + "") +
y("doit: " + doit + "") +
y("atoms: " + atoms);
}
//vvvvvvv All local vars declared before here are
//vvvvvvv ephemeral and expendable.
//// console.log("Compiled: ", doit);
var setprog = new Function("xmy", "xMath", doit);
//vvvvvvv All local vars declared after here are
//vvvvvvv safe, because the user formula doesn't know about them.
//vvvvvvv OTOH, the user gets to play Calvinball with global variables.
var error_count = 0;
var onepoint = function(){
try {
var obj = new setprog(my, Math);
return obj.result;
} catch (err) {
if (++error_count > 100) {
var foo = new Error("Too many errors");
foo.prev = err;
throw foo;
}
}
}
for (var ii = 0; ii < my.invar.length; ii++){
var item = my.invar[ii];
item.sync();
item.idx = 0;
}
// Do the C3T stuff.
var nominal = onepoint(); // central "nominal" point
if ('number' !== typeof nominal) return my.badresult(nominal);
my.nominal = nominal; // save it so set_cornax can see it
// Always do both cornax modes:
var allbars = [];
allbars[0] = my.axis_bars(nominal, onepoint);
allbars[1] = my.corner_bars(nominal, onepoint);
my.allbars = allbars;
// unpack the most-relevant error bars:
var cornax_mode = my["cornax-mode"].cur.value;
var lo = allbars[cornax_mode][0];
var hi = allbars[cornax_mode][1];
var places = my.set_result_bars(nominal, allbars);
// round nominal to the same number of places as the error bar:
if ('number' === typeof nominal) {
_rnom.value = my.rndFmt(nominal, places);
} else {
_rnom.value = nominal;
}
// special check for huge number of zeros
// after the decimal point /and/ after all digits
// /and/ not needed to line up with the uncertainty:
//
//xxx Does this ever occur?
//xxx var parse = rstr.split("e");
//xxx rstr = parse[0];
//xxx if (0 && bar_mag < -500 && rstr.search(/[.]/) >= 0) {
//xxx //xxx console.log(rstr, parse[0], bar_mag)
//xxx rstr = rstr.replace(/0+$/, "~");
//xxx }
//xxx if (parse.length > 1) rstr += parse[1];
if (my.mc_mode) {
var sum = 0;
var sumsq = 0;
var lobound = nominal - lo;
var hibound = nominal + hi;
var tails_lo = 0;
var tails_hi = 0;
var tails_losigma = 0;
var tails_hisigma = 0;
var scatter = [];
var start = Date.now()/1000;
if ('undefined' !== typeof opt.mc_npts)
my.L("mc-npts").value = opt.mc_npts;
var npts = my.maybeSave("mc-npts");
// the big loop over all MC points,
// evaluating the user's transformation formula
for (var mcii = 0; mcii < npts; mcii++) {
// randomize all the input variables:
for (var ii = 0; ii < my.invar.length; ii++) {
var obj = my.invar[ii];
obj.idx = obj.shape ? my.BoxMuller() : 2*Math.random() - 1;
}
var tmp = onepoint();
if (tmp === null) continue;
scatter.push(tmp);
sum += tmp;
sumsq += tmp*tmp;
if (tmp < lobound) tails_lo++;
if (tmp > hibound) tails_hi++;
}
npts = scatter.length; // could be less than before, due to nulls
var end = Date.now()/1000;
var mean = sum/npts;
var variance = sumsq / npts - mean*mean;
var stdev = variance > 0 ? Math.sqrt(variance) : 0;
hibound = mean + stdev
lobound = mean - stdev;
for (var jj = 0; jj < npts; jj++) {
var tmp = scatter[jj];
if (tmp < lobound) tails_losigma++;
if (tmp > hibound) tails_hisigma++;
}
// format the mean and standard deviation
var bar_mag = -1000;
if (stdev > 0)
bar_mag = Math.floor(my.log10(Math.abs(stdev)));
var places = 1 - bar_mag;
my.L("mean-hibar").value
= "" + my.rndFmt(stdev, places);
my.L("mean-nom").value
= "" + my.rndFmt(mean, places);
my.set_pct(mean, stdev, "mean-hipct");
// Beware: array.sort does the sort in place!
var sscat = scatter.slice().sort(function(a, b){return a-b});
// This will fail if selecting the first small_size batch of points
// is not a random sample ... i.e. if the formula is some
// function of mcii, rather than of the random x,y,z values.
var small_size = Math.min(10000, npts);
var small_scat = scatter.slice(0, small_size);
var small_sscat = (small_scat.length < npts) ?
small_scat.sort(function(a, b){return a-b}) :
sscat;
// locate the special percentiles:
var lintel = sscat.length-1; // fewer lintels than fenceposts
var loside = my.interp(sscat, lintel * (1-my.erf1sigma));
var median = my.interp(sscat, lintel * 0.5 );
var hiside = my.interp(sscat, lintel * (my.erf1sigma) );
var hibar = hiside - median;
var lobar = median - loside;
allbars[2] = [lobar, hibar, median];
var bigbar = Math.max(hibar, lobar);
bar_mag = -1000;
if (bigbar > 0)
bar_mag = Math.floor(my.log10(Math.abs(bigbar)));
places = 1 - bar_mag;
my.L("median-hibar").value
= "" + my.rndFmt(hibar, places);
my.set_pct(median, hibar, "median-hipct");
my.L("median-lobar").value
= "" + my.rndFmt(lobar, places);
my.set_pct(median, lobar, "median-lopct");
my.L("median-nom").value
= "" + my.rndFmt(median, places);
my.minus_showhide_1("median");
// show the timing measurement:
var dt = end - start;
var apiece = dt / scatter.length;
my.L("show-timing").innerHTML
= "@ " + my.maground(apiece*1e6*100)/100 + " μs";
my.L("show-timing2").innerHTML = "= "
+ my.maground(dt*1000)/1000 + " s";
// show the percentage of outliers
// resolution scales like the square root of npts
places = Math.floor(my.log10(npts)/2);
places = Math.max(0, places);
my.L("tails-hisigma").value
= my.rndFmt(100*tails_hisigma/scatter.length, places) + " %";
my.L("tails-losigma").value
= my.rndFmt(100*tails_losigma/scatter.length, places) + " %";
my.L("tails-hibar").value
= my.rndFmt(100*tails_hi/scatter.length, places) + " %";
my.L("tails-lobar").value
= my.rndFmt(100*tails_lo/scatter.length, places) + " %";
// plot the graphs
if (my.plot_mode){
var center = (opt.centermean) ? mean : nominal;
var bigbar = (opt.centermean) ? stdev : Math.max(hi, lo);
my.plot_cume (scatter, small_sscat, center, bigbar,
nominal, allbars, mean, stdev, opt);
// similar, but uses the non-small sscat:
my.plot_density (scatter, sscat, center, bigbar,
nominal, allbars, mean, stdev, opt);
}
} // end of if mc_mode
// now that all the dust has settled,
// set the name of the result variable:
//------ var validName = /^[$A-Z_][0-9A-Z_$]*$/i;
var assignment = /^ *([$A-Z_][0-9A-Z_$]*) *[-+*/%]?=[^=]/i;
var newname = assignment.exec(expr);
if (newname) my.L("result-alias").innerHTML
= newname[1];
}
my.crunch = function(opt){
try {
// the use of miscOpt here is dubious.
// there may be better ways to do it,
// especially for variables on the HTML form
my.crunch_1(my.meld(my.miscOpt, opt));
} catch (e) {
console.log("Bombed out:", e.message);
my.badresult(e.message);
if (e.prev) {
console.log("Last error:", e.prev.message);
console.log("in:", e.prev.fileName);
console.log("line:", e.prev.lineNumber);
}
}
return;
}