// Javascript rewrite of // http://statistics.berkeley.edu/~stark/Java/Html/Ci.htm // // Authors: James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the histogram (and // controls) in. // params: A javascript object with various parameters to customize the chart. function Stici_Ci(container_id, params) { var self = this; // jQuery object containing the entire chart. var container = jQuery('#' + container_id); // These are constants. var maxSamples = 1000; // max number of samples var maxSampleSize = 250; // max sample size var nDigs = 4; // number of digits in numbers in box var defaultPopSize = 10; var rSE = { "True SE": "true se", "Estimated SE": "estimated se", "Bound on SE (0-1 box only)": "bound on se (0-1 box only)" }; var rSource = { "Normal": "normal", "Uniform": "uniform", "Box": "box", "0-1 Box": "0-1 box" }; // User-configurable parameters. These are directly lifted from // Ci.java. var options = { factor: 1, showTruth: true, toggleTruth: true, editBox: true, replaceControl: false, replace: true, sampleSize: 2, sources: "all", seChoices: "all", useSe: null, boxContents: "0,1,2,3,4" }; jQuery.extend(options, params); // UI Elements. var sampleSizeBar = null; // SticiTextBar var samplesToTakeBar = null; // SticiTextBar var facBar = null; // SticiTextBar var takeSampleButton = null; var hideBoxButton = null; // SticiToggleButton var sourceChoice = null; // SticiComboBox var seChoice = null; // SticiComboBox var box = null; // <textarea> var replaceCheck = null; // SticiCheck var sourceLabel = null; var seLabel = null; var myCiPlot = new CiPlot(); // CiPlot var title = null; var lastItem = null; var lastSE = null; var useSe = null; var sampleSize = options.sampleSize; // size of current sample var samplesToTake = 1; // number of samples to take of that size var samplesSoFar = 0; // number of samples taken so far var cover = null; // number of intervals that cover var pop = null; // elements of the population var nPop = 0; var sample = null; // elements of the current sample var boxAve = null; // the population mean var boxSd = null; // the population SD var sampleMean = []; // the history of sample means var sampleSe = []; // the history of sample SE(mean)'s var seUsed = []; // vector of SE's to use for intervals var factor = options.factor; // the blow-up factor for the intervals var showTruth = options.showTruth; // show the box contents and the true mean var toggleTruth = options.toggleTruth; // allow toggling hide/show var toggleSe = options.toggleSe; // allow toggling true/sample SE var editBox = null; // allow box contents to be edited var stats = null; var coverLabel = null; // myLabel[0] var samplesLabel = null; // myLabel[2] var aveLabel = null; // myLabel[1] var sdLabel = null; // myLabel[3] function init() { var o = jQuery('<div/>').addClass('stici stici_ci'); container.append(o); // General pieces var top = jQuery('<div/>').addClass('top_controls'); var middle = jQuery('<div/>').addClass('middle'); var bottom = jQuery('<div/>').addClass('bottom_controls'); o.append(top, middle, bottom); // Compose the top piece. top.append(createSelectDataSourceControls()); // Compose the middle pieces. middle.append(createStatsBox(), myCiPlot, createPopulationBox()); // Compose the bottom piece. bottom.append(createInfoRow()); // Make sure everything is sized correctly. middle.height(container.height() - top.height() - bottom.height()); myCiPlot.width(middle.width() - stats.width() - box.width() - 20); // Set all of the handlers. jQuery.each([hideBoxButton, sampleSizeBar, samplesToTakeBar, facBar, box, seChoice, sourceChoice, replaceCheck], function(_, e) {e.change(handleEvent);}); takeSampleButton.click(handleEvent); // Below this point lie methods used to build the individual pieces. // Top. function createSelectDataSourceControls() { var dataSelectControls = jQuery('<div/>'); var n; hideBoxButton = new SticiToggleButton({ trueLabel: 'Hide Box', falseLabel: 'Show Box', value: true }); var showSources = true; if (options.sources != "all") { n = 0; var oldRSource = rSource; rSource = {}; jQuery.each(oldRSource, function(k, v) { if (options.sources.indexOf(k) >= 0 || options.sources.indexOf(v) >= 0) { rSource[k] = v; n += 1; } }); if (n <= 1) showSources = false; } sourceChoice = new SticiComboBox({ label: "Sample from: ", options: rSource, selected: "Box" }); if (showSources) dataSelectControls.append(sourceChoice); replaceCheck = new SticiCheck({ label: ' with replacement', value: options.replace, readonly: !options.replaceControl }); if (!options.replaceControl && !options.replace) replaceCheck.label(' without replacement'); takeSampleButton = jQuery('<button id="takeSample"/>').text('Take Sample'); dataSelectControls.append(takeSampleButton); dataSelectControls.append(replaceCheck); dataSelectControls.append(hideBoxButton); return dataSelectControls; } // Middle. function createPopulationBox() { var container = jQuery('<div/>').addClass('population'); box = jQuery('<textarea/>'); if (!options.editBox) box.attr("readonly", "readonly"); container.append(box); return container; } function createStatsBox() { stats = jQuery('<div/>').addClass('statsText'); aveLabel = jQuery('<p/>'); samplesLabel = jQuery('<p/>'); sdLabel = jQuery('<p/>'); stats.append(samplesLabel, sdLabel, aveLabel); return stats; } // Bottom. function createInfoRow() { var row = jQuery('<div/>'); if (options.seChoices != "all") { var oldRSE = rSE; rSE = {}; jQuery.each(oldRSE, function(k, v) { if (options.seChoices.indexOf(k) >= 0 || options.seChoices.indexOf(v) >= 0) { rSE[k] = v; } }); } seChoice = new SticiComboBox({ label: " * ", options: rSE }); sampleSizeBar = jQuery('<input type="text"/>').val(sampleSize); samplesToTakeBar = jQuery('<input type="text"/>').val(samplesToTake); facBar = jQuery('<input type="text" id="bins" />').val(factor); coverLabel = jQuery('<p/>'); row.append("Sample Size: ", sampleSizeBar, " Samples to take: ", samplesToTakeBar, " Intervals: +/- ", facBar, seChoice, " ", coverLabel); return row; } if (options.useSe !== null) { if (options.useSe == "estimated") seChoice.selected("Estimated SE"); else if (options.useSe == "true") seChoice.selected("True SE"); else if (options.useSe == "bound") seChoice.selected("Bound on SE (0-1 box only)"); } // The UI has been set up. Now initialize the data. var bc = ""; if (options.boxContents !== null) { bc = options.boxContents; } else if (sourceChoice.selected() == "Normal") { bc = "Normal"; } else if (sourceChoice.selected() == "Uniform") { bc = "Uniform"; } else { bc = "0 1 2 3 4"; } setBox(bc, true); if (!replaceCheck.checked()) { sampleSize = Math.min(sampleSize, nPop); } setSe(); // initialize the vector of SEs to use // set the labels aveLabel.text("#SEs:"); if (showTruth) { coverLabel.text("0% cover"); aveLabel.text("Ave(Box): " + boxAve.fix(2)); sdLabel.text("SD(Box): " + boxSd.fix(2)); } else { coverLabel.text(" "); aveLabel.text(" "); sdLabel.text(" "); } samplesLabel.text("Samples: " + samplesSoFar); samplesToTakeBar.val(samplesToTake); sampleSizeBar.val(sampleSize); facBar.val(factor); //myCiPlot = new CiPlot(boxAve, showTruth, null, seUsed, factor); showPlot(); // refresh the ciplot } function setSe() { var f = 1.0; var i; if (!replaceCheck.checked()) { f = Math.sqrt((nPop-sampleSize+0.0)/(nPop-1.0)); } if (seChoice.selected() == "True SE") { if (sourceChoice.selected() == "Box" || sourceChoice.selected() == "0-1 Box") { for (i = 0; i < samplesSoFar; i++) { seUsed[i] = f*boxSd/Math.sqrt(sampleSize + 0.0); } } else if (sourceChoice.selected() == "Normal") { for (i = 0; i < samplesSoFar; i++) { seUsed[i] = 1.0/Math.sqrt(sampleSize + 0.0); } } else if (sourceChoice.selected() == "Uniform") { for (i = 0; i < samplesSoFar; i++) { seUsed[i] = (1.0/12.0)/Math.sqrt(sampleSize + 0.0); } } else { console.error("Error in Ci.setSE(): unsupported source " + sourceChoice.selected()); } } else if (seChoice.selected() == "Estimated SE") { for (i = 0; i < samplesSoFar; i++) { seUsed[i] = f*sampleSe[i]; } } else if (seChoice.selected() == "Bound on SE (0-1 box only") { for (i = 0; i < samplesSoFar; i++) { seUsed[i] = f*0.5/Math.sqrt(sampleSize + 0.0); } } else { console.error("Error in Ci.setSE(): SE option not set!"); } return; } function initPop() { var i; // compute population statistics if (sourceChoice.selected() == "Box" || sourceChoice.selected() == "0-1 Box") { nPop = pop.length; boxAve = 0.0; boxSd = 0.0; for (i = 0; i < nPop; i++) { boxAve += pop[i]; } boxAve /= nPop; for (i = 0; i < nPop; i++) { boxSd += (pop[i] - boxAve)*(pop[i] - boxAve); } boxSd = Math.sqrt(boxSd/nPop); } else if (sourceChoice.selected() == "Normal") { replaceCheck.checked(true); nPop = 0; boxAve = 0.0; boxSd = 1.0; } else if (sourceChoice.selected() == "Uniform") { replaceCheck.checked(true); nPop = 0; boxAve = 0.5; boxSd = Math.sqrt(1.0/12.0); } // reset the labels if (showTruth) { coverLabel.text("0% cover"); aveLabel.text("Ave(Box): " + boxAve.fix(3)); sdLabel.text("SD(Box): " + boxSd.fix(3)); } else { coverLabel.text(" "); aveLabel.text(" "); sdLabel.text(" "); } } function handleEvent(e) { var i; if (sampleSizeBar.is(e.target)) { // clear history, reset sample size sampleSize = parseFloat(sampleSizeBar.val()); if (!replaceCheck.checked()) { sampleSize = Math.min(sampleSize, nPop); sampleSizeBar.val(sampleSize); } refresh(); } else if (facBar.is(e.target)) { factor = parseFloat(facBar.val()); setCover(); showPlot(); } else if (samplesToTakeBar.is(e.target)) { samplesToTake = parseFloat(samplesToTakeBar.val()); } else if (replaceCheck.is(e.target)) { if (sourceChoice.selected() != "Box" && sourceChoice.selected() != "0-1 Box") { replaceCheck.checked(true); } else { sampleSize = Math.min(sampleSize, nPop); sampleSizeBar.val(sampleSize); } refresh(); } else if (sourceChoice.is(e.target)) { var thisItem = sourceChoice.selected(); if (thisItem != lastItem) { lastItem = thisItem; if ( sourceChoice.selected() == "Box" ) { if (lastSE == "Bound on SE (0-1 box only)") { lastSE = "Estimated SE"; seChoice.selected(lastSE); } setBox(box.val(),true); } else if (sourceChoice.selected() == "0-1 Box") { setBox(box.val(),true); } else { setBox(sourceChoice.selected()); } showTruth = true; showPlot(); } } else if (seChoice.is(e.target)) { var thisSE = seChoice.selected(); if (thisSE != lastSE) { if (thisSE == "Bound on SE (0-1 box only)") { // make sure this is a 0-1 box if (sourceChoice.selected() != "0-1 Box") { seChoice.selected(lastSE); } } lastSE = thisSE; setSe(); setCover(); showPlot(); } } else if (box.is(e.target)) { setBox(box.val(),false); showPlot(); } else if (takeSampleButton.is(e.target)) { e.preventDefault(); var lim = maxSamples - samplesSoFar; // number possible for (i = 0; i < Math.min(samplesToTake, lim); i++) { xBar(); } samplesLabel.text("Samples: " + samplesSoFar); if (showTruth) { coverLabel.text((cover/samplesSoFar).pct() + " cover"); } showPlot(); } else if (hideBoxButton.is(e.target)) { showTruth = hideBoxButton.val(); if (!showTruth) { box.val("Contents \n Hidden"); sourceChoice.selected('Box'); randBox(); samplesSoFar = 0; setCover(); samplesLabel.text("Samples: " + samplesSoFar); showPlot(); } else { var thePop = ""; for (i = 0; i < nPop; i++) { thePop += pop[i].fix(nDigs) + "\n"; // print the population } setBox(thePop,true, false); setCover(); showPlot(); } } } function showPlot() { if (samplesSoFar > 0) { var sv = new Array(samplesSoFar); sv = sampleMean.slice(0, samplesSoFar); myCiPlot.redraw(boxAve, showTruth, sv, seUsed, factor); } else { myCiPlot.redraw(boxAve, showTruth, null, seUsed, factor); } return; } function setCover() { cover = 0; var wide = 0; for (var i = 0; i < samplesSoFar; i++) { wide = factor*seUsed[i]; if (Math.abs(sampleMean[i] - boxAve) <= wide) cover++; } if (showTruth) { if (samplesSoFar > 0) coverLabel.text((cover/samplesSoFar).pct() + " cover"); else coverLabel.text("0% cover"); } else { coverLabel.text(" "); } } function randBox() { nPop = defaultPopSize; pop = Array(nPop); var i; if (sourceChoice.selected() != "0-1 Box") { var lim = 50*rand.next(); var ctr = 25*rand.next(); for (i = 0; i < nPop; i++) { pop[i] = lim*rand.next() - ctr; } } else { var ones = Math.floor(9*rand.next()+1); for (i = 0; i < ones; i++) { pop[i] = 1; } for (i = ones; i < nPop; i++) { pop[i] = 0; } } initPop(); } function setBox(newBox, updateBox, reInit) { // parse new population newBox = newBox.replace(/^[,\n\t\r ]+|[,\n\t\r ]+$/g, ''); var i; if (updateBox === undefined) updateBox = true; if (reInit === undefined) reInit = true; if (sourceChoice.selected() == '0-1 Box' && (newBox == 'Uniform' || newBox == 'Normal')) newBox = '0 1'; if (newBox.toLowerCase() == "normal") { replaceCheck.checked(true); pop = new Array(2); pop[0] = -4; pop[1] = 4; box.val("Normal"); sourceChoice.selected("Normal"); } else if (newBox.toLowerCase() == "uniform") { replaceCheck.checked(true); pop = new Array(2); pop[0] = 0; pop[1] = 1; box.val("Uniform"); sourceChoice.selected("Uniform"); } else { pop = newBox.split(/[,\n\t\r ]+/); pop = jQuery.map(pop, function(v) {return parseFloat(v);}); nPop = pop.length; var zeroOneOnly = true; if (sourceChoice.selected() == "0-1 Box") { for (i = 0; i < nPop; i++) { if ((pop[i] !== 0.0) && (pop[i] != 1.0)) { zeroOneOnly = false; if (Math.abs(pop[i]) <= 0.5) { pop[i] = 0; } else { pop[i] = 1; } } } } if (updateBox || (!zeroOneOnly && sourceChoice.selected() == "0-1 Box")) { if (showTruth) { box.val(jQuery.map(pop, function(e) {return e.fix(nDigs);}).join("\n")); } else { box.val("Contents Hidden"); } } } if (reInit) { initPop(); samplesSoFar = 0; setCover(); samplesLabel.text("Samples: " + samplesSoFar); } if (!replaceCheck.checked()) { sampleSize = Math.min(sampleSize, nPop); } } // ends setBox(String, boolean) function refresh() { samplesSoFar = 0; var sSd = boxSd/Math.sqrt(sampleSize); setCover(); showPlot(); } function xBar() { var xb = 0; var sse = 0; var x = new Array(sampleSize); var i; if (sourceChoice.selected() == "Box" || sourceChoice.selected() == "0-1 Box") { if (replaceCheck.checked()) { for (i = 0; i < sampleSize; i++) { x[i] = pop[Math.floor(rand.next()*nPop)]; xb += x[i]; } } else { var samInx = listOfDistinctRandInts(sampleSize, 0, nPop-1); for (i = 0; i < sampleSize; i++) { x[i] = pop[samInx[i]]; xb += x[i]; } } } else if (sourceChoice.selected() == "Uniform") { for (i = 0; i < sampleSize; i++) { x[i] = rand.next(); xb += x[i]; } } else if (sourceChoice.selected() == "Normal") { for (i = 0; i < sampleSize; i++) { x[i] = rNorm(); xb += x[i]; } } xb /= sampleSize; sse = sampleSd(x)/Math.sqrt(sampleSize); sampleMean[samplesSoFar] = xb; sampleSe[samplesSoFar] = sse; var f = 1.0; if (!replaceCheck.checked()) { f = Math.sqrt((nPop-sampleSize+0.0)/(nPop-1.0)); } if (seChoice.selected() == "True SE") { seUsed[samplesSoFar++] = f*boxSd/Math.sqrt(sampleSize); } else if (seChoice.selected() == "Bound on SE (0-1 box only") { seUsed[samplesSoFar++] = f*0.5/Math.sqrt(sampleSize); } else { seUsed[samplesSoFar++] = f*sse; } if (Math.abs(xb - boxAve) <= factor*seUsed[samplesSoFar - 1]) { cover++; } } doWhileVisible(container, function() { init(); }); function CiPlot() { var self = this; self = jQuery('<div/>').addClass('stici_ciplot stici_chartbox'); self.redraw = function(truth, showTruth, center, se, factor) { self.children().remove(); var height = self.height() - 20; var width = self.width(); var yScale = 0; var x_min = -0.1; var x_max = 1; if (center !== null && center !== undefined) { if (!isNaN(center.min())) x_min = center.min(); if (!isNaN(center.max())) x_max = center.max(); yScale = height / center.length; jQuery.each(center, function(i, c) { if (isNaN(c)) return; x_min = Math.min(x_min, c - factor * se[i]); x_max = Math.max(x_max, c + factor * se[i]); }); } if (showTruth) { x_min = Math.min(x_min, truth); x_max = Math.max(x_max, truth); } // Draw the axis. var scale = d3.scale.linear() .domain([x_min, x_max]) .range([0, width]); d3.select(self.get(0)) .append('svg') .attr('class', 'axis') .append('g').call(d3.svg.axis().scale(scale).orient('bottom')); if (center === null) return; // Draw the box. d3.select(self.get(0)) .append('svg') .selectAll('div') .data(center) .enter() .append('rect') .attr('y', function(d, i) { return height - i * yScale - 4; }) .attr('height', 4) .attr('x', function(d, i) { return (d - x_min - se[i] * factor) / (x_max - x_min) * width; }) .attr('width', function(d, i) { return (se[i] * factor * 2) / (x_max - x_min) * width; }) .attr('class', function(d, i) { var lo = d - factor * se[i]; var hi = d + factor * se[i]; if (!showTruth) return 'hidden'; else if (truth >= lo && truth <= hi) return 'inner'; else return 'outer'; }); // Draw the tick in the center. d3.select(self.get(0)) .append('svg') .selectAll('div') .data(center) .enter() .append('rect') .attr('y', function(d, i) { return height - i * yScale - 4; }) .attr('height', 4) .attr('x', function(d) { return (d - x_min) / (x_max - x_min) * width - 2; }) .attr('width', 5); // Draw the truth line. if (showTruth) { var line = d3.svg.line() .x(function(d) { return (d - x_min) / (x_max - x_min) * width; }) .y(function(d, i) { if (i === 0) return 0; else return height; }); d3.select(self.get(0)) .append('svg') .attr('class', 'truth') .append('path') .data([[truth, truth]]) .attr('d', line); } }; return self; } } // Javascript rewrite of // http://statistics.berkeley.edu/~stark/Java/Html/HistHiLite.htm // // Authors: James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the histogram (and // controls) in. // params: A javascript object with various parameters to customize the chart. // // Show normal by default. Normal can still be toggled by the button. // - showNormal: false // // // Whether or not to display the 'Show Normal' button. // - showNormalButton: true // // // Default number of bins to display. // - bins: 10 // // // Whether the user can set the number of bins. // - changeNumBins: true // // // Whether or not to display the 'Show Univariate Stats' button. // - showUnivariateStats: true // // // Initial bounds for area from/to sliders. // - hiLiteHi: null // - hiLiteLo null // // // There are three different ways to supply input to the histogram: // // 1) External JSON-encoded data file. // // 2) Manual specification of bin ends and counts. // // 3) Binomial distribution generated from given n and p. // // // // These input methods are all mutually exclusive - if the parameters for a // // particular input method are set, then the parameters for the other input // // methods should not be set. // // // // TODO(jmeady): Rip HistHiLite into separate scriptlets for each of these // // use cases. // // // 1) External JSON-encoded data file // // // Array of URLs (as strings) of json-encoded datasets. // - data: null // // // Whether or not to display the 'List Data' button. // - listData: true // // // Whether or not the user can select restricted subsets of the data. // - restrict: false // // // 2) Manual specification of bin ends and counts. // // // // showNormal, showNormalButton, showUnivariateStats, changeNumBins, and // // listData will all automatically be set to false by default. The normal // // curve may be enabled, but mu and sd must be specified. // // // Array of bin counts to use instead of parsing data. // - counts: null // // // Array of bin ends. If counts is set, ends must also be set. // - ends: null // // // Manually specify data mean. // - mu: null // // // Manually specify data standard deviation. // - sd: null // // // 3) Binomial distribution generated from given n and p. // // // Number of trials. // - n: null // // // Probability of success. // - p: null // // // Show sliders to change n and p // - binomialBars: true function Stici_HistHiLite(container_id, params) { var self = this; if (!params instanceof Object) { console.error('Stici_HistHiLite params should be an object'); return; } // Configuration option defaults. this.options = { showNormalButton: true, showNormal: false, bins: 10, changeNumBins: true, showUnivariateStats: true, hiLiteHi: null, hiLiteLo: null, data: null, listData: true, restrict: false, counts: null, ends: null, n: null, p: null, binomialBars: true }; // For debugging: Warn of user params that are unknown. jQuery.each(params, function(key) { if (typeof(self.options[key]) == 'undefined') console.warn('Stici_HistHiLite: Unknown key \'' + key + '\''); }); // Override options with anything specified by the user. jQuery.extend(this.options, params); // jQuery object containing the entire chart. this.container = jQuery('#' + container_id); // Labels for the data. this.dataFields = null; // The data itself. this.dataValues = null; // The URL we got the JSON-encoded data from. this.dataSource = null; // Currently rendered data. this.data = null; this.restrictedData = null; // Histogram information calculated via stat_utils.js. Cached here. this.nBins = self.options.bins; this.binEnds = null; this.binCounts = null; this.sd = 0; this.mu = 0; this.n = self.options.n; this.p = self.options.p; this.normalCurve = function(x) { return normPdf(self.mu, self.sd, x); }; // Various handles to important jQuery objects. this.urlInput = null; this.dataSelect = null; this.variableSelect = null; this.areaFrom = null; this.areaTo = null; this.areaInfoDiv = null; this.showNormalButton = null; this.showingOriginal = null; this.showingRestricted = null; this.additionalInfo = null; this.histogram = null; // Restricted variable handles. this.restrictedVariable = null; this.restrictLowerEnable = null; this.restrictLower = null; this.restrictUpperEnable = null; this.restrictUpper = null; this.restrictedStates = {}; this.restrictedCounts = null; this.restrictedSd = 0; this.restrictedMu = 0; this.restrictedNormal = function(x) { return normPdf(self.restrictedMu, self.restrictedSd, x); }; // Mode. this.dataIsBinomial = false; this.dataIsManual = false; this.dataIsExternal = false; // This method will be set according to which data source we are using. this.reloadData = null; if (self.options.n !== null || self.options.p !== null) { // Binomial data source. self.dataIsBinomial = true; self.options.showUnivariateStats = false; self.options.listData = false; self.options.changeNumBins = false; self.reloadData = loadBinomialData; } else if (self.options.counts !== null || self.options.ends !== null) { // Manually specified data source. self.dataIsManual = true; self.dataSource = null; if (!params.showNormal) self.options.showNormal = false; if (!params.showNormalButton) self.options.showNormalButton = false; self.options.showUnivariateStats = false; self.options.listData = false; self.options.changeNumBins = false; self.options.binomialBars = false; self.reloadData = loadManualData; } else if (self.options.data instanceof Array) { // External data source. self.dataIsExternal = true; self.dataSource = self.options.data[0]; self.options.binomialBars = false; if (self.options.restrict) { self.reloadData = function() { loadExternalData(function() { self.restrictedVariable.html(''); self.restrictedStates = {}; jQuery.each(self.dataFields, function(i, field) { if (field.indexOf('//') === 0) return; var dat = jQuery.map(self.dataValues, function(values) { return parseFloat(values[i]); }); self.restrictedStates[i] = { lower: dat.min(), lowerEnable: false, upper: dat.max(), upperEnable: false }; }); self.restrictedVariable.html(self.variableSelect.html()); self.restrictedVariable.val( (parseInt(self.variableSelect.val(), 10) + 1) % self.variableSelect.children().length); updateVariableRestrictionControls(); updateRestrictedData(); }); }; } else { self.reloadData = loadExternalData; } } else { console.error('Unknown data source.'); return; } this.reloadChart = function() { redrawChart(); self.areaFrom.bounds(self.binEnds.min(), self.binEnds.max()); self.areaTo.bounds(self.binEnds.min(), self.binEnds.max()); if (!self.dataIsBinomial) { if (self.options.hiLiteLo === null) { self.areaFrom.val(self.binEnds.min()); } else { self.areaFrom.val(self.options.hiLiteLo); } if (self.options.hiLiteHi === null) { self.areaTo.val(self.binEnds.min()); } else { self.areaTo.val(self.options.hiLiteHi); } } }; function redrawChart() { if (null !== self.restrictedCounts && self.showingRestricted.is(':checked')) { if (self.showingOriginal.is(':checked')) { self.histogram.set( self.binEnds, [self.binCounts, self.restrictedCounts], [self.normalCurve, self.restrictedNormal]); } else { self.histogram.set( self.binEnds, [[], self.restrictedCounts], [null, self.restrictedNormal]); } } else { self.histogram.set(self.binEnds, self.binCounts, self.normalCurve); } // Do we really need this? refreshSelectedAreaOverlay(); } function loadExternalData(cb) { self.dataSource = self.dataSelect.val(); jQuery.getJSON(self.dataSource, function(data) { self.dataFields = data[0]; self.dataValues = data.slice(1); self.variableSelect.children().remove(); jQuery.each(self.dataFields, function(i, field) { if (field.indexOf('//') === 0) return; self.variableSelect.append( jQuery('<option/>').attr('value', i).text(field) ); }); self.variableSelect.val(2); // TODO(jmeady): un-hardcode this. refreshFromExternalData(); self.reloadChart(); if (cb) cb(); }); } function refreshFromExternalData() { self.data = jQuery.map(self.dataValues, function(values) { return parseFloat(values[self.variableSelect.val()]); }); self.binEnds = histMakeBins(self.nBins, self.data); self.binCounts = histMakeCounts(self.binEnds, self.data); self.sd = sd(self.data); self.mu = mean(self.data); var info = 'n=' + self.data.length; info += ' '; info += 'Mean=' + self.mu.toFixed(3); info += ' '; info += 'SD=' + self.sd.toFixed(3); self.additionalInfo.html(info); } function loadManualData() { self.data = [self.options.ends.min(), self.options.ends.max()]; if (self.options.sd === null) self.sd = sd(self.data); else self.sd = self.options.sd; if (self.options.mu === null) self.mu = mean(self.data); else self.mu = self.options.mu; self.binEnds = self.options.ends; self.binCounts = self.options.counts; var total = self.binCounts.reduce(function(a, b) { return a + b; }); for (var i = 0; i < self.binCounts.length; i++) { self.binCounts[i] /= total * (self.binEnds[i + 1] - self.binEnds[i]); } self.reloadChart(); } function loadBinomialData() { var p = self.p; var n = self.n; self.nBins = n + 1; self.binCounts = [Math.pow((1 - p), n)]; self.binEnds = [-0.5]; for (var i = 1; i < self.nBins; i++) { if (p < 1) { self.binCounts.push( self.binCounts[i - 1] * p * (n - i + 1) / ((1 - p) * i)); } else { self.binCounts.push(0); } self.binEnds.push(i - 0.5); } self.binEnds.push(n + 0.5); if (p == 1) self.binCounts[self.nBins - 1] = 1; self.sd = Math.sqrt(n * p * (1 - p)); self.mu = n * p; self.reloadChart(); if (!self.options.binomialBars) self.additionalInfo.html('n=' + n + ' p=' + p); } // Initializes the chart controls. Adds the sliders, input fields, etc. function initControls() { // Create html for basic structure: // top_controls -> stici_chart -> area_info -> botom_controls. var o = jQuery('<div/>').addClass('stici').addClass('stici_histhilite'); self.container.append(o); var top = jQuery('<div/>').addClass('top_controls'); o.append(top); self.histogram = new SticiHistogram(); o.append(self.histogram); self.areaInfoDiv = jQuery('<div/>') .addClass('area_info'); o.append(self.areaInfoDiv); var bottom = jQuery('<div/>').addClass('bottom_controls'); o.append(bottom); var rowHeight = 30; // px var topOffset = 0; var bottomOffset = 0; function appendHeaderRow(o) { top.append(o); topOffset += rowHeight; } function appendFooterRow(o) { bottom.append(o); bottomOffset += rowHeight; } // TODO(jmeady): Move this into a more general file function createPopBox() { var parent = jQuery('<div/>').addClass('popbox'); var open = jQuery('<button/>').addClass('open').text('Click Here!'); var collapse = jQuery('<div/>').addClass('collapse'); collapse.html( '<div class="box">' + ' <div class="arrow"></div>' + ' <div class="arrow-border"></div>' + ' <div class="popbox-content">' + ' </div>' + ' <a href="#" class="close">close</a>' + '</div>'); parent.append(open).append(collapse); var content = collapse.find('.popbox-content'); content.text('Content Here :)'); parent.data('onPopBox', function() { parent.find('.viewport').parent().width(content.width() + 20); }); return { parent: parent, button: open, content: content }; } function createSelectDataSourceControls() { // TODO(jmeady): It should not be necessary to keep these controls as // member variables. self.urlInput = jQuery('<input type="text" />'); self.dataSelect = jQuery('<select/>').change(self.reloadData); self.variableSelect = jQuery('<select/>').change(function() { refreshFromExternalData(); if (self.options.restrict) updateRestrictedData(); self.reloadChart(); }); var dataSelectControls = jQuery('<div/>'); dataSelectControls.append('Data: '); if (self.options.data.length > 1) { dataSelectControls.append(self.dataSelect); } else { dataSelectControls.append( self.options.data[0].replace(/^.*[\\\/]/, '')); dataSelectControls.append(' '); } jQuery.each(self.options.data, function(i, dataUrl) { self.dataSelect.append(jQuery('<option/>') .attr('value', dataUrl) .text(dataUrl.replace(/^.*[\\\/]/, ''))); }); dataSelectControls.append('Variable: ').append(self.variableSelect); if (self.options.restrict) { self.showingOriginal = jQuery('<input type="checkbox" />'); self.showingOriginal.prop('checked', true); self.showingOriginal.change(function() { redrawChart(); }); dataSelectControls.append(self.showingOriginal) .append('Show original data'); self.showingRestricted = jQuery('<input type="checkbox" />'); self.showingRestricted.prop('checked', true); self.showingRestricted.change(function() { redrawChart(); }); dataSelectControls.append(self.showingRestricted) .append('Show restricted data'); } appendHeaderRow(dataSelectControls); } function createAreaSelectControls() { var row = jQuery('<div/>').addClass('areaHiLite'); self.areaFrom = new SticiTextBar({ change: refreshSelectedAreaOverlay, step: self.dataIsBinomial ? 1.0 : 0.001, value: -0.5, label: 'Area from: ' }); self.areaTo = new SticiTextBar({ change: refreshSelectedAreaOverlay, step: self.dataIsBinomial ? 1.0 : 0.001, value: -0.5, label: ' to: ' }); row.append(self.areaFrom, self.areaTo); if (self.options.changeNumBins) { var binsInput = jQuery('<input type="text" />') .val(self.options.bins); binsInput.change(function() { self.nBins = parseInt(binsInput.val(), 10); self.binEnds = histMakeBins(self.nBins, self.data); self.binCounts = histMakeCounts(self.binEnds, self.data); if (self.options.restrict) updateRestrictedData(); redrawChart(); }); row.append('Bins: ').append(binsInput); } else if(!self.dataIsBinomial) { row.append('Bins: ' + self.options.bins); } appendFooterRow(row); } function createRestrictionControls() { var row = jQuery('<div/>'); self.restrictedVariable = self.variableSelect.clone(); self.restrictedVariable.change(function() { updateVariableRestrictionControls(); updateRestrictedData(); }); row.append('Restrict to ').append(self.restrictedVariable); self.restrictLowerEnable = jQuery('<input type="checkbox" />'); row.append(self.restrictLowerEnable).append('>= '); self.restrictLower = jQuery('<input type="text" />'); row.append(self.restrictLower); self.restrictUpperEnable = jQuery('<input type="checkbox" />'); row.append(self.restrictUpperEnable).append('and <= '); self.restrictUpper = jQuery('<input type="text" />'); row.append(self.restrictUpper); jQuery.each([self.restrictLowerEnable, self.restrictLower, self.restrictUpperEnable, self.restrictUpper], function(i, o) { o.change(updateRestrictedData); }); var clearAll = jQuery('<button/>').text('Clear Restrictions'); clearAll.click(function(e) { e.preventDefault(); var dat = jQuery.map(self.dataValues, function(values) { return parseFloat(values[self.restrictedVariable.val()]); }); self.restrictUpperEnable.prop('checked', false); self.restrictUpper.val(dat.max()); self.restrictLowerEnable.prop('checked', false); self.restrictLower.val(dat.min()); updateRestrictedData(); }); row.append(' ').append(clearAll); appendFooterRow(row); } function createExtraInfoControls() { var row = jQuery('<div/>'); // Extra info buttons var lastListDataHeader = null; var listDataButton = createPopBox(); listDataButton.button.text('List Data'); listDataButton.button.click(function(e) { e.preventDefault(); // Thank you Ken var dataFields = self.dataFields, dataValues = self.dataValues, numDataFields = dataFields.length, tempDataRow, tempDataCell, tempConcatData, html; html = '<div class="table-container">'+ '<div class="table-header">' + '<table class="data-fields">' + '<thead>' + '<tr>'; for (var i = 0; i < dataFields.length; i++) { html += '<td>' + dataFields[i] + '</td>'; } html += '</tr>' + '</thead>' + '</table>' + '</div>'; html += '<div class="table-body">' + '<table class="data-values">' + '<tbody>'; for (var j = 0; j < dataValues.length; j++) { tempDataRow = dataValues[j]; if (tempDataRow.length > dataFields.length) { // concat last elements tempConcatData = tempDataRow.slice(dataFields.length - 1) .join(' ') .replace(/[\/]/g, ''); tempDataRow = tempDataRow.slice(0, dataFields.length - 1); tempDataRow.push(tempConcatData); } html += '<tr data-index="' + j + '">'; for (var k = 0; k < tempDataRow.length; k++) { tempDataCell = tempDataRow[k]; html += '<td>' + tempDataCell + '</td>'; } html += '</tr>'; } html += '</tbody>' + '</table>' + '</div>' + '</div>'; listDataButton.content.html(html); listDataButton.content .find('table') .css('width', self.dataFields.length * 120 + 'px'); }); if (self.options.listData) { row.append(listDataButton.parent); } var statsButton = createPopBox(); statsButton.button.text('Univariate Stats'); statsButton.button.click(function(e) { e.preventDefault(); // Thank you Ken var html = '<div class="univariate-stats-container">'; $.each(self.dataFields, function(index) { if (self.dataFields[index].indexOf('//') === 0) return; html += '<div class="univariate-stat-wrapper">' + '<h3>' + self.dataFields[index] + '</h3>'; var data = $.map(self.dataValues, function(values) { return parseFloat(values[index]); }); html += '<ul class="stat-list">' + '<li class="stat-item">Cases: ' + data.length + '</li>' + '<li class="stat-item">Mean: ' + mean(data).toFixed(2) + '</li>' + '<li class="stat-item">SD: ' + sd(data).toFixed(2) + '</li>' + '<li class="stat-item">Min: ' + data.min().toFixed(2) + '</li>' + '<li class="stat-item">LQ: ' + percentile(data, 25).toFixed(2) + '</li>' + '<li class="stat-item">Median: ' + percentile(data, 50).toFixed(2) + '</li>' + '<li class="stat-item">UQ: ' + percentile(data, 75).toFixed(2) + '</li>' + '<li class="stat-item">Max: ' + data.max().toFixed(2) + '</li>' + '</ul>'; html += '</div>'; }); html += '</div>'; statsButton.content.html(html); }); if (self.options.showUnivariateStats) row.append(statsButton.parent); self.showNormalButton = new SticiToggleButton({ trueLabel: 'Hide Normal Curve', falseLabel: 'Show Normal Curve', value: self.options.showNormal, change: function(e, show) { self.histogram.showCurves(show); }}); if (self.options.showNormalButton) row.append(self.showNormalButton); else self.showNormalButton.set(false); self.histogram.showCurves(self.options.showNormal); if (row.children().length > 0) appendFooterRow(row); } function createBinomialBars() { var row = jQuery('<div/>').addClass('binomialBars'); var nSlider = new SticiTextBar({ step: 1, min: 1, max: 500, value: self.n, change: updateNP, label: 'n: ' }); var pSlider = new SticiTextBar({ step: 0.001, min: 0, max: 1, value: self.p, change: updateNP, label: 'p: ' }); function updateNP() { self.n = nSlider.val(); self.p = pSlider.val(); self.reloadData(); } row.append(nSlider, pSlider); appendFooterRow(row); } // Top controls only show if the file/variable can be changed. if (self.dataSource !== null) createSelectDataSourceControls(); createAreaSelectControls(); if (self.options.restrict) createRestrictionControls(); if (self.options.binomialBars) createBinomialBars(); createExtraInfoControls(); var additionalInfoDiv = jQuery('<div/>').addClass('additional_info'); self.additionalInfo = jQuery('<p/>'); additionalInfoDiv.append(self.additionalInfo); if (!self.dataIsBinomial || !self.options.binomialBars) appendFooterRow(additionalInfoDiv); jQuery('.popbox').popbox(); // Set vertical positions based upon available controls. self.areaInfoDiv.css('bottom', bottomOffset + 'px'); top.css('height', topOffset + 'px'); bottom.css('height', bottomOffset + 'px'); self.histogram.css('margin-bottom', (bottomOffset + 15) + 'px'); self.histogram.css('margin-top', (topOffset) + 'px'); } function updateVariableRestrictionControls() { if (!self.options.restrict) return; var state = self.restrictedStates[self.restrictedVariable.val()]; self.restrictUpperEnable.prop('checked', state.upperEnable); self.restrictUpper.val(state.upper); self.restrictLowerEnable.prop('checked', state.lowerEnable); self.restrictLower.val(state.lower); } function updateRestrictedData() { var state = self.restrictedStates[self.restrictedVariable.val()]; state.upperEnable = self.restrictUpperEnable.prop('checked'); state.upper = self.restrictUpper.val(); state.lowerEnable = self.restrictLowerEnable.prop('checked'); state.lower = self.restrictLower.val(); self.restrictedStates[self.restrictedVariable.val()] = state; var info = 'n=' + self.data.length; info += ' '; info += 'Mean=' + self.mu.toFixed(3); info += ' '; info += 'SD=' + self.sd.toFixed(3); self.restrictedData = jQuery.map(self.dataValues, function(values) { return [[ parseFloat(values[self.variableSelect.val()]), parseFloat(values[self.restrictedVariable.val()])]]; }); if (self.restrictUpperEnable.is(':checked')) { self.restrictedData = jQuery.grep(self.restrictedData, function(o) { if (o[1] <= self.restrictUpper.val()) return true; else return false; }); } if (self.restrictLowerEnable.is(':checked')) { self.restrictedData = jQuery.grep(self.restrictedData, function(o) { if (o[1] >= self.restrictLower.val()) return true; else return false; }); } self.restrictedData = jQuery.map(self.restrictedData, function(o) { return o[0]; }); if (!self.restrictUpperEnable.is(':checked') && !self.restrictLowerEnable.is(':checked')) { self.restrictedCounts = null; } else { self.restrictedCounts = histMakeCounts(self.binEnds, self.restrictedData); if (isNaN(self.restrictedCounts[0])) self.restrictedCounts = null; self.restrictedMu = mean(self.restrictedData); self.restrictedSd = sd(self.restrictedData); info += ' '; info += 'Subset: n=' + self.restrictedData.length; info += ' '; info += 'Mean=' + self.restrictedMu.toFixed(3); info += ' '; info += 'SD=' + self.restrictedSd.toFixed(3); } self.additionalInfo.html(info); redrawChart(); } // Helper function that is called whenever any of the area overlay // sliders or inputs are changed. function refreshSelectedAreaOverlay() { var lower = self.areaFrom.val(); var upper = self.areaTo.val(); self.histogram.hilite(lower, upper); var p = histHiLitArea(lower, upper, self.binEnds, self.binCounts); p *= 100; var text = 'Selected area: ' + p.fix(2) + '%'; if (self.showNormalButton.toggled()) { var m = self.mu; var s = self.sd; p = Math.max( 0, (normCdf((upper - m) / s) - normCdf((lower - m) / s)) * 100 ); text += ' '; text += 'Normal approx: ' + p.fix(2) + '%'; } if (self.restrictedCounts !== null) { text += ' '; p = histHiLitArea(lower, upper, self.binEnds, self.restrictedCounts); p *= 100; text += 'Subset data: ' + p.fix(2) + '%'; if (self.showNormalButton.toggled()) { rm = self.restrictedMu; rs = self.restrictedSd; p = Math.max( 0, (normCdf((upper - rm) / rs) - normCdf((lower - rm) / rs)) * 100 ); text += ' '; text += 'Normal approx: ' + p.fix(2) + '%'; } } self.areaInfoDiv.html(text); } doWhileVisible(self.container, function() { initControls(); self.reloadData(); }); } // Javascript rewrite of // http://statistics.berkeley.edu/~stark/Java/Html/lln.htm // // Authors: James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the histogram (and // controls) in. // params: A javascript object with various parameters to customize the chart. // // Show percentage mode by default. // - percent: false function Stici_Lln(container_id, params) { var self = this; if (!params instanceof Object) { console.error('Stici_Lln params should be an object'); return; } // Configuration option defaults. this.options = { percent: false }; // For debugging: Warn of user params that are unknown. jQuery.each(params, function(key) { if (typeof(self.options[key]) == 'undefined') console.warn('Stici_Lln: Unknown key \'' + key + '\''); }); // Override options with anything specified by the user. jQuery.extend(this.options, params); // jQuery object containing the entire chart. this.container = jQuery('#' + container_id); // The root object that holds everything. this.root = jQuery('<div/>').addClass('stici stici_lln'); // The title to show which mode we're in. this.title = jQuery('<div/>').addClass('title'); // The divs holding the SVGs. this.chart_box = jQuery('<div/>').addClass('chart_box'); this.chart = jQuery('<div/>').addClass('simulation'); this.x_axis = jQuery('<div/>').addClass('axis x_axis'); this.y_axis = jQuery('<div/>').addClass('axis y_axis'); // The div that holds the controls at the bottom. this.controls = jQuery('<div/>').addClass('controls'); // The controls themselves. this.probability = new SticiTextBar({ label: 'Chance of success (%)', value: 50, min: 0, max: 100, change: function() { self.results = []; self.simulations = []; redraw(); } }); this.trials = new SticiTextBar({ label: '# Trials', value: 800, min: 1, max: 20000, change: redraw }); this.percent = new SticiToggleButton({ trueLabel: 'Number', falseLabel: 'Percent', value: this.options.percent, change: function() { self.results = []; redraw(); } }); // Compose everthing. this.container.append(this.root); this.root.append(this.title, this.chart_box, this.controls); this.chart_box.append(this.chart, this.x_axis, this.y_axis); this.controls.append(this.probability, this.trials, this.percent); this.chart_box.height( this.container.height() - this.controls.height() - this.title.height()); this.chart.height(this.chart_box.height() - this.x_axis.height()); this.chart.width(this.chart_box.width() - this.y_axis.width()); // The actual random numbers generated. this.simulations = []; // The displayed cumulative results. this.results = []; // The drawing function. function redraw() { // Start from fresh slate. self.chart.children().remove(); self.x_axis.children().remove(); self.y_axis.children().remove(); // Fetch the probability. var p = self.probability.val() / 100; var n_trials = self.trials.val(); while (self.simulations.length < n_trials) self.simulations.push(rand.next()); // If the trials the user has specified doesn't match what we have, either // perform some more or cut some off. var n; var prev; if (self.results.length > n_trials) self.results = self.results.slice(0, n_trials); if (self.percent.val()) { // Percent. if (self.results.length === 0) { self.results.push(-p * 100); if (self.simulations[0] <= p) self.results[0] += 100; } while (self.results.length < n_trials) { n = self.results.length; prev = self.results[n - 1]; self.results.push((n * prev - 100 * p) / (n + 1)); if (self.simulations[n] <= p) self.results[n] += (100 / (n + 1)); } } else { // Number. if (self.results.length === 0) { self.results.push(-p); if (self.simulations[0] <= p) self.results[0] += 1; } while (self.results.length < n_trials) { n = self.results.length; prev = self.results[n - 1]; self.results.push(prev - p); if (self.simulations[n] <= p) self.results[n] += 1; } } // Draw the thing. var width = self.chart.width(); var height = self.chart.height(); var y_min = self.results.min(); var y_max = self.results.max(); var yScale = (y_max - y_min) / height; var line = d3.svg.line() .x(function(d) {return d;}) .y(function(d) { // Convert the pixel offset to a usable x-coordinate that means // something. var x = Math.round(d * n_trials / width); var y = height - ((self.results[x] - y_min) / yScale); if (isNaN(y)) return 0; return y; }); d3.select(self.chart.get(0)) .append('svg') .append('path') .data([range(0, width)]) .attr('d', line); // Draw the x axis. var xscale = d3.scale.linear() .domain([0, n_trials]) .range([0, width]); d3.select(self.x_axis.get(0)) .append('svg') .append('g') .call(d3.svg.axis().scale(xscale).orient('bottom')); self.x_axis.css( 'top', ((y_max / (y_max - y_min)) * height + self.title.height()) + 'px'); // Draw the y axis. var yscale = d3.scale.linear() .domain([y_max, y_min]) .range([0, height]); d3.select(self.y_axis.get(0)) .append('svg') .append('g') .attr("transform", "translate(" + self.y_axis.width() + ", " + self.title.height() + ")") .call(d3.svg.axis().scale(yscale).orient('left')); // Set the title if (self.percent.val()) self.title.text('Law of Large Numbers: %Successes - %Expected'); else self.title.text('Law of Large Numbers: #Successes - #Expected'); } // Initial render. doWhileVisible(self.container, function() { redraw(); }); } // Javascript rewrite of // http://statistics.berkeley.edu/~stark/Java/Html/NormHiLite.htm // // Authors: James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the curve (and // controls) in. // params: A javascript object with various parameters to customize the chart. // // Default distributions. Options are 'normal', 'chi-square', and // // 'Student-t'. // - distribution: 'normal' // // // Distribution mean. // - mean: 0 // // // Distribution standard deviation. // - SD: 1 // // // Chi-square degrees of freedom // - df: 2 // // // Default hi-lit area. // - hiLiteLo: 0 // - hiLiteHi: 0 function Stici_NormHiLite(container_id, params) { var self = this; if (!params instanceof Object) { console.error('Stici_NormHiLite params should be an object'); return; } // Configuration option defaults. this.options = { distribution: 'normal', mean: 0, SD: 1, df: 2, hiLiteLo: 0, hiLiteHi: 0 }; // For debugging: Warn of user params that are unknown. jQuery.each(params, function(key) { if (typeof(self.options[key]) == 'undefined') console.warn('Stici_NormHiLite: Unknown key \'' + key + '\''); }); // Override options with anything specified by the user. jQuery.extend(this.options, params); // jQuery object containing the entire chart. this.container = jQuery('#' + container_id); this.df = self.options.df; // Object that contains the parameters for the selected distribution. this.distribution = null; if (this.options.distribution == 'normal') { this.distribution = { lo: function() { return -5; }, hi: function() { return 5; }, y: function(x) { return normPdf(self.options.mean, self.options.SD, x); }, area: function(lo, hi) { return normCdf(hi) - normCdf(lo); } }; } else if (this.options.distribution == 'Student-t') { this.distribution = { lo: function() { return -6; }, hi: function() { return 6; }, y: function(x) { return tPdf(self.df, x); }, area: function(lo, hi) { return tCdf(self.df, hi) - tCdf(self.df, lo); } }; } else if (this.options.distribution == 'chi-square') { this.distribution = { lo: function() { return 0; }, hi: function() { return 6 * self.df; }, y: function(x) { return chi2Pdf(self.df, x); }, area: function(lo, hi) { return chi2Cdf(self.df, hi) - chi2Cdf(self.df, lo); } }; } else { console.error('Stici_NormHiLite: Unknown distribution specified.'); this.container.text('Unknown distributions specified.'); return; } // Various handles to important jQuery objects. this.chartDiv = null; this.overlayDiv = null; // Used for the area selection feature. this.normalOverlayDiv = null; this.areaFrom = null; this.areaTo = null; this.areaInfoDiv = null; this.reloadChart = function() { redrawChart(); if (self.options.hiLiteLo === null) self.areaFrom.val(self.distribution.lo()); else self.areaFrom.val(self.options.hiLiteLo); if (self.options.hiLiteHi === null) self.areaTo.val(self.distribution.lo()); else self.areaTo.val(self.options.hiLiteHi); }; function redrawChart() { self.areaFrom.min(self.distribution.lo()); self.areaFrom.max(self.distribution.hi()); self.areaTo.min(self.distribution.lo()); self.areaTo.max(self.distribution.hi()); var i; self.chartDiv.children().remove(); var normalChartDiv = jQuery('<div/>').addClass('chart_box'); self.overlayDiv = normalChartDiv.clone().addClass('overlay'); self.normalOverlayDiv = jQuery('<div/>').addClass('chart_box'); self.chartDiv.append(normalChartDiv); self.chartDiv.append(self.overlayDiv); self.chartDiv.append(self.normalOverlayDiv); // Background calculations. var width = self.overlayDiv.width(); var height = self.overlayDiv.height(); var graphWidth = self.distribution.hi() - self.distribution.lo(); function remappedY(d) { var remappedD = (d / width) * graphWidth + self.distribution.lo(); return self.distribution.y(remappedD); } var yScale = 0; for(i = 0; i < width; i++) yScale = Math.max(yScale, remappedY(i)); yScale /= (height - 1); var curve = d3.svg.line() .x(function(d) {return d;}) .y(function(d) { return height - (remappedY(d) / yScale); }); d3.select(normalChartDiv.get(0)).append('svg') .attr('height', '100%') .append('path') .data([d3.range(0, width)]) .attr('d', curve); var overlayCurve = d3.svg.line() .x(function(d, i) { return d; }) .y(function(d, i) { if (i === 0 || i == width + 1) return height; return height - (remappedY(d) / yScale); }); var overlayDat = [0].concat(d3.range(0, width), [width]); d3.select(self.overlayDiv.get(0)).append('svg') .attr('height', '100%') .append('path') .data([overlayDat]) .attr('d', overlayCurve); self.overlayDiv.css('clip', 'rect(0px, 0px, ' + height + 'px, 0px)'); // Draw the axis var axisSvg = d3.select(normalChartDiv.get(0)) .append('svg') .attr('class', 'axis'); var axisScale = d3.scale.linear() .domain([self.distribution.lo(), self.distribution.hi()]) .range([0, width]); var axis = d3.svg.axis().scale(axisScale).orient('bottom'); axisSvg.append('g').call(axis); refreshSelectedAreaOverlay(); } // Initializes the chart controls. Adds the sliders, input fields, etc. function initControls() { // Create html for basic structure: // top_controls -> stici_chart -> area_info -> botom_controls. var o = jQuery('<div/>').addClass('stici').addClass('stici_normhilite'); self.container.append(o); self.chartDiv = jQuery('<div/>') .addClass('stici_chart') .addClass('chart_box'); o.append(self.chartDiv); self.areaInfoDiv = jQuery('<div/>') .addClass('area_info'); o.append(self.areaInfoDiv); var bottom = jQuery('<div/>').addClass('bottom_controls'); o.append(bottom); // Area from input/slider. self.areaFrom = new SticiTextBar({ label: 'Lower endpoint: ', step: 0.001, change: refreshSelectedAreaOverlay }); // Area to input/slider. self.areaTo = new SticiTextBar({ label: ' Upper endpoint: ', step: 0.001, change: refreshSelectedAreaOverlay }); bottom.append(self.areaFrom, self.areaTo); if (self.options.distribution != 'normal') { var df = new SticiTextBar({ label: ' Degrees of Freedom: ', step: 1, min: 1, max: 350, value: self.df, change: function(e, value) { self.df = value; redrawChart(); refreshSelectedAreaOverlay(); } }); bottom.append(df); } // Set vertical positions based upon available controls. self.areaInfoDiv.css('bottom', (bottom.height() + 10) + 'px'); self.chartDiv.css('margin-bottom', (bottom.height() + self.areaInfoDiv.height() + 15) + 'px'); } // Helper function that is called whenever any of the area overlay // sliders or inputs are changed. function refreshSelectedAreaOverlay() { var lower = parseFloat(self.areaFrom.val()); var upper = parseFloat(self.areaTo.val()); var scale = self.chartDiv.width() / (self.distribution.hi() - self.distribution.lo()); var left = (lower - self.distribution.lo()) * scale; var right = (upper - self.distribution.lo()) * scale; self.overlayDiv.css('clip', 'rect(0px,' + right + 'px,' + self.chartDiv.height() + 'px,' + left + 'px)'); var p = self.distribution.area(lower, upper); p *= 100; var text = 'Selected area: ' + p.fix(2) + '%'; self.areaInfoDiv.html(text); } doWhileVisible(self.container, function() { initControls(); self.reloadChart(); refreshSelectedAreaOverlay(); }); } // Javascript rewrite of // http://statistics.berkeley.edu/~stark/Java/Html/SampleDist.htm // // Authors: James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the histogram (and // controls) in. // params: A javascript object with various parameters to customize the chart. // // Whether or not to allow the user to edit the number of bins. // - binControls: true, // // // Lorem // - bins: 100, // // // What to initialize the population box contents to. // - boxContents: "0,1,2,3,4", // // // Whether or not the sure can change the contents of the population box. // - boxEditable: true, // // // Whether or not to show the button that toggles the population histogram. // - boxHistControl: false, // // // Whether or not to show the curve selection box and curve area. // - curveControls: true, // // // Which curves to allow the user to choose. If not 'all', the curves whose // // names (either short or long form) are present in this string are visible // // for the user to select. // - curves: "all", // // // Default hilighting bounds. // - hiLiteHi: 0.0, // - hiLiteLo: 0.0, // // // Default value of the "with replacement" checkbox. // - replace: true, // // // Whether or not to show the "with replacement" checkbox. // - replaceControl: false, // // // Defaults for sampling parameters. // - sampleSize: 5, // - samplesToTake: 1, // // // Default value for the show/hide population histogram button. // - showBoxHist: true, // // if (varChoice.selected() == "Sample Mean" || // varChoice.selected() == "Sample Sum") { // curveChoice.selected("Normal Curve"); // } else if (varChoice.selected() == "Sample S-Squared" || // varChoice.selected() == "Sample Chi-Squared") { // curveChoice.selected("Chi-Squared Curve"); // } else if (varChoice == "Sample t") { // curveChoice.selected("Student t curve"); // } // // Whether or not to initially display the curve. If set to true, the curve // // choice is set to: // // - Normal for 'Sample Mean' and 'Sample Sum' variables // // - Chi-Squared for 'Sample S-Squared' and 'Sample Chi-Squared' variables. // // - Student t for 'Sample t' variable. // - showCurve: false, // // // Which sources to allow the user to choose. If not 'all', the sources // // whose names (either short or long form) are present in this string are // // visible for the user to select. // - sources: "all", // // // The source to initialize the variables combobox to. // - startWith: "sum", // // // Whether or not to display the sample mean and SD. // - statLabels: true, // // // Whether or not the selected variable can be changed. // - toggleVar: true, // // // Which curves to allow the user to choose. If not 'all', the curves whose // // names (either short or long form) are present in this string are visible // // for the user to select. // - variables: "all" function Stici_SampleDist(container_id, params) { var self = this; // jQuery object containing the entire chart. var container = jQuery('#' + container_id); // These are constants. var maxBins = 100; var nDigs = 4; var maxSamples = 10000; var maxMaxSampleSize = 500; // max size of each sample var curveLabel = { "No Curve": "none", "Normal Curve": "normal", "Student t Curve": "t", "Chi-Squared Curve": "chi-squared" }; var rVar = { // random variable options and their abbreviations "Sample Sum": "sum", "Sample Mean": "mean", "Sample t": "t", "Sample S-Squared": "s-squared", "Sample Chi-Squared": "chi-squared" }; var rSource = { "Normal": "normal", "Uniform": "uniform", "Box": "box" }; // User-configurable parameters. These are directly lifted from // SampleDist.java. I don't know what the commented ones are yet, nor what // their default values should be. -jeady var options = { binControls: true, bins: maxBins, boxContents: "0,1,2,3,4", boxEditable: true, boxHistControl: false, curveControls: true, curves: "all", hiLiteHi: 0.0, hiLiteLo: 0.0, replace: true, replaceControl: false, sampleSize: 5, samplesToTake: 1, showBoxHist: true, showCurve: false, sources: "all", startWith: "sum", statLabels: true, toggleVar: true, variables: "all" }; jQuery.extend(options, params); // UI Elements. // Most of these are actually initialized later in init(), but they're // here temporarily to conform with the existing java code. var takeSampleButton = null; // SticiToggleButton, myButton[0] in the Java. var populationButton = null; // SticiToggleButton, myButton[1] in the Java. var sampleSizeBar = null; // <input/> //size of each sample var samplesToTakeBar = null; // <input/> //number of samples to take var binBar = null; // <input/> //number of bins in the histogram var lo = null; // SticiTextBar var hi = null; // SticiTextBar var box = null; // textarea // holds the population. var popMeanLabel = null; // to display the population mean var popSdLabel = null; // to display the population SD var statSampleMeanLabel = null; // to display mean of sample means var statSampleSDLabel = null; // sample SD of sample means var statExpLabel = null; // theor. Expected value of statistic var statSELabel = null; // to display theor. SD of statistic or d.f. of chi-square var samplesSoFarLabel = null; // number of samples of current size taken var boxLabel = null; // label box as population or category probabilities var areaLabel = null; // span var curveAreaLabel = null; // span var hist = new SticiHistogram(); var replaceCheck = null; var varChoice = null; // SticiComboBox // options for which random variable to sample var curveChoice = null; // SticiComboBox // options for which approximating curve to plot var sourceChoice = null; // SticiComboBox // options for data source (box, normal, uniform) var stats = null; // Contains all of the statistics labels. // State variables. var pop = []; // elements of the population var sample = []; // elements of the current sample var xMin = null; var xMax = null; var samplesSoFar = 0; var sampleMean = []; // the history of sample means var sampleSSq = []; // history of sample s^2 var sampleT = []; // history of sample t var sampleSize = options.sampleSize; // size of current sample var samplesToTake = options.samplesToTake; // number of samples to take of that size var binEnd = []; // bin endpoints var countPop = []; // areas of the bins for the pop. histogram var countSample = []; // areas of bins for the hist. of sample means var hiLiteLo = options.hiLiteLo; var hiLiteHi = options.hiLiteHi; var showBoxHist = options.showBoxHist; var nBins = options.bins; var minSampleSize; // minimum sample size (2 for vars that use ssd) var maxSampleSize; // maximum sample size (population size if sampling w/o replacement) var currVar = null; var lastVar = null; var EX = 0; // expected value of the variable plotted var SE = 0; // standard error of the variable plotted var popMin = 0; // smallest value in pop. var popMax = 0; // largest value in pop. var popMean = 0; // the population mean var popSd = 0; // the population SD function init() { var o = jQuery('<div/>').addClass('stici stici_sampledist'); container.append(o); // General pieces var top = jQuery('<div/>').addClass('top_controls'); var middle = jQuery('<div/>').addClass('middle'); var bottom = jQuery('<div/>').addClass('bottom_controls'); o.append(top, middle, bottom); // Compose the top piece. top.append(createSelectDataSourceControls()); // Compose the middle pieces. middle.append(createStatsBox(), hist, createPopulationBox()); // Compose the bottom piece. bottom.append(createSampleRow(), createAreaSelectRow(), createInfoRow()); // Make sure everything is sized correctly. middle.height(container.height() - top.height() - bottom.height()); hist.width(middle.width() - stats.width() - box.width() - 20); // Set all of the handlers. jQuery.each([populationButton, sampleSizeBar, samplesToTakeBar, binBar, lo, hi, box, varChoice, curveChoice, sourceChoice, replaceCheck], function(_, e) {e.change(handleEvent);}); takeSampleButton.click(handleEvent); // Below this point lie methods used to build the individual pieces. // Top. function createSelectDataSourceControls() { var dataSelectControls = jQuery('<div/>'); var n; if (options.variables != "all") { var oldRVar = rVar; rVar = {}; jQuery.each(oldRVar, function(k, v) { if (options.variables.indexOf(k) >= 0 || options.variables.indexOf(v) >= 0) { rVar[k] = v; n += 1; } }); if (n <= 1) options.toggleVar = false; } varChoice = new SticiComboBox({ label: "Distribution of: ", options: rVar, value: options.startWith }); currVar = varChoice.selected(); var showSources = true; if (options.sources != "all") { n = 0; var oldRSource = rSource; rSource = {}; jQuery.each(oldRSource, function(k, v) { if (options.sources.indexOf(k) >= 0 || options.sources.indexOf(v) >= 0) { rSource[k] = v; n += 1; } }); if (n <= 1) showSources = false; } sourceChoice = new SticiComboBox({ label: "Sample from: ", options: rSource, selected: "Box" }); if (options.toggleVar) dataSelectControls.append(varChoice); if (showSources) dataSelectControls.append(sourceChoice); replaceCheck = jQuery('<input type="checkbox"/>'); if (options.replaceControl) dataSelectControls.append(replaceCheck, ' with replacement'); takeSampleButton = jQuery('<button id="takeSample"/>').text('Take Sample'); dataSelectControls.append(takeSampleButton); return dataSelectControls; } // Middle. function createPopulationBox() { var container = jQuery('<div/>').addClass('population'); boxLabel = jQuery('<div/>'); box = jQuery('<textarea/>'); if (!options.boxEditable) box.attr("readonly", "readonly"); container.append(boxLabel, box); return container; } function createStatsBox() { stats = jQuery('<div/>').addClass('statsText'); popMeanLabel = jQuery('<p/>'); popSdLabel = jQuery('<p/>'); statExpLabel = jQuery('<p/>'); statSELabel = jQuery('<p/>'); stats.append(popMeanLabel, popSdLabel, statExpLabel, statSELabel); statSampleMeanLabel = jQuery('<p/>'); statSampleSDLabel = jQuery('<p/>'); if (options.statLabels) { stats.append(statSampleMeanLabel, statSampleSDLabel); } samplesSoFarLabel = jQuery('<p/>'); stats.append(samplesSoFarLabel); return stats; } // Bottom. function createSampleRow() { var row = jQuery('<div/>'); areaLabel = jQuery('<span/>'); curveAreaLabel = jQuery('<span/>'); if (options.curves != "all") { var oldLabels = curveLabel; curveLabel = {}; jQuery.each(oldLabels, function(k, v) { if (options.curves.indexOf(k) >= 0 || options.curves.indexOf(v) >= 0) curveLabel[k] = v; }); } curveChoice = new SticiComboBox({ label: '', options: curveLabel }); populationButton = SticiToggleButton({ trueLabel: 'No Population Histogram', falseLabel: 'Population Histogram', value: showBoxHist }); if (options.showCurve) { if (varChoice.selected() == "Sample Mean" || varChoice.selected() == "Sample Sum") { curveChoice.selected("Normal Curve"); } else if (varChoice.selected() == "Sample S-Squared" || varChoice.selected() == "Sample Chi-Squared") { curveChoice.selected("Chi-Squared Curve"); } else if (varChoice == "Sample t") { curveChoice.selected("Student t curve"); } } row.append(areaLabel); if (options.curveControls) row.append(curveAreaLabel, curveChoice); if (options.boxHistControl) row.append(populationButton); return row; } function createAreaSelectRow() { var row = jQuery('<div/>').addClass('areaHiLite'); lo = new SticiTextBar({ step: 0.001, value: hiLiteLo, min: -10000, max: 10000, label: 'Area from: ' }); hi = new SticiTextBar({ step: 0.001, value: hiLiteHi, min: -10000, max: 10000, label: ' to: ' }); row.append(lo, hi); return row; } function createInfoRow() { var row = jQuery('<div/>'); sampleSizeBar = jQuery('<input type="text"/>').val(sampleSize); samplesToTakeBar = jQuery('<input type="text"/>').val(samplesToTake); row.append("Sample Size: ", sampleSizeBar, " Take ", samplesToTakeBar, " samples. "); binBar = jQuery('<input type="text" id="bins" />').val(options.bins); if (options.binControls) { row.append(" Bins: ", binBar); } return row; } // The UI has been set up. Now initialize the data. if (options.sources === null || options.sources.toLowerCase().indexOf("box") >= 0 || options.sources.toLowerCase().indexOf("all") >= 0) { if (varChoice.selected() == "Sample Chi-Squared") boxLabel.text("Category Probabilities"); else boxLabel.text("Population"); } var bc = ""; if (options.boxContents !== null) { bc = options.boxContents; } else if (sourceChoice.selected() == "Normal") { bc = "Normal"; } else if (sourceChoice.selected() == "Uniform") { bc = "Uniform"; } else { bc = "0 1 2 3 4"; } replaceCheck.attr('checked', options.replace); setBox(bc, true); var vmx = vMinMax(pop); xMin = vmx[0]; xMax = vmx[1]; initPop(); setCurve(); // set the approximating curve setBins(); // make the histogram counts setBars(options.hiLiteLo, options.hiLiteHi); adjustSampleSize(); showPlot(); // refresh the histogram } // compute population statistics function initPop() { if (sourceChoice.selected() == "Box") { popMean = 0; popSd = 0; if (pop.length === 0) { console.error("Error in SampleDist.initPop(): Population is empty!\n"); for (var i= 0; i < nBins; i++) { countPop[i] = 0; } // TODO(jmeady): Do we need to clear anything out here? return; } popMean = mean(pop); popSd = sd(pop); // FIX ME! need to handle probabilities here. } else if (sourceChoice.selected() == "Normal") { popMean = 0; popSd = 1; replaceCheck.attr('checked', true); } else if (sourceChoice.selected() == "Uniform") { popMean = 0.5; popSd = Math.sqrt(1.0/12.0); replaceCheck.attr('checked', true); } popMin = pop.min(); popMax = pop.max(); setLims(); // set plot limits // make the histogram of the population setBins(); // set the class intervals; make the counts setBars(lo.val(),hi.val()); // set the hilight scrollbar scales // reset the labels if (varChoice.selected() == "Sample Chi-Squared") { popMeanLabel.text("Categories: " + pop.length); popSdLabel.text("E(Chi-Squared): " + (pop.length-1)); replaceCheck.attr('checked', true); } else { popMeanLabel.text("Ave(Box): " + popMean.fix(3)); popSdLabel.text("SD(Box): " + popSd.fix(3)); statSampleMeanLabel.text("Mean(values): undefined"); statSampleSDLabel.text("SD(values): undefined"); } setCurve(); setCurveLabel(); setAreas(); } function handleEvent(e) { if (binBar.is(e.target)) { // update # bins and redraw histogram nBins = parseInt(binBar.val(), 10); setBins(); // reset the class intervals, make the counts showPlot(); // refresh the histogram } else if (sampleSizeBar.is(e.target)) { // clear history, reset sample size, redisplay histogram sampleSize = parseInt(sampleSizeBar.val(), 10); setBars(lo.val(), hi.val()); samplesSoFar = 0; setSamLabel(); setLims(); setCurveLabel(); setBins(); setCurve(); setAreas(); showPlot(); } else if (samplesToTakeBar.is(e.target)) { samplesToTake = parseInt(samplesToTakeBar.val(), 10); } else if (lo.is(e.target) || hi.is(e.target)) { hiLiteLo = lo.val(); hiLiteHi = hi.val(); if (hiLiteLo >= hiLiteHi) hiLiteLo = hiLiteHi; setAreas(); showPlot(); } else if(box.is(e.target)) { setBox(box.val()); } else if (takeSampleButton.is(e.target)) { e.preventDefault(); var lim = maxSamples - samplesSoFar; // number remaining samples drawSample(Math.min(samplesToTake, lim)); setSamLabel(); } else if (sourceChoice.is(e.target)) { if (sourceChoice.selected() == "Box" ) { setBox(box.val()); } else { setBox(sourceChoice.selected()); replaceCheck.attr('checked', true); } } else if (varChoice.is(e.target)) { lastVar = currVar; currVar = varChoice.selected(); newVariable(varChoice.selected()); showPlot(); } else if (curveChoice.is(e.target)) { setCurve(); setAreas(); showPlot(); } else if (replaceCheck.is(e.target)) { if (replaceOK(replaceCheck.is(':checked'))) { samplesSoFar = 0; setLims(); setBins(); setSamLabel(); setBins(); setBars(hiLiteLo, hiLiteHi); setCurveLabel(); setCurve(); setAreas(); } else { replaceCheck.attr('checked', true); } } else if (populationButton.is(e.target)) { showBoxHist = populationButton.val(); setLims(); setBins(); setBars(hiLiteLo, hiLiteHi); setCurve(); showPlot(); } else { console.log("Handling unknown event for " + e.target); } } function replaceOK(rep) { var v = true; if (!rep) { if (sourceChoice.selected() != "Box") { v = false; } else { var s = varChoice.selected(); if (!(s == "Sample Sum" || s == "Sample Mean" || s == "Sample S-Squared" || s == "Sample t")) v = false; } } return v; } // test what is to be plotted; adjust variables accordingly function showPlot() { var curves = hist.curves(); if (samplesSoFar > 0) { if (showBoxHist) hist.set(binEnd, [countPop, countSample], curves); else hist.set(binEnd, [[], countSample], curves); } else { if (showBoxHist) hist.set(binEnd, [countPop, []], curves); else hist.set(binEnd, [[], countSample], curves); } hist.hilite(hiLiteLo, hiLiteHi); } // set things up when the variable is changed function newVariable() { // set things up when the variable is changed if ((varChoice.selected() == "Sample S-Squared" || varChoice.selected() == "Sample t") && (sampleSize == 1) ) { samplesSoFar = 0; } adjustSampleSize(); if (lastVar == "Sample Chi-Squared" || varChoice.selected() == "Sample Chi-Squared") { samplesSoFar = 0; } if (varChoice.selected() == "Sample Chi-Squared") { boxLabel.text("Category Probabilities"); setBox(box.val(),true); } else { boxLabel.text("Population"); } if (!(varChoice.selected() == "Sample Mean" || varChoice.selected() == "Sample Sum" || varChoice.selected() == "Sample S-Squared") || varChoice.selected() == "Sample t") { replaceCheck.attr('checked', true); } setSamLabel(); setLims(); setBins(); setBars(hiLiteLo, hiLiteHi); setCurveLabel(); setCurve(); setAreas(); } // ends newVariable // function population function setBox(newBox, updateBox) { // parse new population if (updateBox === undefined) updateBox = false; newBox = newBox.replace(/^[,\n\t\r ]+|[,\n\t\r ]+$/g, ''); if (newBox.toLowerCase() == "normal") { replaceCheck.attr('checked', true); pop = [-4, 4]; box.val("Normal"); sourceChoice.selected("Normal"); if (varChoice.selected() == "Sample Chi-Squared") { console.log("Warning in SampleDist.setBox(): normal incompatible " + "with Sample Chi-Squared"); varChoice.selected("Sample Mean"); } } else if (newBox.toLowerCase() == "uniform") { replaceCheck.attr('checked', true); pop = [0, 1]; box.val("Uniform"); sourceChoice.selected("Uniform"); if (varChoice.selected() == "Sample Chi-Squared") { console.log("Warning in SampleDist.setBox(): uniform incompatible " + "with Sample Chi-Squared"); varChoice.select("Sample Mean"); } } else { pop = newBox.split(/[,\n\t\r ]+/); pop = jQuery.map(pop, function(v) {return parseFloat(v);}); if (varChoice.selected() == "Sample Chi-Squared") { pop = jQuery.grep(pop, function(v) { return (v !== 0 && !isNaN(v)); }); pop = scalVMult(1.0/vSum(pop), pop); updateBox = true; } if (updateBox) { box.val(jQuery.map(pop, function(v) {return v.fix(nDigs);}).join("\r")); } sourceChoice.selected("Box"); } initPop(); samplesSoFar = 0; setSamLabel(); } // ends setBox(String, boolean) function setSamLabel() { samplesSoFarLabel.text("Samples: " + samplesSoFar); if (varChoice.selected() == "Sample Mean") { countSample = listToHist(sampleMean, binEnd, nBins, samplesSoFar); statSampleMeanLabel.text( "Mean(values): " + mean(sampleMean, samplesSoFar).fix(nDigs)); statSampleSDLabel.text( "SD(values): " + sd(sampleMean, samplesSoFar).fix(nDigs)); } else if (varChoice.selected() == "Sample Sum") { countSample = listToHist(scalVMult(sampleSize, sampleMean), binEnd, nBins, samplesSoFar); statSampleMeanLabel.text( "Mean(values): " + (sampleSize * mean(sampleMean, samplesSoFar)).fix(nDigs)); statSampleSDLabel.text( "SD(values): " + (sampleSize * sd(sampleMean, samplesSoFar)).fix(nDigs)); } else if (varChoice.selected() == "Sample t") { countSample = listToHist(sampleT, binEnd, nBins, samplesSoFar); statSampleMeanLabel.text( "Mean(values): " + mean(sampleT, samplesSoFar).fix(nDigs)); statSampleSDLabel.text( "SD(values): " + sd(sampleT, samplesSoFar).fix(nDigs)); } else if (varChoice.selected() == "Sample S-Squared" || varChoice.selected() == "Sample Chi-Squared") { countSample = listToHist(sampleSSq, binEnd, nBins, samplesSoFar); statSampleMeanLabel.text( "Mean(values): " + mean(sampleSSq, samplesSoFar).fix(nDigs)); statSampleSDLabel.text( "SD(values): " + sd(sampleSSq, samplesSoFar).fix(nDigs)); } setAreas(); showPlot(); } function setAreas() { areaLabel.text(" Selected area: " + hiLitArea().pct()); if (curveChoice.selected() == "Normal Curve") curveAreaLabel.text(" Normal approx: " + normHiLitArea().pct()); else if (curveChoice.selected() == "Student t Curve") curveAreaLabel.text(" Student t approx: " + tHiLitArea().pct()); else if (curveChoice.selected() == "Chi-Squared Curve") curveAreaLabel.text(" Chi-squared approx: " + chiHiLitArea().pct()); else curveAreaLabel.text(""); } // ends setAreas() function setCurveLabel() { var fpc = 1.0; if (!replaceCheck.is(':checked')) { fpc = Math.sqrt( (pop.length - sampleSize + 0.0)/(pop.length-1.0)); } if (varChoice.selected() == "Sample Sum") { SE = fpc*popSd*Math.sqrt(sampleSize + 0.0); EX = sampleSize*popMean; statExpLabel.text("E(sum): " + EX.fix(3) + " "); statSELabel.text("SE(sum): " + SE.fix(3) + " "); } else if (varChoice.selected() == "Sample Mean") { SE = fpc*popSd/Math.sqrt(sampleSize + 0.0); EX = popMean; statExpLabel.text("E(mean): " + EX.fix(4) + " "); statSELabel.text("SE(mean): " + SE.fix(4) + " "); } else if (varChoice.selected() == "Sample t") { if ( sampleSize > 2 ) { SE = Math.sqrt((sampleSize + 0.0)/(sampleSize - 2.0)); } else { SE = Double.NaN; } EX = popMean; statExpLabel.text("E(t): " + EX.fix(4) + " "); statSELabel.text("SE(t): " + SE.fix(4) + " "); } else if (varChoice.selected() == "Sample S-Squared") { if (replaceCheck.is(':checked')) { EX = popSd*popSd; } else { EX = popSd*popSd*pop.length/(pop.length-1.0); } SE = Math.sqrt(2.0/(sampleSize-1.0))*popSd*popSd; statExpLabel.text("E(S-squared): " + EX.fix(3) + " "); statSELabel.text("df: " + (sampleSize-1) + " "); } else if (varChoice.selected() == "Sample Chi-Squared") { EX = pop.length - 1; SE = Math.sqrt(2.0*(pop.length-1.0)); popMeanLabel.text("Categories: " + pop.length); popSdLabel.text("E(Chi-Squared): " + (pop.length - 1)); statExpLabel.text("df: " + (pop.length - 1) + " "); statSELabel.text(" "); } } // ends setCurveLabel() function drawSample(nSams) { var theSample = new Array(sampleSize); var indices = new Array(sampleSize); var xb; var ssq; var tStat; var tmp; var i; for (var j=0; j < nSams; j++) { xb = 0; ssq = 0; tStat = 0; if ( varChoice.selected() == "Sample Chi-Squared") { if (sourceChoice.selected() == "Box") { var cum = vCumSum(pop); // cum expecting an Array var count = new Array(pop.length); // count expecting an Array for (i=0; i < pop.length; i++) { count[i] = 0.0; } for (i=0; i < sampleSize; i++) { tmp = rand.next(); if (tmp <= cum[0]) { count[0]++; } for (var k=1; k < count.length; k++) { if ( tmp > cum[k-1] && tmp <= cum[k] ) { count[k]++; } } } ssq = 0.0; for (i=0; i < pop.length; i++) { tmp = sampleSize*pop[i]; ssq += (count[i] - tmp)*(count[i] - tmp)/tmp; } sampleSSq[samplesSoFar++] = ssq; // FIX: aculich 2013-03-06 if (ssq < xMin || ssq > xMax) { xMin = Math.min(ssq, xMin); xMax = Math.max(ssq, xMax); } } else { console.error("Error in SampleDist.drawSample(): cannot draw from " + "this distribution with Sample Chi-Square!"); } } else { if (sourceChoice.selected() == "Box") { if (replaceCheck.is(':checked')) { indices = listOfRandInts(sampleSize, 0, pop.length-1); } else { indices = listOfDistinctRandInts(sampleSize, 0, pop.length-1); } for (i = 0; i < sampleSize; i++) { theSample[i] = pop[ indices[i] ]; xb += theSample[i]; } } else if (sourceChoice.selected() == "Normal") { for (i = 0; i < sampleSize; i++) { theSample[i] = rNorm(); xb += theSample[i]; } } else if (sourceChoice.selected() == "Uniform") { for (i = 0; i < sampleSize; i++) { theSample[i] = rand.next(); xb += theSample[i]; } } xb /= sampleSize; for (i = 0; i < sampleSize; i++) { ssq += (theSample[i] - xb)*(theSample[i] - xb); } if (sampleSize > 1) { // if n>1, log the sample s^2 and t ssq /= (sampleSize-1); sampleSSq[samplesSoFar] = ssq; tStat = xb/(Math.sqrt(ssq)/Math.sqrt(sampleSize)); sampleT[samplesSoFar] = tStat; } else { // otherwise, set to 0. sampleSSq[samplesSoFar] = 0; sampleT[samplesSoFar] = 0; } sampleMean[samplesSoFar++] = xb; // log the sample mean // FIX: aculich 2013-03-06 if (varChoice.selected() == "Sample Mean") { if (xb < xMin || xb > xMax) { xMin = Math.min(xb, xMin); xMax = Math.max(xb, xMax); } } else if (varChoice.selected() == "Sample t") { if (tStat < xMin || tStat > xMax) { xMin = Math.min(tStat, xMin); xMax = Math.max(tStat, xMax); } } else if ( varChoice.selected() == "Sample Sum") { tmp = xb * sampleSize; if (tmp < xMin || tmp > xMax) { xMin = Math.min(tmp, xMin); xMax = Math.max(tmp, xMax); } } else if (varChoice.selected() == "Sample S-Squared") { if (ssq < xMin || ssq > xMax) { xMin = Math.min(ssq, xMin); xMax = Math.max(ssq, xMax); } } } } setBins(); setBars(hiLiteLo, hiLiteHi); } // set the TextBars for hilight and sampleSize function setBars(l, h) { hi.set(h, xMin, xMax, Math.pow(10, -1 * nDigs)); lo.set(l, xMin, xMax, Math.pow(10, -1 * nDigs)); hiLiteLo = l; hiLiteHi = h; adjustSampleSize(); } function setBins() { binEnd = []; jQuery.each(range(0, nBins + 1), function(i) { binEnd[i] = xMin + i*(xMax - xMin)/nBins; }); countPop = new Array(nBins); countSample = new Array(nBins); if (sourceChoice.selected() == "Box" && pop.length > 0) { if (varChoice.selected() == "Sample Chi-Squared") { setCurve(); } else { countPop = listToHist(pop, binEnd, nBins); setCurve(); } } else if (sourceChoice.selected() == "Normal") { jQuery.each(range(0, nBins), function(i) { countPop[i] = (normCdf(binEnd[i+1]) - normCdf(binEnd[i]))/(binEnd[i+1] - binEnd[i]); }); } else if (sourceChoice.selected() == "Uniform") { var midPt; jQuery.each(range(0, nBins), function(i) { midPt = (binEnd[i]+binEnd[i+1])/2; if (midPt >= 0 && midPt <= 1) { countPop[i] = 1; } else { countPop[i] = 0; } }); } if (samplesSoFar > 0 ) { if (varChoice.selected() == "Sample S-Squared" || varChoice.selected() == "Sample Chi-Squared") { countSample = listToHist(sampleSSq, binEnd, nBins, samplesSoFar); } else if (varChoice.selected() == "Sample Mean") { countSample = listToHist(sampleMean, binEnd, nBins, samplesSoFar); } else if (varChoice.selected() == "Sample t") { countSample = listToHist(sampleT, binEnd, nBins, samplesSoFar); } else if (varChoice.selected() == "Sample Sum") { countSample = listToHist(scalVMult(sampleSize, sampleMean), binEnd, nBins, samplesSoFar); } } else { jQuery.each(range(0, nBins), function(i) { countSample[i] = 0; }); } } function setLims() { if (varChoice.selected() == "Sample Sum") { xMin = sampleSize * popMin; // these are the limits for the histogram xMax = sampleSize * popMax; } else if (varChoice.selected() == "Sample Chi-Squared") { xMin = 0.0; xMax = 10*Math.sqrt(pop.length - 1); // 5 SD } else if (varChoice.selected() == "Sample S-Squared") { xMin = 0.0; var maxDev = Math.max(popMean-popMin, popMax-popMean); xMax = 3*maxDev*maxDev/Math.sqrt(sampleSize); } else if (varChoice.selected() == "Sample Mean") { xMin = popMean-4*popSd/Math.sqrt(sampleSize); xMax = popMax+4*popSd/Math.sqrt(sampleSize); } else if (varChoice.selected() == "Sample t") { if (sampleSize > 2) { xMin = -3*Math.sqrt((sampleSize+0.0)/(sampleSize - 2.0)); xMax = 3*Math.sqrt((sampleSize+0.0)/(sampleSize - 2.0)); } else { xMin = -5; xMax = 5; } } if (showBoxHist) { xMin = Math.min(popMin, xMin); xMax = Math.max(popMax, xMax); } } function setSampleSize(size) { sampleSize = size; adjustSampleSize(); showPlot(); } function adjustSampleSize() { minSampleSize = 1; if (varChoice.selected() == "Sample S-Squared" || varChoice.selected() == "Sample t") { minSampleSize = 2; } if ( !replaceCheck.is(':checked') ) { maxSampleSize = pop.length; } else { maxSampleSize = maxMaxSampleSize; } sampleSize = Math.max(sampleSize,minSampleSize); sampleSize = Math.min(sampleSize,maxSampleSize); //sampleSizeBar.setValues(sampleSize,minSampleSize,maxSampleSize,1); //TODO(jmeady): Allow bounds on this. sampleSizeBar.val(sampleSize); } function setCurve() { var sd = 0; // sd for normal approx var mu = 0; // mean for normal approx hist.curves([null, null]); var fpc = 1.0; var popVar = popSd*popSd; if ( !replaceCheck.is(':checked') ) { popVar = popVar*pop.length/(pop.length-1.0); } if (!replaceCheck.is(':checked')) { fpc = Math.sqrt( (pop.length - sampleSize + 0.0)/(pop.length-1.0)); } if (curveChoice.selected() == "No Curve") { hist.hideCurves(); } else { hist.showCurves(); if (curveChoice.selected() == "Chi-Squared Curve") { if (varChoice.selected() == "Sample S-Squared") { var scale = (sampleSize - 1.0)/(popVar); // change of variables: (n-1)*S^2/sigma^2 ~ Chi^2_{n-1} hist.curves(1, function(x) { return scale*chi2Pdf(sampleSize-1.0, x * scale); }); } else if (varChoice.selected() == "Sample Chi-Squared") { hist.curves(1, function(x) { return chi2Pdf(pop.length-1.0, x); }); } else { console.warn("Warning in SampleDist.setCurve(): Chi-squared " + "approximation to " + varChoice.selected() + " Not Supported!"); curveChoice.select("No Curve"); hist.hideCurves(); return false; } } else if (curveChoice.selected() == "Normal Curve") { if (varChoice.selected() == "Sample Mean") { sd = fpc*popSd/Math.sqrt(sampleSize + 0.0); mu = popMean; } else if (varChoice.selected() == "Sample Sum") { sd = fpc*popSd * Math.sqrt(sampleSize + 0.0); mu = popMean * sampleSize; } else if (varChoice.selected() == "Sample S-Squared") { // E(chi^2) = (n-1), so E( sigma^2 chi^2 / (n-1) = sigma^2. // SD(chi^2) = sqrt(2(n-1)), so SD( sigma^2 chi^2/ (n-1)) = sqrt(2/(n-1)) sigma^2. sd = Math.sqrt(2.0/(sampleSize-1.0))*popSd*popSd; // FIX ME! // doesn't account for no replacement mu = popVar; } else if (varChoice.selected() == "Sample Chi-Squared") { sd = Math.sqrt(2.0*(pop.length-1.0)); mu = pop.length-1; } else if (varChoice.selected() == "Sample t") { if (sampleSize > 2) { sd = sampleSize/(sampleSize-2.0); } else { sd = NaN; console.warn("Warning in SampleDist.setCurve(): normal " + "approximation to Student t with sample size <= 2 " + " Not Supported!"); curveChoice.select("No Curve"); hist.hideCurves(); return false; } mu = 0; } hist.curves(1, function(x) { return normPdf(mu, sd, x); }); } else if (curveChoice.selected() == "Student t Curve") { if (varChoice.selected() == "Sample t") { hist.curves(1, function(x) { return tPdf(sampleSize-1, x); }); } else { console.warn("Warning in SampleDist.setCurve(): Student t " + "approximation to " + varChoice.selected() + " Not Supported!"); curveChoice.select("No Curve"); hist.hideCurves(); return false; } } } return true; } function hiLitArea() { var area = 0; for (var i=0; i < nBins; i++) { if( binEnd[i] > hiLiteHi || binEnd[i+1] <= hiLiteLo) { } else if (binEnd[i] >= hiLiteLo && binEnd[i+1] <= hiLiteHi) { area += countSample[i]*(binEnd[i+1]-binEnd[i]); } else if (binEnd[i] >= hiLiteLo && binEnd[i+1] > hiLiteHi) { area += countSample[i]*(hiLiteHi - binEnd[i]); } else if (binEnd[i] <= hiLiteLo && binEnd[i+1] <= hiLiteHi) { area += countSample[i]*(binEnd[i+1]-hiLiteLo); } else if (binEnd[i] < hiLiteLo && binEnd[i+1] > hiLiteHi) { area += countSample[i]*(hiLiteHi - hiLiteLo); } } return(area); } // ends hiLitArea() function normHiLitArea() { var area = 0; var fpc = 1.0; if (!replaceCheck.is(':checked')) { fpc = Math.sqrt( (pop.length - sampleSize + 0.0)/(pop.length-1.0)); } if (hiLiteHi > hiLiteLo) { area = normCdf((hiLiteHi - EX)/(fpc*SE)) - normCdf((hiLiteLo - EX)/(fpc*SE)); } return(area); }// ends normHiLitArea function chiHiLitArea() { var area = 0; if (hiLiteHi > hiLiteLo) { if (varChoice.selected() == "Sample S-Squared") { var scale = (sampleSize - 1.0)/(popSd*popSd); area = chi2Cdf(sampleSize-1, scale*hiLiteHi) - chi2Cdf(sampleSize-1, scale*hiLiteLo); } else if (varChoice.selected() == "Sample Chi-Squared") { area = chi2Cdf(pop.length-1, hiLiteHi) - chi2Cdf(pop.length-1, hiLiteLo); } else { console.error("Error in SampleDist.chiHiLitArea(): " + varChoice.selected() + " not supported. "); area = 0.0; } } return(area); }// ends chiHiLitArea function tHiLitArea() { var area = 0; if (hiLiteHi > hiLiteLo) { if (varChoice.selected() == "Sample t") { area = tCdf(sampleSize-1, hiLiteHi) - tCdf(sampleSize-1, hiLiteLo); } else { console.error("Error in SampleDist.tHiLitArea(): " + varChoice.selected() + " not supported. "); area = 0.0; } } return(area); }// ends chiHiLitArea doWhileVisible(container, function() { init(); }); } // Javascript rewrite of // http://statistics.berkeley.edu/~stark/Java/Html/ScatterPlot.htm // // Authors: Ken Yu <kenniyu@gmail.com> // James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the scatterplot (and // controls) in. // params: A javascript object with various parameters to customize the chart. // // title // - title: null // // // Whether or not to show the 'Graph of Ave' button. // - graphAveButton: true // // // Number of points in Graph of Ave. // - graphOfAvePoints: 9 // // // Boolean, true if user is allowed to add points to the chart. // - addPoints: true // // // Whether or not to show the 'Regression Line' button. // - regressButton: true // // // Whether or not to show the 'Plot Residuals' button. // - residualsButton: true // // // Whether or not to show the 'SDs' button. // - sdButton: true // // // Whether or not to show the 'SD Line' button. // - sdLineButton: true // // // Whether or not to show the 'R=' info. // - showR: true // // // Whether or not to show the 'R=' and 'N=' slider bars. // - showRBar: true // // // Whether or not to show the SD Lines by default. // - showSDs: false // // // Whether or not to show the SD Line by default. // - showSdLine: false // // // Whether or not to show the Graph of Ave by default. // - showGraphOfAve: false // // // Whether or not to show the Regression Line by default. // - showRegress: false // // // Whether or not to show the residuals by default. // - showResiduals: false // // // There are three different ways to supply input to the scatterplot: // // 1) External JSON-encoded data file. // // 2) Manual specification of data x and y values. // // 3) Normal bivariate with specified realized correlation coefficient. // // // // These input methods are all mutually exclusive - if the parameters for a // // particular input method are set, then the parameters for the other input // // methods should not be set. // // // 1) External JSON-encoded data file // // // Array of URLs (as strings) of json-encoded datasets // - files: null // // // Variable name to display on the X-axis by default. // - Xinit: null // // // Variable name to display on the Y-axis by default. // - Yinit: null // // // 2) Manual specification of data x and y values. // // // Data points. x and y should be arrays of the same length. // - x: null // - y: null // // // 3) Normal bivariate with specified realized correlation coefficient. // // // Correlation coefficient of generated data // - r: null // // // Number of generated data points // - n: null function Stici_Scatterplot(container_id, params) { var self = this; if (!params instanceof Object) { console.error('Stici_ScatterPlot params should be an object'); return; } // Configuration option defaults. this.options = { title: null, graphAveButton: true, graphOfAvePoints: 9, addPoints: true, regressButton: true, residualsButton: true, sdButton: true, sdLineButton: true, showR: true, showRBar: true, showSDs: false, showSdLine: false, showGraphOfAve: false, showRegress: false, showResiduals: false, files: null, Xinit: null, Yinit: null, r: null, n: null, x: null, y: null }; // For debugging: Warn of user params that are unknown. jQuery.each(params, function(key) { if (typeof(self.options[key]) == 'undefined') console.warn('Stici_Scatterplot: Unknown key \'' + key + '\''); }); // Override options with anything specified by the user. jQuery.extend(this.options, params); if (!this.options.showRBar || !this.options.showR) { this.options.showRBar = false; this.options.showR = false; } // jQuery object containing the entire chart. this.container = $('#' + container_id); // Check to make sure we know where the data is coming from before we do // anything else. if (!dataIsGenerated() && !dataIsFromExternalFile() && !dataIsManual()) { console.error('Unknown scatterplot data source.'); self.container.html('Unable to load scatterplot: unknown data source.'); return; } // Labels for the data. this.dataFields = null; // The data itself. this.dataValues = null; // The URL we got the JSON-encoded data from. this.dataSource = null; // Used so that some options are only triggered when the chart is initially // loaded (e.g. showSDs) - the user can change them afterwards and the chart // can reload without resetting the user preferences. this.inited = false; // Various handles to important jQuery objects. this.urlInput = null; this.dataSelect = null; this.xVariableSelect = null; this.yVariableSelect = null; this.currentData = null; this.xScale = null; this.yScale = null; this.chartWidth = this.container.width() - 20; this.chartHeight = this.container.height() - 100; this.chartDiv = null; this.r = null; this.n = null; this.rInput = null; // TODO(jmeady): Put this in the css file. this.chartMargins = { 'top': '10', 'right': '10', 'bottom': '25', 'left': '40' }; this.bottomControls = { 'sds': false, 'sd-line': false, 'avg-graph': false, 'reg-line': false, 'res-plot': false, 'use-points': false, 'plot-mean': true, 'r-hat': true }; // Select which function to use for reloading the data. this.reloadData = null; if (dataIsFromExternalFile()) { if (!params.files instanceof Array) { this.dataSource = null; this.options.files = []; } else { this.dataSource = this.options.files[0]; } this.reloadData = loadExternalData; } else if (dataIsGenerated()) { this.n = self.options.n; this.r = self.options.r; this.reloadData = loadGeneratedData; } else if (dataIsManual()) { if (self.options.x.length != self.options.y.length) { console.error('Data has been manually specified, but x and y options ' + 'have different numbers of data points.'); self.container.html('Invalid data supplied.'); return; } this.reloadData = loadManualData; } // Reloads chart data from this.dataSource // upon new data set selection function loadExternalData() { var $bottomControls = self.container.find('.bottom_controls'), $popboxControls = self.container.find('.popbox-controls'); self.options.files = []; self.dataFields = []; self.dataValues = []; self.dataSource = self.dataSelect.val(); // TODO unhardcode this var Xinit = 3; var Yinit = 2; jQuery.getJSON(self.dataSource, function(data) { self.dataFields = data[0]; self.dataValues = data.slice(1); // remove all data chilren self.xVariableSelect.empty(); self.yVariableSelect.empty(); // append all options for x and y variables $.each(self.dataFields, function(i, field) { // don't have option to graph ordinal or comments... if (field.indexOf('//') > -1) { return true; } if (!self.inited && null !== self.options.Xinit && field == self.options.Xinit) Xinit = i; if (!self.inited && null !== self.options.Yinit && field == self.options.Yinit) Yinit = i; self.xVariableSelect.append( $('<option/>').attr('value', i).text(field) ); self.yVariableSelect.append( $('<option/>').attr('value', i).text(field) ); }); self.xVariableSelect.val(Xinit); self.yVariableSelect.val(Yinit); // create popbox controls self.container.find('.popbox-controls').empty(); self.createPopbox('list-data'); self.createPopbox('univar-stats'); self.container.find('.popbox').popbox({'toggler': ['list-data', 'univar-stats']}); self.container.find('.popbox-content table').css('width', self.dataFields.length * 120 + 'px'); self.container.find('.popbox').on('mouseover mouseout click', function(e) { e.preventDefault(); handlePopboxMouseEvents(e); }); // reload the chart after new data set self.prepareData(); self.reloadChart(); }); } // Generates and loads data according to the specified r/n options. function loadGeneratedData() { var raw = cNormPoints(self.n, self.r); var xVals = raw[0]; var yVals = raw[1]; if (self.n == 1) { xVals = [5.5]; yVals = [5.5]; } // get array of x and y points self.currentData = $.map(xVals, function(xVal, index) { return { 'x': xVals[index], 'y': yVals[index], 'index': index, 'added': false, 'selected': false }; }); self.reloadChart(); } // Loads data that has been manually specified in the chart x and y options. function loadManualData() { self.currentData = $.map(self.options.x, function(xVal, index) { return { 'x': self.options.x[index], 'y': self.options.y[index], 'index': index, 'added': false, 'selected': false }; }); self.reloadChart(); } this.prepareData = function() { // get array of x and y points self.currentData = $.map(self.dataValues, function(values, index) { return { 'x': parseFloat(values[self.xVariableSelect.val()]), 'y': parseFloat(values[self.yVariableSelect.val()]), 'index': index, 'added': false, 'selected': self.container.find('.popbox.list-data tr[data-index="' + index + '"]').hasClass('selected') }; }); }; this.setScale = function(data) { // x scale. range from 0 to width var chartWidth = self.chartWidth, chartMargins = self.chartMargins, xMin = d3.min(data, function(d) { return d.x; }), xMax = d3.max(data, function(d) { return d.x; }), xScale, yMin = d3.min(data, function(d) { return d.y; }), yMax = d3.max(data, function(d) { return d.y; }), yScale; if (xMin == xMax) { xMin -= 4.5; xMax += 4.5; } if (yMin == yMax) { yMin -= 4.5; yMax += 4.5; } xScale = d3.scale.linear() .domain([xMin, xMax]) .range([0, chartWidth - chartMargins.left - chartMargins.right]) .nice(); if (yMax < 0.001 && yMin > -0.001) { yMax = 1; yMin = -1; } yScale = d3.scale.linear() .domain([yMin, yMax]) .range([self.chartHeight - self.chartMargins.top - self.chartMargins.bottom, 0]) .nice(); self.xScale = xScale; self.yScale = yScale; }; this.initCanvas = function() { // empty canvas self.chartDiv.empty(); var chartWidth = self.chartWidth, chartHeight = self.chartHeight, svg = d3.select(self.chartDiv.get(0)) .append('svg') .attr('width', self.chartWidth) .attr('height', self.chartHeight); }; this.drawDataPlot = function() { var data = self.currentData; // initialize blank canvas self.initCanvas(); // set scale self.setScale(data); // plot axes self.plotAxes('data'); // add points self.plotPoints(data, 'data'); // draw mouse rectangle self.drawMouseRect(); // update r-hat self.updateRHat(); }; this.plotAxes = function(plotType) { // function generating x axis, passed to another function later var xScale = self.xScale, yScale = self.yScale, chartMargins = self.chartMargins, chartWidth = self.chartWidth, chartHeight = self.chartHeight, xAxis = d3.svg.axis() .scale(xScale) .orient('bottom'), yAxis = d3.svg.axis() .scale(yScale) .orient('left'), svg = d3.select(self.chartDiv.get(0)).select('svg'); // add axes if (plotType === 'data') { svg.append('g') .attr('class', 'x axis') .attr('transform', 'translate(' + chartMargins.left + ', ' + (chartHeight - chartMargins.bottom) + ')') .call(xAxis); } else { svg.append('g') .attr('class', 'x axis') .attr('transform', 'translate(' + chartMargins.left + ', ' + (yScale(0) + 1*chartMargins.top) + ')') .call(xAxis); } svg.append('g') .attr('class', 'y axis') .attr('transform', 'translate(' + chartMargins.left + ', ' + chartMargins.top + ')') .call(yAxis); svg.select('g.x.axis') .append('line') .attr('class', 'line x-axis') .attr('x1', 0) .attr('x2', chartWidth - chartMargins.left) .attr('y1', 0) .attr('y2', 0); svg.select('g.y.axis') .append('line') .attr('class', 'line y-axis') .attr('x1', 0) .attr('x2', 0) .attr('y1', 0) .attr('y2', chartHeight - chartMargins.bottom - chartMargins.top); }; this.reloadChart = function() { if (self.bottomControls['res-plot'] === true) { self.toggleResPlot(true); } else { self.drawDataPlot(); self.plotSelectedOptions(['res-plot']); } if (!self.inited) { if (self.options.showSdLine) simulateBtnPress('sd-line'); if (self.options.showSDs) simulateBtnPress('sds'); if (self.options.showGraphOfAve) simulateBtnPress('graph-of-ave'); if (self.options.showRegress) simulateBtnPress('reg-line'); if (self.options.showResiduals) simulateBtnPress('res-plot'); } self.inited = true; }; this.updateRHat = function() { var data = self.filterData(), xData = $.map(data, function(d) { return d.x; }), yData = $.map(data, function(d) { return d.y; }), cc = corr(xData, yData).toFixed(2); if (dataIsGenerated()) self.rInput.val(cc); self.container.find('.bottom_controls').children().filter(function() { return $(this).data('btnId') == 'r-hat'; }).text('r: ' + cc); }; /*** * plotSelectedOptions: called when chart redrawn * params: Array discardOptions - options to ignore plotting ***/ this.plotSelectedOptions = function(discardOptions) { var tempSelected; discardOptions = (discardOptions || []); for (var option in self.bottomControls) { if (discardOptions.indexOf(option) > -1) { continue; } tempSelected = self.bottomControls[option]; self.toggleOption(option, tempSelected); } }; /*** * toggleOption: toggles an option in bottom controls * params: String option - the option to plot, * Boolean show ***/ this.toggleOption = function(option, show) { switch (option) { case 'sds': self.toggleSds(show); break; case 'sd-line': self.toggleSdLine(show); break; case 'graph-of-ave': self.toggleGraphOfAve(show); break; case 'reg-line': self.toggleRegLine(show); break; case 'res-plot': self.toggleResPlot(show); break; case 'use-points': self.toggleUsePoints(); break; case 'clear-points': self.toggleClearPoints(); break; case 'plot-mean': self.plotMean(); break; case 'r-hat': self.updateRHat(); break; default: break; } }; /*** * createListDataHtml - creates html for list data button ***/ this.createPopbox = function(popboxClass) { var content, btnTitle, btnId, popboxHtml; if (popboxClass === 'list-data') { btnTitle = 'List Data'; content = self.createListDataHtml(); btnId = 'list-data'; } else if (popboxClass === 'univar-stats') { btnTitle = 'Univariate Stats'; content = self.createUnivarStatsHtml(); btnId = 'univar-stats'; } popboxHtml = '' + '<div class="popbox ' + popboxClass + '">' + '<button class="open" id="' + btnId + '">' + btnTitle + '</button>' + '<div class="collapse">' + '<div class="box">' + '<div class="arrow"></div>' + '<div class="arrow-border"></div>' + '<div class="popbox-content">' + content + '</div>' + '</div>' + '</div>' + '</div>'; self.container.find('.bottom_controls .popbox-controls').append(popboxHtml); }; this.createUnivarStatsHtml = function() { var html = '<div class="univariate-stats-container">'; $.each(self.dataFields, function(index) { if (self.dataFields[index].indexOf('//') === 0) return; html += '<div class="univariate-stat-wrapper">' + '<h3>' + self.dataFields[index] + '</h3>'; var data = $.map(self.dataValues, function(values) { return parseFloat(values[index]); }); html += '<ul class="stat-list">' + '<li class="stat-item">Cases: ' + data.length + '</li>' + '<li class="stat-item">Mean: ' + mean(data).toFixed(2) + '</li>' + '<li class="stat-item">SD: ' + sd(data).toFixed(2) + '</li>' + '<li class="stat-item">Min: ' + data.min().toFixed(2) + '</li>' + '<li class="stat-item">LQ: ' + percentile(data, 25).toFixed(2) + '</li>' + '<li class="stat-item">Median: ' + percentile(data, 50).toFixed(2) + '</li>' + '<li class="stat-item">UQ: ' + percentile(data, 75).toFixed(2) + '</li>' + '<li class="stat-item">Max: ' + data.max().toFixed(2) + '</li>' + '</ul>'; html += '</div>'; }); html += '</div>'; return html; }; /*** * createListDataHtml - creates html for list data button ***/ this.createListDataHtml = function() { var dataFields = self.dataFields, dataValues = self.dataValues, numDataFields = dataFields.length, tempDataRow, tempDataCell, tempConcatData, html; html = '<div class="table-container">'+ '<div class="table-header">' + '<table class="data-fields">' + '<thead>' + '<tr>'; for (var i = 0; i < dataFields.length; i++) { html += '<td>' + dataFields[i] + '</td>'; } html += '</tr>' + '</thead>' + '</table>' + '</div>'; html += '<div class="table-body">' + '<table class="data-values">' + '<tbody>'; for (var j = 0; j < dataValues.length; j++) { tempDataRow = dataValues[j]; if (tempDataRow.length > dataFields.length) { // concat last elements tempConcatData = tempDataRow.slice(dataFields.length - 1).join(' ').replace(/[\/]/g, ''); tempDataRow = tempDataRow.slice(0, dataFields.length - 1); tempDataRow.push(tempConcatData); } html += '<tr data-index="' + j + '">'; for (var k = 0; k < tempDataRow.length; k++) { tempDataCell = tempDataRow[k]; html += '<td>' + tempDataCell + '</td>'; } html += '</tr>'; } html += '</tbody>' + '</table>' + '</div>' + '</div>'; return html; }; /*** * toggleClearPoints: toggles when clear added points is clicked ***/ this.toggleClearPoints = function() { self.currentData = $.grep( self.currentData, function(data, index) { return data.added === false; }); d3.select(self.chartDiv.get(0)).selectAll('.data-point[data-added="true"]').remove(); self.plotSelectedOptions(['res-plot', 'use-points']); }; /*** * toggleUsePoints: toggles when use added points is clicked ***/ this.toggleUsePoints = function() { self.plotSelectedOptions(['res-plot', 'use-points']); if (self.bottomControls['res-plot']) { self.toggleResPlot(true); } }; /*** * toggleResPlot: toggles residual plot * params: boolean show - true or false ***/ this.toggleResPlot = function(show) { var filteredData = self.filterData(), xData = $.map(filteredData, function(d) { return d.x; }), yData = $.map(filteredData, function(d) { return d.y; }), xMean = mean(xData), yMean = mean(yData), xSd = sd(xData), ySd = sd(yData), cc = corr(xData, yData), slope = cc * ySd / xSd, expectedY, tempData, tempRes, tempAdded, numPoints = self.currentData.length; if (show) { // initialize blank canvas self.initCanvas(); // empty residual data, and recalculate for each point self.residualData = []; for (var i = 0; i < numPoints; i++) { tempData = self.currentData[i]; expectedY = yMean - slope * (xMean - tempData.x); tempRes = tempData.y - expectedY; self.residualData.push({ 'x': tempData.x, 'y': tempRes, 'added': tempData.added, 'index': tempData.index }); } // recalculate scales self.setScale(self.residualData); // plot axes self.plotAxes('residual'); // add points self.plotPoints(self.residualData, 'residual'); // draw rectangle to detect mouse position self.drawMouseRect(); // plot selected options but ignore residual plot and use points self.plotSelectedOptions(['res-plot', 'use-points']); } else { // draw data plot, and plot selected options, but ignore residual plot self.drawDataPlot(); self.plotSelectedOptions(['res-plot']); } }; /*** * plotPoints: plots data points * params: Array data - array of data points * String plotType - identifies intent of chart to plot (residual or data) ***/ this.plotPoints = function(data, plotType) { var chartMargins = self.chartMargins, xScale = self.xScale, yScale = self.yScale, plotClass = plotType === 'data' ? 'plot-data' : 'plot-residual', pointPlot = d3.select(self.chartDiv.get(0)).select('svg').append('g') .attr('class', plotClass + ' plot') .attr('transform', 'translate(' + chartMargins.left + ',' + chartMargins.top + ')'), selectedDataPoints; pointPlot.selectAll('circle') .data(data) .enter() .append('circle') .attr('class', function(d) { var className = 'data-point', selectedMod = d.selected ? 'selected' : '', addedMod = d.added ? 'added' : ''; return className + ' ' + selectedMod + ' ' + addedMod; }) .attr('cx', function(d) { return xScale(d.x); }) .attr('cy', function(d) { return yScale(d.y); }) .attr('stroke', '#333') .attr('stroke-width', '1') .attr('data-index', function(d) { return d.index; }) .attr('data-added', function(d) { return d.added; }) .attr('data-x', function(d) { return d.x; }) .attr('data-y', function(d) { return d.y; }) .attr('r', function(d) { return (d.selected ? 3.5 : 2); }) .attr('fill', function(d) { return 'rgba(0, 0, 255, 1)'; }); d3.select(self.chartDiv.get(0)).selectAll('.data-point') .sort(function(d) { if (d && d.selected === true) { return 1; } else { return -1; } }); }; this.drawMouseRect = function() { var chartMargins = self.chartMargins, chartWidth = self.chartWidth, chartHeight = self.chartHeight, xScale = self.xScale, yScale = self.yScale, plotContainer = d3.select(self.chartDiv.get(0)).select('.plot'); plotContainer.append('rect') .attr('class', 'mouse-event-handler') .attr('width', chartWidth - chartMargins.left - chartMargins.right) .attr('height', chartHeight - chartMargins.top - chartMargins.bottom) .attr('fill', 'rgba(0, 0, 0, 0)') .on('mousemove', function() { var pos = d3.mouse(this), xPos = pos[0], yPos = pos[1], xVal = (xScale.invert(xPos)).toFixed(2), yVal = (yScale.invert(yPos)).toFixed(2); self.container.find('.cursor-pos').text(' x = ' + xVal + ' y = ' + yVal); }); d3.select(self.chartDiv.get(0)).select('.plot-data.plot .mouse-event-handler') .on('click', function() { if (!self.options.addPoints) return; var pos = d3.mouse(this), xPos = pos[0], yPos = pos[1], xVal = (xScale.invert(xPos)).toFixed(2), yVal = (yScale.invert(yPos)).toFixed(2), tempPoint = { 'x': parseFloat(xVal), 'y': parseFloat(yVal), 'added': true, 'index': self.currentData.length }; self.currentData.push(tempPoint); // draw points again self.plotPoint(tempPoint); // update any existing plots, if using added points if (!self.bottomControls['res-plot'] && self.bottomControls['use-points']) { self.plotSelectedOptions(['res-plot', 'use-points']); } }); }; this.plotPoint = function(tempPoint) { var yScale = self.yScale, xScale = self.xScale; pointPlot = d3.select(self.chartDiv.get(0)).select('.plot-data.plot'); pointPlot.insert('circle', '.data-point') .attr('class', 'data-point added') .attr('cx', xScale(tempPoint.x)) .attr('cy', yScale(tempPoint.y)) .attr('stroke', '#333') .attr('stroke-width', '1') .attr('data-index', tempPoint.index) .attr('data-added', tempPoint.added) .attr('data-x', tempPoint.x) .attr('data-y', tempPoint.y) .attr('r', 2); }; /*** * toggleRegLine: toggles regression line * params: boolean show - true or false ***/ this.toggleRegLine = function(show) { var data = self.filterData(), xData, yData, xMean, yMean, xMin, xMax, xSd, ySd, slope, cc, regLine, pointMean, point1, point2; self.container.find('svg g.reg-line').remove(); if (show) { xData = $.map(data, function(d) { return d.x; }); yData = $.map(data, function(d) { return d.y; }); xMin = d3.min(xData); xMax = d3.max(xData); xMean = mean(xData); yMean = mean(yData); xSd = sd(xData); ySd = sd(yData); cc = corr(xData, yData); slope = cc * ySd / xSd; pointMean = { 'x': xMean, 'y': yMean }; point1 = { 'x': xMin, 'y': pointMean.y - slope * (pointMean.x - xMin) }; point2 = { 'x': xMax, 'y': pointMean.y - slope * (pointMean.x - xMax) }; regLine = d3.select(self.chartDiv.get(0)).select('.plot-data') .append('g') .attr('class', 'reg-line'); regLine.append('line') .attr('x1', self.xScale(point1.x)) .attr('x2', self.xScale(point2.x)) .attr('y1', self.yScale(point1.y)) .attr('y2', self.yScale(point2.y)); } }; /*** * toggleGraphOfAve: toggles graph of averages * params: boolean show - true or false ***/ this.toggleGraphOfAve = function(show) { var plotType = self.bottomControls['res-plot'] === true ? 'plot-residual' : 'plot-data', data = self.filterData(), xData, yData, xMax, xMin, xMean, yMean, xSd, ySd, cc, slope, expectedY, tempRes, tempXMin, tempXMax, numPointsInBin, tempPointOfAve, xRangeIncrement, cumY, avgY, graphOfAve; self.container.find('svg g.graph-of-ave').remove(); if (show) { xData = $.map(data, function(d) { return d.x; }); yData = $.map(data, function(d) { return d.y; }); xMax = d3.max(xData); xMin = d3.min(xData); xRangeIncrement = (xMax - xMin) / self.options.graphOfAvePoints; xMean = mean(xData); yMean = mean(yData); xSd = sd(xData); ySd = sd(yData); cc = corr(xData, yData); slope = cc * ySd / xSd; graphOfAve = d3.select(self.chartDiv.get(0)).select('.' + plotType) .append('g') .attr('class', 'graph-of-ave'); for (var i = 0; i < self.options.graphOfAvePoints; i++) { cumY = 0; numPointsInBin = 0; tempXMin = xMin + i*xRangeIncrement; tempXMax = xMin + (i+1)*xRangeIncrement; for (var j = 0; j < data.length; j++) { if (i === self.options.graphOfAvePoints - 1) { // fix to include largest endpoint in range if (data[j].x >= tempXMin && data[j].x <= tempXMax) { numPointsInBin += 1; cumY += data[j].y; } } else { if (data[j].x >= tempXMin && data[j].x < tempXMax) { numPointsInBin += 1; cumY += data[j].y; } } } if (numPointsInBin > 0) { // calculate average, create point avgY = cumY/numPointsInBin; tempPointOfAve = { 'x': (tempXMin + tempXMax)/2, 'y': avgY }; if (plotType === 'plot-residual') { // residual plot, plot on residual scale expectedY = yMean - slope * (xMean - tempPointOfAve.x); tempRes = avgY - expectedY; tempPointOfAve = { 'x': (tempXMin + tempXMax)/2, 'y': avgY - expectedY }; } // plot the point graphOfAve.append('rect') .attr('x', self.xScale(tempPointOfAve.x) - 2.5) .attr('y', self.yScale(tempPointOfAve.y) - 2.5) .attr('height', 5) .attr('width', 5); } } } }; /*** * toggleSdLine: toggles SD line * params: boolean show - true or false ***/ this.toggleSdLine = function(show) { var data = self.filterData(), xData, yData, xMean, yMean, xMax, xMin, xSd, ySd, cc, slope, pointMean, point1, point2, sdLine; self.container.find('svg g.sd-line').remove(); if (show) { xData = $.map(data, function(d) { return d.x; }); yData = $.map(data, function(d) { return d.y; }); xMean = mean(xData); yMean = mean(yData); xSd = sd(xData); ySd = sd(yData); cc = corr(xData, yData); xMax = d3.max(xData); xMin = d3.min(xData); yMin = d3.min(yData); yMax = d3.max(yData); slope = (cc >= 0 ? 1 : -1 ) * ySd/xSd; pointMean = { 'x': xMean, 'y': yMean }; point1 = { 'x': xMin, 'y': pointMean.y - slope * (pointMean.x - xMin) }; point2 = { 'x': xMax, 'y': pointMean.y - slope * (pointMean.x - xMax) }; if (point1.y < yMin) { // y is below axes, get x "intercept" (y = yMin) point1.x = pointMean.x - (pointMean.y - yMin)/slope; point1.y = yMin; } if (point2.y < yMin) { point2.x = (yMin - pointMean.y)/slope + xMean; point2.y = yMin; } sdLine = d3.select(self.chartDiv.get(0)).select('.plot-data') .append('g') .attr('class', 'sd-line'); sdLine.append('line') .attr('x1', self.xScale(point1.x)) .attr('x2', self.xScale(point2.x)) .attr('y1', self.yScale(point1.y)) .attr('y2', self.yScale(point2.y)); } }; this.filterData = function() { var useAddedPoints = self.bottomControls['use-points'], allData = self.currentData, numData = allData.length, tempDataPoints; if (useAddedPoints) { return allData; } else { // if not using added points, filter points that have added = false tempDataPoints = $.grep( allData, function(data, index) { return data.added === false; }); return tempDataPoints; } }; /*** * highlightDataPoint: highlights and unhighlights data point * params: dataIndex: index of data point to highlight ***/ this.highlightDataPoint = function(dataIndex, eventType) { var selectedDataPoint = d3.select(self.chartDiv.get(0)).select('.data-point[data-index="' + dataIndex + '"]'), dataSticky = selectedDataPoint.classed('selected'), newStickyState; if (eventType === 'mouseover') { // reorders the elements such that the one to be highlighted is on top selectedDataPoint.node().parentNode.appendChild(selectedDataPoint.node()); // apply the active class, emphasize data point selectedDataPoint.classed('active', true) .transition() .duration(200) .attr('r', 5); } else if (eventType === 'mouseout') { // revert circle back to looks of regular data point, unless sticky selectedDataPoint.classed('active', false) .transition() .duration(200) .attr('r', function() { return (dataSticky ? 3.5 : 2); }); } else if (eventType === 'click') { // remove other sticky data points /* d3.select(self.chartDiv.get(0)).select('.data-point.selected:not([data-index="' + dataIndex + '"])') .classed('selected', false) .classed('highlight', false) .transition() .duration(200) .attr('r', 2); */ newStickyState = !dataSticky; // toggle sticky on data point selectedDataPoint.classed('selected', newStickyState); // new sticky state self.currentData[dataIndex].selected = newStickyState; // update class for corresponding data in data-list // self.container.find('.popbox.list-data tr:not([data-index="' + dataIndex + '"])').removeClass('selected'); self.container.find('.popbox.list-data tr[data-index="' + dataIndex + '"]').toggleClass('selected'); } }; /*** * plotMean: plots mean ***/ this.plotMean = function() { // called whenever data changes var data = self.filterData(), xData = $.map(data, function(d) { return d.x; }), yData = $.map(data, function(d) { return d.y; }), xMean = mean(xData), yMean = (self.bottomControls['res-plot'] === true ? 0 : mean(yData)), xScale = self.xScale, yScale = self.yScale, rectSize = 5; d3.select(self.chartDiv.get(0)).select('.mean-point').remove(); d3.select(self.chartDiv.get(0)).select('svg .plot') .append('rect') .attr('x', xScale(xMean) - rectSize/2) .attr('y', yScale(yMean) - rectSize/2) .attr('class', 'mean-point') .attr('width', rectSize) .attr('height', rectSize) .attr('fill', 'rgba(255, 0, 0, 1)') .attr('stroke', '#000'); }; /*** * toggleSds: toggles SDs * params: boolean show - true or false ***/ this.toggleSds = function(show) { var data = self.filterData(), xMean, yMean, xSd, ySd, xData, yData, xSdPlots, ySdPlots, sds; self.container.find('svg g.sds').remove(); if (show) { // compute lines xData = $.map(data, function(d) { return d.x; }); yData = $.map(data, function(d) { return d.y; }); xMean = mean(xData); yMean = mean(yData); xSd = sd(xData); ySd = sd(yData); xSdPlots = [(xMean - xSd), (xMean + xSd)]; ySdPlots = [(yMean - ySd), (yMean + ySd)]; // plot lines sds = d3.select(self.chartDiv.get(0)).select('.plot-data') .append('g') .attr('class', 'sds'); sds.append('line') .attr('x1', self.xScale(xSdPlots[0])) .attr('x2', self.xScale(xSdPlots[0])) .attr('y1', 0) .attr('y2', self.chartHeight - self.chartMargins.bottom - self.chartMargins.top); sds.append('line') .attr('x1', self.xScale(xSdPlots[1])) .attr('x2', self.xScale(xSdPlots[1])) .attr('y1', 0) .attr('y2', self.chartHeight - self.chartMargins.bottom - self.chartMargins.top); sds.append('line') .attr('x1', 0) .attr('x2', self.chartWidth - self.chartMargins.left - self.chartMargins.right) .attr('y1', self.yScale(ySdPlots[0])) .attr('y2', self.yScale(ySdPlots[0])); sds.append('line') .attr('x1', 0) .attr('x2', self.chartWidth - self.chartMargins.left - self.chartMargins.right) .attr('y1', self.yScale(ySdPlots[1])) .attr('y2', self.yScale(ySdPlots[1])); } }; // Initializes the chart controls (top, data, and bottom) function initControls() { var $stici = $('<div/>').addClass('stici').addClass( 'stici_scatterplot'), $topControls = $('<div/>').addClass('top_controls'); self.container.append($stici); $stici.append($topControls); if (typeof(self.options.title) == "string") { $topControls.append(self.options.title); } // Chart (for svg container) self.chartDiv = $('<div/>').addClass('stici_chart').addClass('chart_box'); $stici.append(self.chartDiv); // Top controls if (dataIsFromExternalFile()) { self.urlInput = $('<input type="text" />'); self.dataSelect = $('<select class="data_select"/>').change(self.reloadData); self.xVariableSelect = $('<select class="variable_select"/>').change(function() { self.prepareData(); self.reloadChart(); }); self.yVariableSelect = $('<select class="variable_select"/>').change(function() { self.prepareData(); self.reloadChart(); }); $topControls.append('Data: '); if (self.options.files.length > 1) { $topControls.append(self.dataSelect); } else { $topControls.append(self.options.files[0].replace(/^.*[\\\/]/, '')); $topControls.append(' '); } // for each data set, append option to option select $.each(self.options.files, function(i, dataUrl) { self.dataSelect.append($('<option/>') .attr('value', dataUrl) .text(dataUrl.replace(/^.*[\\\/]/, ''))); }); // append x and y variable selects $topControls.append(self.yVariableSelect); $topControls.append(' vs '); $topControls.append(self.xVariableSelect); } else if (dataIsGenerated()) { // r controls self.rInput = jQuery('<input type="text" />').change(function() { if (!self.inited) return; rSlider.slider('value', self.rInput.val()); self.r = self.rInput.val(); loadGeneratedData(); }); var updateRInput = function() { if (!self.inited) return; self.rInput.val(rSlider.slider('value')); self.r = rSlider.slider('value'); loadGeneratedData(); }; var rSlider = jQuery('<span/>').addClass('slider').slider({ change: updateRInput, slide: updateRInput, step: 0.001, max: 1, min: -1 }); self.rInput.val(self.r); rSlider.slider('value', self.rInput.val()); if (self.options.showRBar) { $topControls.append('r: ').append(self.rInput).append(rSlider); } // n controls var nInput = jQuery('<input type="text" />').change(function() { if (!self.inited) return; nSlider.slider('value', nInput.val()); self.n = nInput.val(); loadGeneratedData(); }); var updateNInput = function() { if (!self.inited) return; nInput.val(nSlider.slider('value')); self.n = nSlider.slider('value'); loadGeneratedData(); }; var nSlider = jQuery('<span/>').addClass('slider').slider({ change: updateNInput, slide: updateNInput, step: 1, max: 200, min: 3 }); if (self.options.showRBar) { nInput.val(self.n); nSlider.slider('value', nInput.val()); $topControls.append('n: ').append(nInput).append(nSlider); } } // Bottom controls var $bottom = $('<div/>').addClass('bottom_controls').addClass('extended'), $rHat = $('<span/>').data('btnId', 'r-hat').text('r: 0.16'), $toggleSd = $('<button/>').data('btnId', 'sds').text('SDs'), $toggleSdLine = $('<button/>').data('btnId', 'sd-line').text('SD Line'), $toggleGraphOfAve = $('<button/>').data('btnId', 'graph-of-ave').text('Graph of Ave'), $toggleRegLine = $('<button/>').data('btnId', 'reg-line').text('Regression Line'), $toggleResPlot = $('<button/>').data('btnId', 'res-plot').text('Plot Residuals'), $popboxControls = $('<div/>').attr('class', 'popbox-controls'), $toggleUsePoints = $('<button/>').data('btnId', 'use-points').text('Use Added Points'), $toggleClearPoints = $('<button/>').data('btnId', 'clear-points').text('Clear Added Points'), $cursorPos = $('<span/>').addClass('cursor-pos').text(''); $stici.append($bottom); if (self.options.showR && !dataIsGenerated()) $bottom.append($rHat); if (self.options.sdButton) $bottom.append($toggleSd); if (self.options.sdLineButton) $bottom.append($toggleSdLine); if (self.options.graphAveButton) $bottom.append($toggleGraphOfAve); if (self.options.regressButton) $bottom.append($toggleRegLine); if (self.options.residualsButton) $bottom.append($toggleResPlot); $bottom.append('<br/>').append($popboxControls); if (self.options.addPoints) $bottom.append($toggleUsePoints).append($toggleClearPoints); $bottom.append($cursorPos); $stici.find('button').on('click', function(e) { e.preventDefault(); handleBtnToggle(e); }); } /*** * handleBtnToggle: Handles button toggle * params: Event e ***/ function handleBtnToggle(e) { var $target = self.container.find(e.target), btnId = $target.data('btnId'); if (btnId === 'clear-points' || btnId === 'list-data' || btnId === 'univar-stats') { } else { $target.toggleClass('selected'); self.bottomControls[btnId] = (self.bottomControls[btnId] === true ? false : true); } self.toggleOption(btnId, $target.hasClass('selected')); } /*** * handleMouseOver: event handler for mouse over data * params: Event e ***/ function handlePopboxMouseEvents(e) { var eventType = e.type, $target = self.container.find(e.target), $closestTable = $target.closest('table'), $closestRow, dataIndex; if ($closestTable.hasClass('data-values')) { $closestRow = $target.closest('tr'); dataIndex = $closestRow.attr('data-index'); self.highlightDataPoint(dataIndex, eventType); } } /*** * Simulates a click on one of the bottom buttons given its btnId, * e.g. 'sd-line'. Used during initialization to set the buttons to the * state defined in the initialization parameters. */ function simulateBtnPress(btnId) { self.container.find('.bottom_controls').children().filter(function() { return $(this).data('btnId') == btnId; }).click(); } /*** * Returns true if data source is an external json-encoded file, false * otherwise. */ function dataIsFromExternalFile() { if (self.options.files !== null) return true; else return false; } /*** * Returns true if data is generated according to a normal bivariate with * specified realized correlation coefficient. */ function dataIsGenerated() { if (self.options.r !== null || self.options.n !== null) return true; else return false; } /*** * Returns true if the data points have been manually specified. */ function dataIsManual() { if (self.options.x !== null || self.options.y !== null) return true; else return false; } doWhileVisible(self.container, function() { initControls(); self.reloadData(); }); } // Javascript implementation of venn diagram for sticigui. No params are // currently available. // // Authors: Jason McGee <mcghee.j@berkeley.edu> // James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> function Stici_Venn(container_id, params) { var self = this; this.env = jQuery('#' + container_id); var app = jQuery('<div/>',{id:container_id + 'app'}) .addClass('stici_venn') .addClass('stici'); this.env.append(app); this.container = jQuery('<div/>',{id:container_id + 'container'}).addClass('container'); var buttons = jQuery('<div/>',{id:container_id + 'buttons'}).addClass('buttons'); var scrollbars = jQuery('<div/>',{id:container_id + 'scrollbars'}).addClass('scrollbars'); app.append(self.container); app.append(buttons); app.append(scrollbars); this.container.css('width',(this.env.width() - buttons.width()) + 'px'); this.container.css('height', (this.env.height() - scrollbars.height()) + 'px'); var s_outline, a_outline, b_outline; var a_fill, b_fill, ab_fill; var ab_text, a_or_b_text; // Create all of the html objects so we can get handles to them and all. Then // create the controls. doWhileVisible(self.env, function() { draw(); }); // Array of row arrays. E.g.: // // A B C // X Y C // // Is represented as // [[A, B, C], // [X, Y, Z]] var button_args = [ [{label: 'A', filled: a_fill}, {label: 'Ac', filled: s_outline, opaque: a_fill}], [{label: 'B', filled: b_fill}, {label: 'Bc', filled: s_outline, opaque: b_fill}], [{label: 'A or B', filled: [a_fill, b_fill]}, {label: 'AB', filled: ab_fill}], [{label: 'ABc', filled: a_fill, opaque: ab_fill}, {label: 'AcB', filled: b_fill, opaque: ab_fill}], [{label: 'S', filled: s_outline}, {label: '{}'}] ]; $.each(button_args, function(i, button_row) { var row = jQuery('<div/>').addClass('button_row'); $.each(button_row, function(i, button) { button.label = button.label.replace(/c/g, '<sup>c</sup>'); button.label = button.label.replace(/\|/g, ' | '); button.label = button.label.replace(/ or /g, ' \u222A '); var button_div = jQuery('<div/>').addClass('button'); row.append(button_div); var inp = jQuery('<input/>',{type:'radio',name:'buttons'}); var label = jQuery('<label/>').click(function() {inp.prop('checked', true);}); button_div.click(function() { inp.prop('checked', true); a_outline.removeClass('selected opaque'); b_outline.removeClass('selected opaque'); a_fill.removeClass('selected opaque'); b_fill.removeClass('selected opaque'); ab_fill.removeClass('selected opaque'); s_outline.removeClass('selected opaque'); if (button.opaque !== undefined) { if (!button.opaque instanceof Array) button.opaque = [button.opaque]; $.each(button.opaque, function(i, opaque_element) { jQuery(opaque_element).addClass('opaque'); }); } if (button.filled !== undefined) { if (!button.filled instanceof Array) button.filled = [button.filled]; $.each(button.filled, function(i, fill_element) { jQuery(fill_element).addClass('selected'); }); } }); label.html(button.label); button_div.append(inp); button_div.append(label); }); buttons.append(row); }); function createPercentControl(letter, size) { var sb = jQuery('<div/>',{id:container_id + 'psb' + letter}).addClass('scrollbar'); var lbl = jQuery('<label/>').attr('for', container_id + 'sb'+letter); lbl.html('P(' + letter + ') (%)'); var idFunc1 = function() { $('#' + container_id + 'sb' + letter).slider('value', this.value); updateSizes(); }; var idFunc2 = function() { $('#' + container_id + 'sb' + letter + 't').val($(this).slider('value')); updateSizes(); }; var input = jQuery('<input/>', { type: 'text', id: container_id + 'sb'+letter+'t', change: idFunc1, value: size, size: 2 }); var input2 = jQuery('<span/>') .addClass('slider') .attr('id', container_id + 'sb' + letter) .slider({ change: idFunc2, slide: idFunc2, min: 1, max: 100, step: 1, value: size }); sb.append(lbl); sb.append(input); sb.append(input2); scrollbars.append(sb); } createPercentControl('A', 30); createPercentControl('B', 20); var infoDiv = jQuery('<div/>').addClass('info'); ab_text = jQuery('<p/>'); a_or_b_text = jQuery('<p/>'); infoDiv.append(ab_text).append(a_or_b_text); scrollbars.append(infoDiv); updateSizes(); // Synchronizes fill positions so that the outline is always visible on the A // and B boxes, and the intersection area is synchronized. function syncPositions() { // Make sure everything stays in bounds. function bound(outline) { var x_offset = outline.offset().left + outline.width() - (s_outline.offset().left + s_outline.width()); if (x_offset > 0) outline.css('left', (outline.position().left - x_offset) + 'px'); var y_offset = outline.offset().top + outline.height() - (s_outline.offset().top + s_outline.height()); if (y_offset > 0) outline.css('top', (outline.position().top - y_offset) + 'px'); if (outline.position().left < s_outline.position().left) outline.css('left', s_outline.position().left + 'px'); if (outline.position().top < s_outline.position().top) outline.css('top', s_outline.position().top + 'px'); } function intersect(a, b, ab) { var a_x = a.position().left; var a_y = a.position().top; var a_w = a.width(); var a_h = a.height(); var b_x = b.position().left; var b_y = b.position().top; var b_w = b.width(); var b_h = b.height(); if (a_x + a_w < b_x || b_x + b_w < a_x || a_y + a_h < b_y || b_y + b_h < a_y) { ab.css('display', 'none'); } else { ab.css('display', ''); var x1 = Math.max(a_x, b_x); var y1 = Math.max(a_y, b_y); ab.css('left', x1 + 'px'); ab.css('top', y1 + 'px'); var x2 = Math.min(a_x + a_w, b_x + b_w); var y2 = Math.min(a_y + a_h, b_y + b_h); ab.css('width', (x2 - x1) + 'px'); ab.css('height', (y2 - y1) + 'px'); } } bound(a_outline); bound(b_outline); // Synchronize the fills with the outlines. a_fill.css('left', a_outline.css('left')); a_fill.css('top', a_outline.css('top')); a_fill.css('width', a_outline.css('width')); a_fill.css('height', a_outline.css('height')); b_fill.css('left', b_outline.css('left')); b_fill.css('top', b_outline.css('top')); b_fill.css('width', b_outline.css('width')); b_fill.css('height', b_outline.css('height')); // Calculate the intersection. intersect(a_outline, b_outline, ab_fill); // Calculate P(AB) and P(A or B) var s_area = s_outline.width() * s_outline.height(); var p_a = a_fill.width() * a_fill.height() / s_area * 100; var p_b = b_fill.width() * b_fill.height() / s_area * 100; var p_ab = ab_fill.width() * ab_fill.height() / s_area * 100; var p_a_or_b = (p_a + p_b - p_ab); if ((ab_fill.position().left == a_outline.position().left && ab_fill.position().top == a_outline.position().top && ab_fill.width() == a_outline.width() && ab_fill.height() == a_outline.height()) || (ab_fill.position().left == b_outline.position().left && ab_fill.position().top == b_outline.position().top && ab_fill.width() == b_outline.width() && ab_fill.height() == b_outline.height())) { p_ab = Math.min($('#' + container_id + 'sbA').slider('value'), $('#' + container_id + 'sbB').slider('value')); p_a_or_b = Math.max($('#' + container_id + 'sbA').slider('value'), $('#' + container_id + 'sbB').slider('value')); } if (ab_fill.css('display') == 'none') { p_ab = 0; p_a_or_b = parseFloat($('#' + container_id + 'sbA').slider('value')) + parseFloat($('#' + container_id + 'sbB').slider('value')); } ab_text.text('P(AB): ' + p_ab.fix(1) + '%'); a_or_b_text.text('P(A or B): ' + p_a_or_b.fix(1) + '%'); } // Updates the sizes of A and B to what the slider specifies. function updateSizes() { // ratio = width / height // area = width * height // width = area / height // width = ratio * height // width * width = area * ratio // height = area / width var ratio = s_outline.width() / s_outline.height(); var s_area = s_outline.width() * s_outline.height(); var a_p = $('#' + container_id + 'sbA').slider('value') / 100; var a_area = s_area * a_p; var a_width = Math.sqrt(a_area * ratio); var a_height = a_area / a_width; a_outline.width(a_width + 'px'); a_outline.height(a_height + 'px'); var b_p = $('#' + container_id + 'sbB').slider('value') / 100; var b_area = s_area * b_p; var b_width = Math.sqrt(b_area * ratio); var b_height = b_area / b_width; b_outline.width(b_width + 'px'); b_outline.height(b_height + 'px'); syncPositions(); } function draw() { // Initial sizes. These will get overwritten as soon as updateSizes is // called. var scaleFactorX = 0.3; var scaleFactorY = 0.3; s_outline = jQuery('<div/>').addClass('box').addClass('S'); s_outline.css('left', '10px'); s_outline.css('top', '10px'); s_outline.css('width', (self.container.width() - 20) + 'px'); s_outline.css('height', (self.container.height() - 20) + 'px'); s_outline.text('S'); self.container.append(s_outline); var rectX = self.container.width() / 2 - 50; var rectY = self.container.height() / 2 - 25; a_fill = jQuery('<div/>').addClass('box').addClass('A').addClass('fill'); a_outline = jQuery('<div/>').addClass('box').addClass('A').addClass('outline'); a_outline.css('left', (rectX - 32) + 'px'); a_outline.css('top', (rectY - 16) + 'px'); a_outline.css('width', (s_outline.width() * scaleFactorX) + 'px'); a_outline.css('height', (s_outline.height() * scaleFactorY) + 'px'); a_outline.text('A'); b_fill = jQuery('<div/>').addClass('box').addClass('B').addClass('fill'); b_outline = jQuery('<div/>').addClass('box').addClass('B').addClass('outline'); b_outline.css('left', (rectX + 32) + 'px'); b_outline.css('top', (rectY + 16) + 'px'); b_outline.css('width', (s_outline.width() * scaleFactorX) + 'px'); b_outline.css('height', (s_outline.height() * scaleFactorY) + 'px'); b_outline.text('B'); ab_fill = jQuery('<div/>').addClass('box').addClass('AB').addClass('fill'); self.container.append(a_fill); self.container.append(b_fill); self.container.append(ab_fill); self.container.append(a_outline); self.container.append(b_outline); a_outline.draggable({ containment: s_outline, drag: syncPositions, stop: syncPositions }); b_outline.draggable({ containment: s_outline, drag: syncPositions, stop: syncPositions }); } } // Javascript implementation of venn diagram for sticigui. No params are // currently available. // // Authors: Jason McGee <mcghee.j@berkeley.edu> // James Eady <jeady@berkeley.edu> // Philip B. Stark <stark@stat.berkeley.edu> // // container_id: the CSS ID of the container to create the venn diagram (and // controls) in. // params: A javascript object with various parameters to customize the chart. // // Whether or not to render the conditional probability radio buttons. // - showConditional: false // // // Whether or not to show the P= labels // - showProbabilities: true function Stici_Venn3(container_id, params) { var self = this; this.env = jQuery('#' + container_id); var app = jQuery('<div/>',{id:container_id + 'app'}) .addClass('stici_venn') .addClass('stici_venn3') .addClass('stici'); this.env.append(app); // Configuration option defaults. this.options = { showConditional: false, showProbabilities: true }; // Override options with anything specified by the user. jQuery.extend(this.options, params); this.container = jQuery('<div/>',{id:container_id + 'container'}).addClass('container'); var buttons_div = jQuery('<div/>',{id:container_id + 'buttons'}).addClass('buttons'); var scrollbars = jQuery('<div/>',{id:container_id + 'scrollbars'}).addClass('scrollbars'); app.append(self.container); app.append(buttons_div); app.append(scrollbars); this.container.css('width',(this.env.width() - buttons_div.width()) + 'px'); this.container.css('height', (this.env.height() - scrollbars.height()) + 'px'); var s_outline, a_outline, b_outline, c_outline; var a_fill, b_fill, c_fill, ab_fill, ac_fill, bc_fill, abc_fill; // Create all of the html objects so we can get handles to them and all. Then // create the controls. doWhileVisible(self.env, function() { draw(); }); // Array of row arrays. E.g.: // // A B C // X Y C // // Is represented as // [[A, B, C], // [X, Y, Z]] this.buttons_arr = [ [{label: 'A', filled: a_fill}, {label: 'B', filled: b_fill}, {label: 'C', filled: c_fill}, {label: 'Ac', filled: s_outline, opaque: a_fill}], [{label: 'Bc', filled: s_outline, opaque: b_fill}, {label: 'Cc', filled: s_outline, opaque: c_fill}, {label: 'AB', filled: ab_fill}, {label: 'AC', filled: ac_fill}], [{label: 'BC', filled: bc_fill}, {label: 'A or B', filled: [a_fill, b_fill]}, {label: 'A or C', filled: [a_fill, c_fill]}, {label: 'B or C', filled: [b_fill, c_fill]}], [{label: 'ABC', filled: abc_fill}, {label: 'A or B or C', filled: [a_fill, b_fill, c_fill]}, {label: 'ABc', filled: a_fill, opaque: ab_fill}, {label: 'AcB', filled: b_fill, opaque: ab_fill}], [{label: 'AcBC', filled: bc_fill, opaque: abc_fill}, {label: 'Ac or BC', filled: [bc_fill, s_outline], opaque: a_fill}, {label: 'S', filled: s_outline}, {label: '{}'}] ]; if (this.options.showConditional) { self.buttons_arr.push( [{label: 'P(A|B)', filled: b_fill, hilite: ab_fill}, {label: 'P(Ac|B)', filled: ab_fill, hilite: b_fill}, {label: 'P(B|A)', filled: a_fill, hilite: ab_fill}]); self.buttons_arr.push( [{label: 'P(A|BC)', filled: bc_fill, hilite: abc_fill}, {label: 'P(Ac|BC)', filled: abc_fill, hilite: bc_fill}, {label: 'P(A|(B or C))', filled: [b_fill, c_fill], hilite: [ab_fill, ac_fill]}]); } self.buttons = {}; self.buttons_arr = $.map(self.buttons_arr, function(button_row) { var row = jQuery('<div/>').addClass('button_row'); button_row = $.map(button_row, function(button) { button.human_label = button.label; button.label = button.label.replace(/c/g, '<sup>c</sup>'); button.label = button.label.replace(/\|/g, ' | '); button.label = button.label.replace(/ or /g, ' \u222A '); if (button.opaque !== undefined) { if (!(button.opaque instanceof Array)) button.opaque = [button.opaque]; } else { button.opaque = []; } if (button.filled !== undefined) { if (!(button.filled instanceof Array)) button.filled = [button.filled]; } else { button.filled = []; } if (button.hilite !== undefined) { if (!(button.hilite instanceof Array)) button.hilite = [button.hilite]; } else { button.hilite = []; } var button_div = jQuery('<div/>').addClass('button'); button.div = button_div; row.append(button_div); var inp = jQuery('<input/>',{type:'radio',name:'buttons'}); var label = jQuery('<label/>').click(function() {inp.prop('checked', true);}); button_div.click(function() { inp.prop('checked', true); a_outline.removeClass('selected opaque hilite'); b_outline.removeClass('selected opaque hilite'); c_outline.removeClass('selected opaque hilite'); a_fill.removeClass('selected opaque hilite'); b_fill.removeClass('selected opaque hilite'); c_fill.removeClass('selected opaque hilite'); ab_fill.removeClass('selected opaque hilite'); bc_fill.removeClass('selected opaque hilite'); ac_fill.removeClass('selected opaque hilite'); abc_fill.removeClass('selected opaque hilite'); s_outline.removeClass('selected opaque hilite'); $.each(button.opaque, function(i, opaque_element) { jQuery(opaque_element).addClass('opaque'); }); $.each(button.filled, function(i, fill_element) { jQuery(fill_element).addClass('selected'); }); $.each(button.hilite, function(i, fill_element) { jQuery(fill_element).addClass('hilite'); }); }); label.html(button.label); var prob_span = jQuery('<div/>'); label.append(prob_span); button.p = function(p) { if (isNaN(p)) p = 0; prob_span.text(' (P = ' + (p / s_outline.area() * 100).fix(1) + '%)'); }; button_div.append(inp); button_div.append(label); self.buttons[button.human_label] = button; return button; }); buttons_div.append(row); return button_row; }); function createPercentControl(letter, size) { var sb = jQuery('<div/>',{id:container_id + 'psb' + letter}).addClass('scrollbar'); var lbl = jQuery('<label/>').attr('for', container_id + 'sb'+letter); lbl.html('P(' + letter + ') (%)'); var idFunc1 = function() { $('#' + container_id + 'sb' + letter).slider('value', this.value); updateSizes(); }; var idFunc2 = function() { $('#' + container_id + 'sb' + letter + 't').val($(this).slider('value')); updateSizes(); }; var input = jQuery('<input/>', { type: 'text', id: container_id + 'sb'+letter+'t', change: idFunc1, value: size, size: 2 }); var input2 = jQuery('<span/>') .addClass('slider') .attr('id', container_id + 'sb' + letter) .slider({ change: idFunc2, slide: idFunc2, min: 1, max: 100, step: 1, value: size }); sb.append(lbl); sb.append(input); sb.append(input2); scrollbars.append(sb); } createPercentControl('A', 30); createPercentControl('B', 20); createPercentControl('C', 10); updateSizes(); function createBox() { var box = jQuery('<div/>').addClass('box'); box.area = function() { if (box.css('display') == 'none') return 0; if (a_outline.isSameBox(box)) return $('#' + container_id + 'sbA').slider('value') / 100 * s_outline.area(); if (b_outline.isSameBox(box)) return $('#' + container_id + 'sbB').slider('value') / 100 * s_outline.area(); if (c_outline.isSameBox(box)) return $('#' + container_id + 'sbC').slider('value') / 100 * s_outline.area(); return box.width() * box.height(); }; box.x = function() { return box.position().left; }; box.y = function() { return box.position().top; }; box.isSameBox = function(other) { return box.y() == other.y() && box.x() == other.x() && box.width() == other.width() && box.height() == other.height(); }; return box; } // Synchronizes fill positions so that the outline is always visible on the A // and B boxes, and the intersection area is synchronized. function syncPositions() { // Make sure everything stays in bounds. function bound(outline) { var x_offset = outline.offset().left + outline.width() - (s_outline.offset().left + s_outline.width()); if (x_offset > 0) outline.css('left', (outline.position().left - x_offset) + 'px'); var y_offset = outline.offset().top + outline.height() - (s_outline.offset().top + s_outline.height()); if (y_offset > 0) outline.css('top', (outline.position().top - y_offset) + 'px'); if (outline.position().left < s_outline.position().left) outline.css('left', s_outline.position().left + 'px'); if (outline.position().top < s_outline.position().top) outline.css('top', s_outline.position().top + 'px'); } function intersect(a, b, ab) { if (ab === undefined) { ab = createBox(); } var a_x = a.position().left; var a_y = a.position().top; var a_w = a.width(); var a_h = a.height(); var b_x = b.position().left; var b_y = b.position().top; var b_w = b.width(); var b_h = b.height(); if (a_x + a_w < b_x || b_x + b_w < a_x || a_y + a_h < b_y || b_y + b_h < a_y) { ab.css('display', 'none'); } else { ab.css('display', ''); var x1 = Math.max(a_x, b_x); var y1 = Math.max(a_y, b_y); ab.css('left', x1 + 'px'); ab.css('top', y1 + 'px'); var x2 = Math.min(a_x + a_w, b_x + b_w); var y2 = Math.min(a_y + a_h, b_y + b_h); ab.css('width', (x2 - x1) + 'px'); ab.css('height', (y2 - y1) + 'px'); } return ab; } bound(a_outline); bound(b_outline); bound(c_outline); // Synchronize the fills with the outlines. a_fill.css('left', a_outline.css('left')); a_fill.css('top', a_outline.css('top')); a_fill.css('width', a_outline.css('width')); a_fill.css('height', a_outline.css('height')); b_fill.css('left', b_outline.css('left')); b_fill.css('top', b_outline.css('top')); b_fill.css('width', b_outline.css('width')); b_fill.css('height', b_outline.css('height')); c_fill.css('left', c_outline.css('left')); c_fill.css('top', c_outline.css('top')); c_fill.css('width', c_outline.css('width')); c_fill.css('height', c_outline.css('height')); // Calculate the intersection. intersect(a_outline, b_outline, ab_fill); intersect(b_outline, c_outline, bc_fill); intersect(a_outline, c_outline, ac_fill); intersect(ab_fill, bc_fill, abc_fill); if (self.options.showProbabilities) { self.buttons['A'].p(a_outline.area()); self.buttons['B'].p(b_outline.area()); self.buttons['C'].p(c_outline.area()); self.buttons['Ac'].p(s_outline.area() - a_outline.area()); self.buttons['Bc'].p(s_outline.area() - b_outline.area()); self.buttons['Cc'].p(s_outline.area() - c_outline.area()); self.buttons['AB'].p(ab_fill.area()); self.buttons['AC'].p(ac_fill.area()); self.buttons['BC'].p(bc_fill.area()); self.buttons['A or B'].p( a_outline.area() + b_outline.area() - ab_fill.area()); self.buttons['A or C'].p( a_outline.area() + c_outline.area() - ac_fill.area()); self.buttons['B or C'].p( b_outline.area() + c_outline.area() - bc_fill.area()); self.buttons['ABC'].p(abc_fill.area()); self.buttons['A or B or C'].p( a_outline.area() + b_outline.area() + c_outline.area() - ab_fill.area() - bc_fill.area() - bc_fill.area() + abc_fill.area()); self.buttons['ABc'].p(a_outline.area() - ab_fill.area()); self.buttons['AcB'].p(b_outline.area() - ab_fill.area()); self.buttons['AcBC'].p(bc_fill.area() - abc_fill.area()); self.buttons['Ac or BC'].p( s_outline.area() - a_outline.area() + abc_fill.area()); self.buttons['S'].p(s_outline.area()); self.buttons['{}'].p(0); if (self.options.showConditional) { self.buttons['P(A|B)'].p( ab_fill.area() / b_outline.area() * s_outline.area()); self.buttons['P(Ac|B)'].p( s_outline.area() - ab_fill.area() / b_outline.area() * s_outline.area()); self.buttons['P(B|A)'].p( ab_fill.area() / a_outline.area() * s_outline.area()); self.buttons['P(A|BC)'].p( abc_fill.area() / bc_fill.area() * s_outline.area()); self.buttons['P(Ac|BC)'].p( s_outline.area() - abc_fill.area() / bc_fill.area() * s_outline.area()); self.buttons['P(A|(B or C))'].p( s_outline.area() * (ab_fill.area() + ac_fill.area() - abc_fill.area()) / (b_outline.area() + c_outline.area() - bc_fill.area())); } } } // Updates the sizes of the boxes according to their sliders. function updateSizes() { // ratio = width / height // area = width * height // width = area / height // width = ratio * height // width * width = area * ratio // height = area / width var ratio = s_outline.width() / s_outline.height(); var s_area = s_outline.width() * s_outline.height(); var a_p = $('#' + container_id + 'sbA').slider('value') / 100; var a_area = s_area * a_p; var a_width = Math.sqrt(a_area * ratio); var a_height = a_area / a_width; a_outline.width(a_width + 'px'); a_outline.height(a_height + 'px'); var b_p = $('#' + container_id + 'sbB').slider('value') / 100; var b_area = s_area * b_p; var b_width = Math.sqrt(b_area * ratio); var b_height = b_area / b_width; b_outline.width(b_width + 'px'); b_outline.height(b_height + 'px'); var c_p = $('#' + container_id + 'sbC').slider('value') / 100; var c_area = s_area * c_p; var c_width = Math.sqrt(c_area * ratio); var c_height = c_area / c_width; c_outline.width(c_width + 'px'); c_outline.height(c_height + 'px'); syncPositions(); } function draw() { // Initial sizes. These will get overwritten as soon as updateSizes is // called. var scaleFactorX = 0.3; var scaleFactorY = 0.3; s_outline = createBox().addClass('S'); s_outline.css('left', '10px'); s_outline.css('top', '10px'); s_outline.css('width', (self.container.width() - 20) + 'px'); s_outline.css('height', (self.container.height() - 20) + 'px'); s_outline.text('S'); self.container.append(s_outline); a_fill = createBox().addClass('A').addClass('fill'); a_outline = createBox().addClass('A').addClass('outline'); a_outline.css('left', (self.container.width() * 0.15) + 'px'); a_outline.css('top', (self.container.height() * 0.3) + 'px'); a_outline.css('width', (s_outline.width() * scaleFactorX) + 'px'); a_outline.css('height', (s_outline.height() * scaleFactorY) + 'px'); a_outline.text('A'); b_fill = createBox().addClass('B').addClass('fill'); b_outline = createBox().addClass('B').addClass('outline'); b_outline.css('left', (self.container.width() * 0.5) + 'px'); b_outline.css('top', (self.container.height() * 0.4) + 'px'); b_outline.css('width', (s_outline.width() * scaleFactorX) + 'px'); b_outline.css('height', (s_outline.height() * scaleFactorY) + 'px'); b_outline.text('B'); c_fill = createBox().addClass('C').addClass('fill'); c_outline = createBox().addClass('C').addClass('outline'); c_outline.css('left', (self.container.width() * 0.4) + 'px'); c_outline.css('top', (self.container.height() * 0.2) + 'px'); c_outline.css('width', (s_outline.width() * scaleFactorX) + 'px'); c_outline.css('height', (s_outline.height() * scaleFactorY) + 'px'); c_outline.text('C'); ab_fill = createBox().addClass('AB').addClass('fill'); ac_fill = createBox().addClass('AC').addClass('fill'); bc_fill = createBox().addClass('BC').addClass('fill'); abc_fill = createBox().addClass('BC').addClass('fill'); self.container.append(a_fill); self.container.append(b_fill); self.container.append(c_fill); self.container.append(ab_fill); self.container.append(ac_fill); self.container.append(bc_fill); self.container.append(abc_fill); self.container.append(a_outline); self.container.append(b_outline); self.container.append(c_outline); a_outline.draggable({ containment: s_outline, drag: syncPositions, stop: syncPositions }); b_outline.draggable({ containment: s_outline, drag: syncPositions, stop: syncPositions }); c_outline.draggable({ containment: s_outline, drag: syncPositions, stop: syncPositions }); } } // Author: James Eady <jeady@berkeley.edu> // // Searches the element's ancestry to make the element take up space (but still // not shown - i.e. temporarly change display: none to visibility: hidden), // execute func, and then set everything back the way it was. This means we can // correctly perform size calculations in func() regardless of whether the // element is meant to be visible now or later. function doWhileVisible(element, func) { var rehide = []; element.parents().each(function(i, e) { e = jQuery(e); if (e.is(':hidden')) { e.show(); rehide.push([e, e.css('visibility')]); e.css('visibility', 'hidden'); } }); func(); jQuery.each(rehide, function(i, e) { e[0].hide(); e[0].css('visibility', e[1]); }); } /* This file contains two functions: statCalc: a simple calculator distCalc: a calculator probability distributions copyright (c) 2013 by P.B. Stark last modified 27 January 2013. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero Public License for more details. http://www.gnu.org/licenses/ Dependencies: irGrade, jQuery, jQuery-ui */ function statCalc(container_id, params) { var self = this; this.container = $('#' + container_id); if (!params instanceof Object) { console.error('statCalc parameters should be an object'); return; } // default options this.options = { keys: [ [["7","num"],["8","num"],["9","num"],["/","bin"], ["nCk","bin"]], [["4","num"],["5","num"],["6","num"],["*","bin"], ["nPk","bin"]], [["1","num"],["2","num"],["3","num"],["-","bin"], ["!","una"]], [["0","num"],[".","num"],["+/-","una"],["+","bin"], ["1/x","una"]], [["=","eq"], ["CE","una"], ["C","una"], ["Sqrt","una"], ["x^2","una"]] ], buttonsPerRow: 5, /* Full set of keys: keys: [ [["7","num"],["8","num"],["9","num"],["/","bin"], ["nCk","bin"], ["nPk","bin"]], [["4","num"],["5","num"],["6","num"],["*","bin"], ["!","una"], ["U[0,1]","una"]], [["1","num"],["2","num"],["3","num"],["-","bin"], ["Sqrt","una"], ["x^2","una"]], [["0","num"],[".","num"],["+/-","una"],["+","bin"], ["1/x","una"], ["x^y","bin"]], [["=","eq"], ["CE","una"], ["C","una"], ["exp(x)","una"], ["log(x)","una"], ["log_y(x)", "bin"]] ], buttonsPerRow: 6 */ digits: 16 }; // Extend options $.extend(this.options, params); self.x = 0.0; self.inProgress = false; self.currentOp = null; function initCalc() { var me = $('<div />').addClass('calc'); self.container.append(me); // display self.theDisplay = $('<input type="text" />').attr('size',self.options.digits); me.append(self.theDisplay); // buttons self.buttonDiv = $('<div />').addClass('buttonDiv'); self.numButtonDiv = $('<div />').addClass('numButtonDiv'); self.fnButtonDiv = $('<div />').addClass('fnButtonDiv'); self.numButtonTable = $('<table />').addClass('numButtonTable'); self.fnButtonTable = $('<table />').addClass('fnButtonTable'); self.numButtonDiv.append(self.numButtonTable); self.fnButtonDiv.append(self.fnButtonTable); self.buttonDiv.append(self.numButtonDiv); self.buttonDiv.append(self.fnButtonDiv); $.each(self.options.keys, function(j, rowKeys) { var row = $('<tr>'); self.numButtonTable.append(row); $.each(rowKeys, function(i, v) { if (null === v) { row.append($('<td/>')); return; } newBut = $('<input type="button" value="' + v[0] + '">') .button() .addClass(v[1]) .addClass('calcButton') .click( function(e) { e.preventDefault(); buttonClick(v[0], v[1]); }); row.append($('<td/>').append(newBut)); }); }); me.append(self.buttonDiv); } initCalc(); // action functions function buttonClick(v, opType) { var t = self.theDisplay.val().replace(/[^0-9e.\-]+/gi,'').replace(/^0+/,''); try { switch(opType) { case 'num': self.theDisplay.val(t+v); break; case 'una': switch(v) { case '+/-': t = (t.indexOf('-') === 0) ? t.substring(1) : '-'+t; self.theDisplay.val(t); break; case '!': self.theDisplay.val(factorial(t).toString()); break; case 'Sqrt': self.theDisplay.val(Math.sqrt(t).toString()); break; case 'x^2': self.theDisplay.val((t*t).toString()); break; case 'exp(x)': self.theDisplay.val(Math.exp(t).toString()); break; case 'ln(x)': self.theDisplay.val(Math.log(t).toString()); break; case '1/x': self.theDisplay.val((1/t).toString()); break; case 'U[0,1]': self.theDisplay.val(rand.next()); break; case 'N(0,1)': self.theDisplay.val(rNorm()); break; case 'CE': self.theDisplay.val('0'); break; case 'C': self.x = 0; self.inProgress = false; self.currentOp = null; self.theDisplay.val('0'); } break; case 'bin': if (self.inProgress) { self.x = doBinaryOp(self.x, self.currentOp, t); self.theDisplay.val(self.x.toString()); } else { self.x = t; self.theDisplay.val('?'); self.inProgress = true; } self.currentOp = v; break; case 'eq': if (self.inProgress) { self.x = doBinaryOp(self.x, self.currentOp, t); self.theDisplay.val(self.x.toString()); self.inProgress = false; self.currentOp = null; } break; default: console.log('unexpected button in statCalc ' + v); } } catch(e) { console.log(e); self.theDisplay.val('NaN'); } } function doBinaryOp(x, op, y) { var res = Math.NaN; try { switch(op) { case '+': res = parseFloat(x)+parseFloat(y); break; case '-': res = parseFloat(x)-parseFloat(y); break; case '*': res = parseFloat(x)*parseFloat(y); break; case '/': res = parseFloat(x)/parseFloat(y); break; case 'x^y': res = parseFloat(x)^parseFloat(y); break; case 'nCk': res = binomialCoef(parseFloat(x), parseFloat(y)); break; case 'nPk': res = permutations(parseFloat(x), parseFloat(y)); break; default: console.log('unexpected binary function in statCalc ' + op); } } catch(e) { } return(res); } } function distCalc(container_id, params) { var self = this; this.container = $('#' + container_id); if (!params instanceof Object) { console.error('distCalc parameters should be an object'); return; } // default options this.options = { distributions: [ ["Binomial", ["n","p"]], ["Geometric", ["p"]], ["Negative Binomial", ["p","r"]], ["Hypergeometric",["N","G","n"]], ["Normal", ["mean","SD"]], ["Student t", ["degrees of freedom"]], ["Chi-square", ["degrees of freedom"]], ["Exponential", ["mean"]], ["Poisson", ["mean"]] ], digits: 8, paramDigits: 4, showExpect: true }; // Extend options $.extend(this.options, params); self.lo = 0.0; self.hi = 0.0; self.currDist = null; self.distDivs = []; function init() { var me = $('<div />').addClass('distCalc'); self.container.append(me); // distribution selection self.selectDiv = $('<div />').addClass('selectDiv').append('If X has a '); self.selectDist = $('<select />').change(function() { changeDist($(this).val()); }); // parameters of the distributions $.each(self.options.distributions, function(i, v) { $('<option/>', { value : v[0] }).text(v[0]).appendTo(self.selectDist); self.distDivs[v[0]] = $('<div />').css('display','inline'); $.each(v[1], function(j, parm) { self.distDivs[v[0]].append(parm + ' = ') .append( $('<input type="text" />') .attr('size','paramDigits') .addClass(parm.replace(/ +/g,'_')) .blur(calcProb) ) .append(', '); }); }); self.paramSpan = $('<span />').addClass('paramSpan'); self.selectDiv.append(self.selectDist) .append('distribution with ') .append(self.paramSpan); // start with the first listed distribution self.currDist = self.options.distributions[0][0]; self.paramSpan.append(self.distDivs[self.currDist]); // range over which to find the probability self.selectDiv.append(' the chance that ') .append($('<input type="checkbox">').addClass('useLower').click(calcProb)) .append('X ≥') .append($('<input type="text" />').addClass('loLim').attr('size',self.options.digits).val("0").blur(calcProb)) .append($('<input type="checkbox">').addClass('useUpper').click(calcProb)) .append('and X ≤') .append($('<input type="text" />').addClass('hiLim').attr('size',self.options.digits).val("0").blur(calcProb)) .append(' is '); // display self.theDisplay = $('<input type="text" readonly />').attr('size',self.options.digits+4); self.expectSpan = $('<span />').addClass('expectSpan'); self.selectDiv.append(self.theDisplay).append(self.expectSpan); me.append(self.selectDiv); } init(); // action functions function changeDist(dist) { self.currDist = dist; self.paramSpan.empty(); self.expectSpan.empty(); var thisDist = $.grep(self.options.distributions, function(v,i) { return(v[0] === dist); }); self.paramSpan.append(self.distDivs[thisDist[0][0]]); // replace with parameters for current distribution self.theDisplay.val('NaN'); calcProb(); } function calcProb(e) { if (typeof(e) != 'undefined' && e instanceof jQuery.Event) e.preventDefault(); var prob = Number.NaN; // get range over which to compute the probability var loCk = self.selectDiv.find('.useLower').prop('checked'); var loLim = loCk ? parseFloat(self.selectDiv.find('.loLim').val()) : Number.NaN; var hiCk = self.selectDiv.find('.useUpper').prop('checked'); var hiLim = hiCk ? parseFloat(self.selectDiv.find('.hiLim').val()) : Number.NaN; var allParamsDefined = true; $.each(self.distDivs[self.currDist].find(':input'), function(i, v) { if (v.value.trim().length === 0) { allParamsDefined = false; } }); if ((!loCk && !hiCk) || !allParamsDefined) { prob = Number.NaN; } else if (loCk && hiCk && (loLim > hiLim)) { prob = 0.0; } else { self.expectSpan.empty(); var n; var p; var t; var b; var df; var m; switch(self.currDist) { case "Binomial": n = parseFloat(self.distDivs[self.currDist].find('.n').val()); p = parsePercent(self.distDivs[self.currDist].find('.p').val()); t = hiCk ? binomialCdf(n, p, hiLim) : 1.0; b = loCk ? binomialCdf(n, p, loLim-1) : 0.0; prob = t - b; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + (n*p).toFixed(self.options.paramDigits) + '; SE(X) = ' + Math.sqrt(n*p*(1-p)).toFixed(self.options.paramDigits)); } break; case "Geometric": p = parsePercent(self.distDivs[self.currDist].find('.p').val()); t = hiCk ? geoCdf(p, hiLim) : 1.0; b = loCk ? geoCdf(p, loLim-1) : 0.0; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + (1/p).toFixed(self.options.paramDigits) + '; SE(X) = ' + (Math.sqrt(1-p)/p).toFixed(self.options.paramDigits)); } prob = t - b; break; case "Negative Binomial": p = parsePercent(self.distDivs[self.currDist].find('.p').val()); r = parseFloat(self.distDivs[self.currDist].find('.r').val()); t = hiCk ? negBinomialCdf( p, r, hiLim) : 1.0; b = loCk ? negBinomialCdf( p, r, loLim-1) : 0.0; prob = t - b; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + (r/p).toFixed(self.options.paramDigits) + '; SE(X) = ' + (Math.sqrt(r*(1-p))/p).toFixed(self.options.paramDigits)); } break; case "Hypergeometric": var N = parseFloat(self.distDivs[self.currDist].find('.N').val()); var G = parseFloat(self.distDivs[self.currDist].find('.G').val()); n = parseFloat(self.distDivs[self.currDist].find('.n').val()); t = hiCk ? hyperGeoCdf(N, G, n, hiLim) : 1.0; b = loCk ? hyperGeoCdf(N, G, n, loLim-1) : 0.0; prob = t - b; var hyP = G/N; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + (n*hyP).toFixed(self.options.paramDigits) + '; SE(X) = ' + (Math.sqrt(n*hyP*(1-hyP)*(N-n)/(N-1))).toFixed(self.options.paramDigits)); } break; case "Normal": m = parseFloat(self.distDivs[self.currDist].find('.mean').val()); var s = parseFloat(self.distDivs[self.currDist].find('.SD').val()); t = hiCk ? normCdf((hiLim-m)/s) : 1.0; b = loCk ? normCdf((loLim-m)/s) : 0.0; prob = t - b; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + m.toFixed(self.options.paramDigits) + '; SE(X) = ' + s.toFixed(self.options.paramDigits)); } break; case "Student t": df = parseFloat(self.distDivs[self.currDist].find('.degrees_of_freedom').val()); t = hiCk ? tCdf(df, hiLim) : 1.0; b = loCk ? tCdf(df, loLim) : 0.0; prob = t - b; var se = (df > 2) ? (Math.sqrt(df/(df-2))).toFixed(self.options.paramDigits) : Number.NaN; if (self.options.showExpect) { self.expectSpan.append('E(X) = 0; SE(X) = ' + se); } break; case "Chi-square": df = parseFloat(self.distDivs[self.currDist].find('.degrees_of_freedom').val()); t = hiCk ? chi2Cdf(df, hiLim) : 1.0; b = loCk ? chi2Cdf(df, loLim) : 0.0; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + df.toFixed(self.options.paramDigits) + '; SE(X) = ' + (Math.sqrt(2*df)).toFixed(self.options.paramDigits)); } prob = t - b; break; case "Exponential": m = parseFloat(self.distDivs[self.currDist].find('.mean').val()); t = hiCk ? expCdf(m, hiLim) : 1.0; b = loCk ? expCdf(m, loLim) : 0.0; prob = t - b; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + m.toFixed(self.options.paramDigits) + '; SE(X) = ' + m.toFixed(self.options.paramDigits)); } break; case "Poisson": m = parseFloat(self.distDivs[self.currDist].find('.mean').val()); t = hiCk ? poissonCdf(m, hiLim) : 1.0; b = loCk ? poissonCdf(m, loLim) : 0.0; prob = t - b; if (self.options.showExpect) { self.expectSpan.append('E(X) = ' + m.toFixed(self.options.paramDigits) + '; SE(X) = ' + (Math.sqrt(m)).toFixed(self.options.paramDigits)); } break; default: console.log('unexpected distribution in distCalc.calcProb ' + dist); } } self.theDisplay.val((100*prob).toFixed(self.options.digits-3)+'%'); } } /* script stat_utils: statistical functions, vector functions, linear algebra copyright (c) 1997-2013. P.B. Stark, statistics.berkeley.edu/~stark Version 1.0 This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. See <http://www.gnu.org/licenses/> */ // !!!!Beginning of the code!!!! Array.prototype.max = function() { var max = -Infinity; for(var i = 0; i < this.length; i++) { max = Math.max(max, this[i]); } return max; }; Array.prototype.min = function() { var min = Infinity; for(var i = 0; i < this.length; i++) { min = Math.min(min, this[i]); } return min; }; Number.prototype.fix = function(n) { return parseFloat(this.toFixed(n)); }; Number.prototype.pct = function() { return (100 * this).fix(2) + '%'; }; // Includes lo, excludes hi. function range(lo, hi) { var seq = []; for (var i = lo; i < hi; i++) seq.push(i); return seq; } var rmin = 2.3e-308; var eps = 2.3e-16; // ============================================================================ // ========================= STATISTICAL SUBROUTINES ========================== function mean(list, n) { // computes the mean of the list if (n !== undefined) list = list.slice(0, n); return vSum(list)/list.length; } function vMult(a, list) { // multiply a vector times a scalar var list2 = new Array(list.length); for (var i=0; i < list.length; i++) { list2[i] = a*list[i]; } return list2; } function vScalarSum(list, scalar) { // adds the scalar to every component of the list var vs = new Array(list.length); for (var i =0; i < list.length; i++) { vs[i] = list[i] + scalar; } return vs; } function vVectorSum(list1, list2) { // vector addition if (list1.length != list2.length) { alert('Error #1 in irGrade.vVectorSum: vector lengths are not equal'); return Math.NaN; } else { var vs = new Array(length(list1)); for (var i =0; i < list1.length; i++) { vs[i] = list1[i] + list2[i]; } return vs; } } function vPointwiseMult(list1, list2) { // componentwise multiplication of two vectors var list3 = Math.NaN; if (list1.length != list2.length) { alert('Error #1 in irGrade.vPointwiseMult: vector lengths do not match!'); } else { list3 = new Array(list1.length); for (var i=0; i < list1.length; i++) { list3[i] = list1[i]*list2[i]; } } return list3; } function vFloor(list) { // takes floor of all components var list2 = new Array(list.length); for (var i = 0; i < list.length; i++) { list2[i] = Math.floor(list[i]); } return list2; } function vCeil(list) { // takes ceil of all components var list2 = new Array(list.length); for (var i = 0; i < list.length; i++) { list2[i] = Math.ceil(list[i]); } return list2; } function vRoundToInts(list) { // round all components to the nearest int var list2 = new Array(list.length); var tmp; for (var i = 0; i < list.length; i++) { list2[i] = Math.floor(list[i]); if (list[i] - list2[i] >= 0.5) { list2[i]++; } } return list2; } function vSum(list) { // computes the sum of the elements of list var tot = 0.0; for (var i = 0; i < list.length; i++) { tot += list[i]; } return tot; } function vProd(list) { // computes the product of the elements of list var p = 1.0; for (var i = 0; i < list.length; i++) { p *= list[i]; } return p; } function vCum(list) { // vector of cumulative sum var list2 = list; for (var i = 1; i < list.length; i++ ) { list2[i] += list2[i-1]; } return list2; } function vDiff(list) { // vector of differences; 1st element unchanged var list2 = new Array(list.length); for (var i = list.length-1; i > 0; i-- ) { list2[i] = list[i] - list[i-1]; } list2[0] = list[0]; return list2; } function vInterval(list) { // vector of differences between successive elements; 0 subtracted from first element var list2 = []; list2[0] = list[0]; for (var i = 1; i < list.length; i++) { list2[i] = list[i] - list[i-1]; } return list2; } function vZero(n) { // returns a vector of zeros of length n var list = new Array(n); for (var i=0; i < n; i++) { list[i] = 0.0; } return list; } function vOne(n) { // returns a vector of ones of length n var list = new Array(n); for (var i=0; i < n; i++) { list[i] = 1.0; } return list; } function twoNorm(list) { // two norm of a vector var tn = 0.0; for (var i=0; i < list.length; i++) { tn += list[i]*list[i]; } return Math.sqrt(tn); } function convolve(a,b) { // convolve two lists var c = new Array(a.length + b.length - 1); var left; var right; for (var i=0; i < a.length + b.length - 1; i++) { c[i] = 0; right = Math.min(i+1, a.length); left = Math.max(0, i - b.length + 1); for (var j=left; j < right; j++) { c[i] += a[j]*b[b.length - i - 1 + j]; } } return c; } function nFoldConvolve(a,n) { var b = a; for (var i=0; i < n; i++ ) { b = convolve(b,a); } return b; } function numberLessThan(a,b) { // numerical ordering for javascript sort function var diff = parseFloat(a)-parseFloat(b); if (diff < 0) { return -1; } else if (diff === 0) { return 0; } else { return 1; } } function numberGreaterThan(a,b) { // numerical ordering for javascript sort function var diff = parseFloat(a)-parseFloat(b); if (diff < 0) { return 1; } else if (diff === 0) { return 0; } else { return -1; } } function sd(list, n) { // computes the SD of the list if (n !== undefined) list = list.slice(0, n); ave = mean(list); ssq = 0; for (var i = 0; i < list.length; i++) { ssq += (list[i] - ave)*(list[i] - ave); } ssq = Math.sqrt(ssq/list.length); return ssq; } function sampleSd(list) { // computes the sample SD of the list ave = mean(list); ssq = 0; for (var i = 0; i < list.length; i++) { ssq += (list[i] - ave)*(list[i] - ave); } ssq = Math.sqrt(ssq/(list.length - 1.0)); return ssq; } function corr(list1, list2) { // computes the correlation coefficient of list1 and list2 if (list1.length != list2.length) { alert('Error #1 in irGrade.corr(): lists have different lengths!'); return Math.NaN; } else { var ave1 = mean(list1); var ave2 = mean(list2); var sd1 = sd(list1); var sd2 = sd(list2); var cc = 0.0; for (var i=0; i < list1.length; i++) { cc += (list1[i] - ave1)*(list2[i] - ave2); } cc /= sd1*sd2*list1.length; return cc; } } function percentile(list,p) { // finds the pth percentile of list var n = list.length; var sList = list.slice(0); sList.sort(numberLessThan); var ppt = Math.max(Math.ceil(p*n/100),1); return sList[ppt-1]; } function histMakeCounts(binEnd, data, fulllength) { // makes vector of histogram heights if (!fulllength) { fulllength = data.length; } var nBins = binEnd.length - 1; var counts = new Array(nBins); var i = 0; for (i=0; i < nBins; i++) { counts[i] = 0; } for (i=0; i < data.length; i++) { for (var k=0; k < nBins - 1; k++) { if (data[i] >= binEnd[k] && data[i] < binEnd[k+1] ) { counts[k] += 1; } } if (data[i] >= binEnd[nBins - 1] ) { counts[nBins - 1] += 1; } } for (i=0; i < nBins; i++) { counts[i] /= fulllength*(binEnd[i+1]-binEnd[i]); } return counts; } function histEstimatedPercentile(pct, binEnd, counts) { // estimates the pth percentile from a histogram var p = pct/100.0; var pctile; if (p > 1.0) { pctile = Math.NaN; } else if (p == 1.0) { pctile = binEnd[nBins]; } else { var area = 0.0; var j = 0; while (area < p) { j++; area = histHiLitArea(binEnd[0], binEnd[j], binEnd, counts); } j--; area = p - histHiLitArea(binEnd[0], binEnd[j], binEnd, counts); var nextBinArea = histHiLitArea(binEnd[j], binEnd[j+1], binEnd, counts); pctile = binEnd[j] + (area/nextBinArea) * (binEnd[j+1] - binEnd[j]); } return pctile; } function histHiLitArea(loEnd, hiEnd, binEnd, counts) { // area of counts from loEnd to hiEnd var nBins = binEnd.length - 1; var area = 0; if (loEnd < hiEnd) { for (var i=0; i < nBins; i++) { if( binEnd[i] > hiEnd || binEnd[i+1] <= loEnd) { } else if (binEnd[i] >= loEnd && binEnd[i+1] <= hiEnd) { area += counts[i]*(binEnd[i+1]-binEnd[i]); } else if (binEnd[i] >= loEnd && binEnd[i+1] > hiEnd) { area += counts[i]*(hiEnd - binEnd[i]); } else if (binEnd[i] <= loEnd && binEnd[i+1] <= hiEnd) { area += counts[i]*(binEnd[i+1]-loEnd); } else if (binEnd[i] < loEnd && binEnd[i+1] > hiEnd) { area += counts[i]*(hiEnd - loEnd); } } } return area; } function normHiLitArea(loEnd, hiEnd, sampleSize, nPop, EX, SE, replacement) { var area = 0; var fpc = 1.0; if (!replacement) { fpc = Math.sqrt( (nPop - sampleSize + 0.0)/(nPop-1.0)); } if (hiEnd > loEnd) { area = normCdf((hiEnd - EX)/(fpc*SE)) - normCdf((loEnd - EX)/(fpc*SE)); } return area; } function chiHiLitArea(loEnd, hiEnd, sampleSize, pop, popSd, sampleType) { var area = 0; if (hiEnd > loEnd) { if (varChoice.getSelectedItem().equals("Sample S-Squared")) { var scale = (sampleSize - 1.0)/(popSd*popSd); area = chi2Cdf(scale*hiEnd, sampleSize-1) - chi2Cdf(scale*loEnd, sampleSize-1); } else if (varChoice.getSelectedItem().equals("Sample Chi-Squared")) { area = chi2Cdf(hiEnd, pop.length-1) - chi2Cdf(loEnd, pop.length-1); } else { console.log("Error in SampleDist.chiHiLitArea(): " + varChoice.getSelectedItem() + " not supported. "); area = 0.0; } } return area; } function tHiLitArea(loEnd, hiEnd) { var area = 0; if (hiEnd > loEnd) { if (varChoice.getSelectedItem().equals("Sample t")) { area = tCdf(hiEnd, sampleSize-1) - tCdf(loEnd, sampleSize-1); } else { console.log("Error in SampleDist.tHiLitArea(): " + varChoice.getSelectedItem() + " not supported. "); area = 0.0; } } return area; } function histSetBins(nBins, xMin, xMax) { var binEnd = new Array(nBins+1); for (var i=0; i < nBins+1; i++) { binEnd[i] = xMin + i*(xMax - xMin)/nBins; } return binEnd; } function histMakeBins(nBins, data) { // makes equispaced histogram bins that span the range of data binEnd = new Array(nBins+1); dMnMx = vMinMax(data); for (var i=0; i < nBins+1; i++) { binEnd[i] = dMnMx[0] + i*(dMnMx[1] - dMnMx[0])/nBins; } return binEnd; } function listToHist(list, binEnd, nBins, lim) { // lim is an optional paramater, defaulting to list.length if not set if (lim === undefined) lim = list.length; c = new Array(nBins); var i; for (i = 0; i < nBins; i++) { c[i] = 0; } var mass = 1.0/lim; for (i = 0; i < lim; i++) { for (var j = 0; j < nBins - 1; j++) { if (list[i] >= binEnd[j] && list[i] < binEnd[j+1]) { c[j] += mass/(binEnd[j+1] - binEnd[j]); } } if (list[i] >= binEnd[nBins - 1] && list[i] <= binEnd[nBins]) { c[nBins-1] += mass/(binEnd[nBins] - binEnd[nBins - 1]); } } return c; } function listOfRandSigns(n) { // random +-1 vector var list = new Array(n); for (var i=0; i < n; i++) { var rn = rand.next(); if (rn < 0.5) { list[i] = -1; } else { list[i] = 1; } } return list; } function listOfRandUniforms(n, lo, hi) { // n random variables uniform on (lo, hi) if ( (typeof(lo) == 'undefined') || (lo === null) ) { lo = 0.0; } if ( (typeof(hi) == 'undefined') || (hi === null) ) { hi = 1.0; } var list = new Array(n); for (var i=0; i < n; i++) { list[i] = lo + (hi-lo)*rand.next(); } return list; } function listOfRandInts(n, lo, hi) { // n random integers between lo and hi var list = new Array(n); for (var i=0; i < n; i++) { list[i] = Math.floor((hi+1 - lo)*rand.next()) + lo; } return list; } function listOfDistinctRandInts(n, lo, hi) { // n dintinct random integers between lo and hi var list = new Array(n); var trial; var i=0; var unique; while (i < n) { trial = Math.floor((hi+1 - lo)*rand.next()) + lo; unique = true; for (var j = 0; j < i; j++) { if (trial == list[j]) unique = false; } if (unique) { list[i] = trial; i++; } } return list; } function randomSample(list, ssize, replace) { // sample from list, size ssize w/ or w/o replacement. // default is without replacement var sample = []; var indices = []; if (replace !== null && replace ) { indices = listOfRandInts(ssize,0,list.length-1); } else { indices = listOfDistinctRandInts(ssize,0,list.length - 1); } for (var i=0; i < ssize; i++) { sample[i] = list[indices[i]]; } return sample ; } function randomPartition(list, n) { // randomly partition list into n nonempty groups var bars = listOfDistinctRandInts(n-1, 1, list.length-1).sort(numberLessThan); bars[bars.length] = list.length; var parts = new Array(n); var i = 0; for (i = 0; i < parts.length; i++) { parts[i] = []; } for (i=0; i < bars[0]; i++) { parts[0][i] = list[i]; } for (var j=1; j < n; j++) { for (i=0; i < bars[j]-bars[j-1]; i++) { parts[j][i] = list[bars[j-1]+i]; } } return parts; } function constrainedRandomPartition(list, n, mx) { // randomly partition list into n nonempty groups no bigger than mx if (n*mx < list.length) { alert('Error in irGrade.constrainedRandomPartition: mx too small!'); return false; } else { var bars = listOfDistinctRandInts(n-1, 1, list.length-1).sort(numberLessThan); bars[bars.length] = list.length; vm = vMinMax(vDiff(bars))[1]; while (vm > mx) { bars = listOfDistinctRandInts(n-1, 1, list.length-1).sort(numberLessThan); bars[bars.length] = list.length; vm = vMinMax(vDiff(bars))[1]; } var parts = new Array(n); var i = 0; for (i = 0; i < parts.length; i++) { parts[i] = []; } for (i=0; i < bars[0]; i++) { parts[0][i] = list[i]; } for (var j=1; j < n; j++) { for (i=0; i < bars[j]-bars[j-1]; i++) { parts[j][i] = list[bars[j-1]+i]; } } return parts; } } function multinomialSample(pVec, n) { // multinomial sample of size n with probabilities pVec pVec = vMult(1.0/vSum(pVec), pVec); // renormalize in case var pCum = vCum(pVec); var counts = vZero(pVec.length); var rv; var inx; for (var i=0; i < n; i++) { rv = rand.next(); inx = 0; while ( (rv > pCum[inx]) && (inx < n) ) { inx++; } counts[inx]++; } return counts; } function normPoints(n, mu, s, dig) { // n normals with expected value mu, sd s, rounded to dig var round = true; if ( (typeof(dig) == 'undefined') || (dig === null) ) { round = false; } var xVal = new Array(n); var i = 0; if (round) { for (i=0; i < n; i++) { xVal[i] = roundToDig(mu + s*rNorm(),dig); } } else { for (i=0; i < n; i++) { xVal[i] = mu + s*rNorm(); } } return xVal; } function cNormPoints(n, r) { // generate n pseudorandom normal bivariates w/ specified realized correlation coefficient r // if n = 1, this is impossible; if n=2, this is possible only if r \in {-1, 0, 1} var xVal = new Array(n); var yVal = new Array(n); var i; if (n == 2) { // only three possible values of r if (r == -1) { xVal[0] = -10; xVal[1] = 10; yVal[0] = 10; yVal[1] = -10; } else if (r === 0) { xVal[0] = -10; xVal[1] = 10; yVal[0] = 0; yVal[1] = 0; } else if (r == 1) { xVal[0] = -10; xVal[1] = 10; yVal[0] = -10; yVal[1] = 10; } else { xVal[0] = Number.NaN; yVal[0] = Number.NaN; } } else if (n > 2) { // anything should be possible for (i=0; i<n ; i++ ) { xVal[i]= rNorm(); yVal[i] = rNorm(); } var rAtt = corr(xVal, yVal); var s = sgn(rAtt)*sgn(r); var xBarAtt = mean(xVal); var yBarAtt = mean(yVal); var xSdAtt = sd(xVal); var ySdAtt = sd(yVal); var pred = new Array(n); var resid = new Array(n); for (i=0; i < n; i++) { xVal[i] = (xVal[i] - xBarAtt)/xSdAtt; pred[i] = s*rAtt*xVal[i]*ySdAtt+ yBarAtt; resid[i] = s*yVal[i] - pred[i]; } var resNrm = rms(resid); for (i = 0; i < n; i++) { yVal[i] = Math.sqrt(1.0-r*r)*resid[i]/resNrm + r*xVal[i]; } var ymnmx = vMinMax(yVal); var xmnmx = vMinMax(xVal); var xscl = 8.5/(xmnmx[1] - xmnmx[0]); var yscl = 8.5/(ymnmx[1] - ymnmx[0]); for (i=0; i < n; i++) { xVal[i] = (xVal[i] - xmnmx[0]) * xscl + 1.0; yVal[i] = (yVal[i] - ymnmx[0]) * yscl + 1.0; } } else { // n = 1 is nonsense xVal[0] = Number.NaN; yVal[0] = Number.NaN; } var lists = new Array(xVal,yVal); return lists; }// ends cNormPoints function listOfRandReals(n,lo,hi) { // n-vector of uniforms on [lo, hi] var list = new Array(n); for (var i=0; i < n; i++) { list[i] = (hi - lo)*rand.next() + lo; } return list; } function rNorm() { // standard normal pseudorandom variable var y = normInv(rand.next()); return y; } // ends rNorm() function normCdf(y) { // normal distribution cumulative distribution function return 0.5*erfc(-y*0.7071067811865475); } function erfc(x) { // error function var xbreak = 0.46875; // for normal cdf // coefficients for |x| <= 0.46875 var a = [3.16112374387056560e00, 1.13864154151050156e02, 3.77485237685302021e02, 3.20937758913846947e03, 1.85777706184603153e-1]; var b = [2.36012909523441209e01, 2.44024637934444173e02, 1.28261652607737228e03, 2.84423683343917062e03]; // coefficients for 0.46875 <= |x| <= 4.0 var c = [5.64188496988670089e-1, 8.88314979438837594e00, 6.61191906371416295e01, 2.98635138197400131e02, 8.81952221241769090e02, 1.71204761263407058e03, 2.05107837782607147e03, 1.23033935479799725e03, 2.15311535474403846e-8]; var d = [1.57449261107098347e01, 1.17693950891312499e02, 5.37181101862009858e02, 1.62138957456669019e03, 3.29079923573345963e03, 4.36261909014324716e03, 3.43936767414372164e03, 1.23033935480374942e03]; // coefficients for |x| > 4.0 var p = [3.05326634961232344e-1, 3.60344899949804439e-1, 1.25781726111229246e-1, 1.60837851487422766e-2, 6.58749161529837803e-4, 1.63153871373020978e-2]; var q = [2.56852019228982242e00, 1.87295284992346047e00, 5.27905102951428412e-1, 6.05183413124413191e-2, 2.33520497626869185e-3]; var y, z, xnum, xden, result, del; /* Translation of a FORTRAN program by W. J. Cody, Argonne National Laboratory, NETLIB/SPECFUN, March 19, 1990. The main computation evaluates near-minimax approximations from "Rational Chebyshev approximations for the error function" by W. J. Cody, Math. Comp., 1969, PP. 631-638. */ // evaluate erf for |x| <= 0.46875 var i = 0; if(Math.abs(x) <= xbreak) { y = Math.abs(x); z = y * y; xnum = a[4]*z; xden = z; for (i = 0; i< 3; i++) { xnum = (xnum + a[i]) * z; xden = (xden + b[i]) * z; } result = 1.0 - x* (xnum + a[3])/ (xden + b[3]); } else if (Math.abs(x) <= 4.0) { y = Math.abs(x); xnum = c[8]*y; xden = y; for (i = 0; i < 7; i++) { xnum = (xnum + c[i])* y; xden = (xden + d[i])* y; } result = (xnum + c[7])/(xden + d[7]); if (y > 0.0) { z = Math.floor(y*16)/16.0; } else { z = Math.ceil(y*16)/16.0; } del = (y-z)*(y+z); result = Math.exp(-z*z) * Math.exp(-del)* result; } else { y = Math.abs(x); z = 1.0 / (y*y); xnum = p[5]*z; xden = z; for (i = 0; i < 4; i++) { xnum = (xnum + p[i])* z; xden = (xden + q[i])* z; } result = z * (xnum + p[4]) / (xden + q[4]); result = (1.0/Math.sqrt(Math.PI) - result)/y; if (y > 0.0) { z = Math.floor(y*16)/16.0; } else { z = Math.ceil(y*16)/16.0; } del = (y-z)*(y+z); result = Math.exp(-z*z) * Math.exp(-del) * result; } if (x < -xbreak) { result = 2.0 - result; } return result; } function normInv(p) { if ( p === 0.0 ) { return Math.NEGATIVE_INFINITY; } else if ( p >= 1.0 ) { return Math.POSITIVE_INFINITY; } else { return Math.sqrt(2.0) * erfInv(2*p - 1); } } function erfInv(y) { var a = [ 0.886226899, -1.645349621, 0.914624893, -0.140543331]; var b = [-2.118377725, 1.442710462, -0.329097515, 0.012229801]; var c = [-1.970840454, -1.624906493, 3.429567803, 1.641345311]; var d = [ 3.543889200, 1.637067800]; var y0 = 0.7; var x = 0; var z = 0; if (Math.abs(y) <= y0) { z = y*y; x = y * (((a[3]*z+a[2])*z+a[1])*z+a[0])/ ((((b[3]*z+b[2])*z+b[1])*z+b[0])*z+1.0); } else if (y > y0 && y < 1.0) { z = Math.sqrt(-Math.log((1-y)/2)); x = (((c[3]*z+c[2])*z+c[1])*z+c[0]) / ((d[1]*z+d[0])*z+1); } else if (y < -y0 && y > -1) { z = Math.sqrt(-Math.log((1+y)/2)); x = -(((c[3]*z+c[2])*z+c[1])*z+c[0])/ ((d[1]*z+d[0])*z+1); } x = x - (1.0 - erfc(x) - y) / (2/Math.sqrt(Math.PI) * Math.exp(-x*x)); x = x - (1.0 - erfc(x) - y) / (2/Math.sqrt(Math.PI) * Math.exp(-x*x)); return x; } // ends erfInv function betaCdf( x, a, b) { if (a <= 0 || b <= 0) { return Math.NaN; } else if (x >= 1) { return 1.0; } else if ( x > 0.0) { return Math.min(incBeta(x ,a ,b),1.0); } else { return 0.0; } } function betaPdf( x, a, b) { if (a <= 0 || b <= 0 || x < 0 || x > 1) { return Math.NaN; } else if ((x === 0 && a < 1) || (x == 2 && b < 1)) { return Math.POSITIVE_INFINITY; } else if (!(a <= 0 || b <= 0 || x <= 0 || x >= 1)) { return Math.exp((a - 1)*Math.log(x) + (b-1)*Math.log(1 - x) - lnBeta(a,b)); } else { return 0.0; } } function lnBeta( x, y) { return lnGamma(x) + lnGamma(y) - lnGamma(x+y); } function betaInv( p, a, b) { if (p < 0 || p > 1 || a <= 0 || b <= 0) { return Math.NaN; } else if ( p === 0 ) { return Math.NEGATIVE_INFINITY; } else if ( p == 1) { return Math.POSITIVE_INFINITY; } else { var maxIt = 100; var it = 0; var tol = Math.sqrt(eps); var work = 1.0; var next; var x; if (a === 0.0 ) { x = Math.sqrt(eps); } else if ( b === 0.0) { x = 1 - Math.sqrt(eps); } else { x = a/(a+b); } while (Math.abs(work) > tol*Math.abs(x) && Math.abs(work) > tol && it < maxIt) { it++; work = (betaCdf(x,a,b) - p)/betaPdf(x,a,b); next = x - work; while (next < 0 || next > 1) { work = work/2; next = x - work; } x = next; } return x; } } function gammaPdf(x, a, b) { var ans = Math.NaN; if (a <= 0 || b <= 0) { ans = Math.NaN; } else if (x > 0) { ans = Math.exp((a-1)*Math.log(x)-(x/b)-lnGamma(a)-a*Math.log(b)); } else if (x === 0 && a < 1) { ans = Math.POSITIVE_INFINITY; } else if (x === 0 && a == 1) { ans = 1/b; } return ans; } function lnGamma(x) { /* natural ln(gamma(x)) without computing gamma(x) P.B. Stark JavaScript subroutine is based on a MATLAB program by C. Moler, in turn based on a FORTRAN program by W. J. Cody, Argonne National Laboratory, NETLIB/SPECFUN, June 16, 1988. References: 1) W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203. 2) K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969. 3) Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968. */ var d1 = -5.772156649015328605195174e-1; var p1 = [4.945235359296727046734888e0, 2.018112620856775083915565e2, 2.290838373831346393026739e3, 1.131967205903380828685045e4, 2.855724635671635335736389e4, 3.848496228443793359990269e4, 2.637748787624195437963534e4, 7.225813979700288197698961e3]; var q1 = [6.748212550303777196073036e1, 1.113332393857199323513008e3, 7.738757056935398733233834e3, 2.763987074403340708898585e4, 5.499310206226157329794414e4, 6.161122180066002127833352e4, 3.635127591501940507276287e4, 8.785536302431013170870835e3]; var d2 = 4.227843350984671393993777e-1; var p2 = [4.974607845568932035012064e0, 5.424138599891070494101986e2, 1.550693864978364947665077e4, 1.847932904445632425417223e5, 1.088204769468828767498470e6, 3.338152967987029735917223e6, 5.106661678927352456275255e6, 3.074109054850539556250927e6]; var q2 = [1.830328399370592604055942e2, 7.765049321445005871323047e3, 1.331903827966074194402448e5, 1.136705821321969608938755e6, 5.267964117437946917577538e6, 1.346701454311101692290052e7, 1.782736530353274213975932e7, 9.533095591844353613395747e6]; var d4 = 1.791759469228055000094023e0; var p4 = [1.474502166059939948905062e4, 2.426813369486704502836312e6, 1.214755574045093227939592e8, 2.663432449630976949898078e9, 2.940378956634553899906876e10, 1.702665737765398868392998e11, 4.926125793377430887588120e11, 5.606251856223951465078242e11]; var q4 = [2.690530175870899333379843e3, 6.393885654300092398984238e5, 4.135599930241388052042842e7, 1.120872109616147941376570e9, 1.488613728678813811542398e10, 1.016803586272438228077304e11, 3.417476345507377132798597e11, 4.463158187419713286462081e11]; var c = [-1.910444077728e-03, 8.4171387781295e-04, -5.952379913043012e-04, 7.93650793500350248e-04, -2.777777777777681622553e-03, 8.333333333333333331554247e-02, 5.7083835261e-03]; var lng = Math.NaN; var mach = 1.e-12; var den = 1.0; var num = 0; var xm1, xm2, xm4; var i = 0; if (x < 0) { return lng; } else if (x <= mach) { return -Math.log(x); } else if (x <= 0.5) { for (i = 0; i < 8; i++) { num = num * x + p1[i]; den = den * x + q1[i]; } lng = -Math.log(x) + (x * (d1 + x * (num/den))); } else if (x <= 0.6796875) { xm1 = x - 1.0; for (i = 0; i < 8; i++) { num = num * xm1 + p2[i]; den = den * xm1 + q2[i]; } lng = -Math.log(x) + xm1 * (d2 + xm1*(num/den)); } else if (x <= 1.5) { xm1 = x - 1.0; for (i = 0; i < 8; i++) { num = num*xm1 + p1[i]; den = den*xm1 + q1[i]; } lng = xm1 * (d1 + xm1*(num/den)); } else if (x <= 4.0) { xm2 = x - 2.0; for (i = 0; i<8; i++) { num = num*xm2 + p2[i]; den = den*xm2 + q2[i]; } lng = xm2 * (d2 + xm2 * (num/den)); } else if (x <= 12) { xm4 = x - 4.0; den = -1.0; for (i = 0; i < 8; i++) { num = num * xm4 + p4[i]; den = den * xm4 + q4[i]; } lng = d4 + xm4 * (num/den); } else { var r = c[6]; var xsq = x * x; for (i = 0; i < 6; i++) { r = r / xsq + c[i]; } r = r / x; var lnx = Math.log(x); var spi = 0.9189385332046727417803297; lng = r + spi - 0.5*lnx + x*(lnx-1); } return lng; } // ends lnGamma function normPdf( mu, sigma, x) { return Math.exp(-(x-mu)*(x-mu)/(2*sigma*sigma))/ (Math.sqrt(2*Math.PI)*sigma); } // ends normPdf function tCdf(df, x) { // cdf of Student's t distribution with df degrees of freedom var ans; if (df < 1) { ans = Math.NaN; } else if (x === 0.0) { ans = 0.5; } else if (df == 1) { ans = 0.5 + Math.atan(x) / Math.PI; } else if (x > 0) { ans = 1 - (incBeta(df/(df+x*x), df/2.0, 0.5))/2; } else if (x < 0) { ans = incBeta(df/(df+x*x), df/2.0, 0.5)/2; } return ans; } function tPdf(df, x) { var d = Math.floor(df); return Math.exp(lnGamma((d+1)/2) - lnGamma(d/2))/ ( Math.sqrt(d*Math.PI)*Math.pow((1+x*x/d),(d+1)/2)); } function scalVMult(s, v) { var ans = new Array(v.length); for (var i=0; i < v.length; i++) { ans[i] = s*v[i]; } return ans; } function vCumSum(x) { // cumulative sum of vector ans = new Array(x.length); ans[0] = x[0]; for (var i=1; i < x.length; i++) { ans[i] = ans[i-1] + x[i]; } return ans; } function tInv(p, df ) { // inverse Student-t distribution with // df degrees of freedom var z; if (df < 0 || p < 0) { return Math.NaN; } else if (p === 0.0) { return Math.NEGATIVE_INFINITY; } else if (p == 1) { return Math.POSITIVE_INFINITY; } else if (df == 1) { return Math.tan(Math.PI*(p-0.5)); } else if ( p >= 0.5) { z = betaInv(2.0*(1-p),df/2.0,0.5); return Math.sqrt(df/z - df); } else { z = betaInv(2.0*p,df/2.0,0.5); return -Math.sqrt(df/z - df); } } function incBeta(x, a, b) { // incomplete beta function // I_x(z,w) = 1/beta(z,w) * integral from 0 to x of t^(z-1) * (1-t)^(w-1) dt // Ref: Abramowitz & Stegun, Handbook of Mathemtical Functions, sec. 26.5. var res; if (x < 0 || x > 1) { res = Math.NaN; } else { res = 0; var bt = Math.exp(lnGamma(a+b) - lnGamma(a) - lnGamma(b) + a*Math.log(x) + b*Math.log(1-x)); if (x < (a+1)/(a+b+2)) { res = bt * betaGuts(x, a, b) / a; } else { res = 1 - bt*betaGuts(1-x, b, a) / b; } } return res; } function betaGuts( x, a, b) { // guts of the incomplete beta function var ap1 = a + 1; var am1 = a - 1; var apb = a + b; var am = 1; var bm = am; var y = am; var bz = 1 - apb*x/ap1; var d = 0; var app = d; var ap = d; var bpp = d; var bp = d; var yold = d; var m = 1; var t; while (y-yold > 4*eps*Math.abs(y)) { t = 2 * m; d = m * (b - m) * x / ((am1 + t) * (a + t)); ap = y + d * am; bp = bz + d * bm; d = -(a + m) * (apb + m) * x / ((a + t) * (ap1 + t)); app = ap + d * y; bpp = bp + d * bz; yold = y; am = ap / bpp; bm = bp / bpp; y = app / bpp; if (m == 1) bz = 1; m++; } return y; } function chi2Cdf(df, x) { var p = (df == Math.floor(df)) ? gammaCdf(x,df/2,2) : Number.NaN; return p; } function chi2Pdf(df, x) { if (x <= 0) { return 0.0; } else { return gammaPdf(x, Math.floor(df)/2, 2); } } function chi2Inv( p, df ) { // kluge for chi-square quantile function. var guess = Math.NaN; if (p === 0.0) { guess = 0.0; } else if ( p == 1.0 ) { guess = Math.POSITIVE_INFINITY; } else if ( p < 0.0 ) { guess = Math.NaN; } else { var tolAbs = 1.0e-8; var tolRel = 1.0e-3; guess = Math.max(0.0, df + Math.sqrt(2*df)*normInv(p)); // guess from normal approx var currP = chi2Cdf( guess, df); var loP = currP; var hiP = currP; var guessLo = guess; var guessHi = guess; while (loP > p) { // step down guessLo = 0.8*guessLo; loP = chi2Cdf( guessLo, df); } while (hiP < p) { // step up guessHi = 1.2*guessHi; hiP = chi2Cdf( guessHi, df); } guess = (guessLo + guessHi)/2.0; currP = chi2Cdf( guess, df); while ( (Math.abs(currP - p) > tolAbs) || (Math.abs(currP - p)/p > tolRel) ) { // bisect if ( currP < p ) { guessLo = guess; } else { guessHi = guess; } guess = (guessLo + guessHi)/2.0; currP = chi2Cdf(guess, df); } } return guess; } function gammaCdf( x, a, b) { // gamma distribution CDF. var p = Math.NaN; if (a <= 0 || b <= 0) { } else if (x <= 0) { p = 0.0; } else { p = Math.min(incGamma(x/b, a), 1.0); } return p; } function incGamma( x, a) { var inc = 0; var gam = lnGamma(a+rmin); if (x === 0.0) { inc = 0; } else if (a === 0) { inc = 1; } else if (x < a+1) { var ap = a; var sum = 1.0/ap; var del = sum; while (Math.abs(del) >= 10*eps*Math.abs(sum)) { del *= x/(++ap); sum += del; } inc = sum * Math.exp(-x + a*Math.log(x) - gam); } else if (x >= a+1) { var a0 = 1; var a1 = x; var b0 = 0; var b1 = 1; var fac = 1; var n = 1; var g = 1; var gold = 0; var ana; var anf; while (Math.abs(g-gold) >= 10*eps*Math.abs(g)) { gold = g; ana = n - a; a0 = (a1 + a0 *ana) * fac; b0 = (b1 + b0 *ana) * fac; anf = n*fac; a1 = x * a0 + anf * a1; b1 = x * b0 + anf * b1; fac = 1.0 / a1; g = b1 * fac; n++; } inc = 1 - Math.exp(-x + a*Math.log(x) - gam) * g; } return inc; } function poissonPmf( lambda, k) { // Poisson probability mass function var p = 0.0; if (k != Math.floor(k)) { p = Number.NaN; } else { if (k >= 0) { p = Math.exp(-lambda)*Math.pow(lambda,k)/factorial(k); } } return p; } function poissonCdf( lambda, k) { // Poisson CDF var p = 0; var b = 0; var m = 0; if (k != Math.floor(k)) { p = Number.NaN; } else { while (m <= k) { b += Math.pow(lambda, m++)/factorial(k); } p += Math.exp(-lambda)*b; } return p; } function poissonTail(lambda, k) { // upper tail probability of the Poisson return 1.0-poissonCdf(lambda, k-1); } function expCdf(lambda, x) { // exponential CDF return 1-Math.exp(-x/lambda); } function expPdf(lambda, x) { // exponential density return (1.0/lambda)*Math.exp(-x/lambda); } function factorial(n) { // computes n! var fac = Number.NaN; if (n != Math.floor(n)) { fac = Number.NaN; } else { fac=1; for (var i=n; i > 1; i--) { fac *= i; } } return Math.round(fac); } function binomialCoef(n,k) { // computes n choose k if (n != Math.floor(n) || k != Math.floor(k)) { return Number.NaN; } else if (n < k || n < 0) { return 0.0; } else if ( k === 0 || n === 0 || n == k) { return 1.0; } else { var minnk = Math.min(k, n-k); var coef = 1; for (var j = 0; j < minnk; j++) { coef *= (n-j)/(minnk-j); } return Math.round(coef); } } function binomialPmf(n, p, k) { // binomial pmf at k. var pmf = binomialCoef(n,k)*Math.pow(p,k)*Math.pow((1-p),(n-k)); return pmf; } function binomialCdf(n, p, k) { // binomial CDF: Pr(X <= k), X~B(n,p) if (k < 0) { return 0.0; } else if (k >= n) { return 1.0; } else { var cdf = 0.0; for (var i = 0; i <= k; i++) { cdf += binomialPmf(n, p, i); } return cdf; } } function binomialTail(n,p,k) { // binomial tail probability Pr(X >= k), X~B(n,p) if (k < 0) { return 1.0; } else if (k >= n) { return 0.0; } else { var tailP = 0.0; for (var i = k; i <= n; i++) { tailP += binomialPmf(n, p, i); } return tailP; } } function binomialInv(n, p, pt) { // binomial percentile function var t = 0; if (pt < 0 || pt > 1) { t = NaN; } else if (pt === 0.0) { t = 0; } else if (pt == 1.0) { t = n; } else { t = 0; var pc = 0.0; while ( pc < pt ) { pc += binomialPmf(n, p, t++); } t -= 1; } return t; } function binomialLowerCL(n, x, cl, inc) { p = x/n; if (x > 0) { f = binomialTail(n, p, x-1); while (f >= 1-cl) { p = p - inc; f = binomialTail(n, p, x-1); } } else { p = 0; } return p; } function multinomialCoef(list, n) { // multinomial coefficient. // WARNING: not very stable algorithm; avoid for large n. var val = 0; var lmn = vMinMax(list); if (typeof(n) == 'undefined' || n === null) { n = vSum(list); } if (lmn[0] < 0.0) { alert('Error #1 in irGrade.multinomialCoef: a number of outcomes is negative!'); } else if (n == vSum(list)) { val = factorial(n); for (var i=0; i < list.length; i++) { val /= factorial(list[i]); } } return val; } function multinomialPmf(olist, plist, n) { // multinomial pmf; not stable algorithm var val = 0.0; var pmn = vMinMax(plist); var omn = vMinMax(olist); if (typeof(n) == 'undefined' || n === null) { n = vSum(olist); } if (olist.length != plist.length) { alert('Error #1 in irGrade.multinomialPmf: length of outcome and probability vectors ' + 'do not match!'); } else if (pmn[0] < 0.0) { alert('Error #2 in irGrade.multinomialPmf: a probability is negative!'); } else if (omn[0] < 0.0) { alert('Error #3 in irGrade.multinomialPmf: a number of outcomes is negative!'); } else if (n == vSum(olist)) { var pl = vMult(1.0/vSum(plist), plist); // just in case val = factorial(n); for (var i=0; i< olist.length; i++) { val *= Math.pow(pl[i], olist[i])/factorial(olist[i]); } } return val; } function geoPmf( p, k) { // chance it takes k trials to the first success in iid Bernoulli(p) trials // EX = 1/p; SD(X) = sqrt(1-p)/p var prob = 0.0; if (k != Math.floor(k)) { prob = Number.NaN; } else if (k < 1 || p === 0.0) { prob = 0.0; } else { prob = Math.pow((1-p),k-1)*p; } return prob; } function geoCdf( p, k) { // chance it takes k or fewer trials to the first success in iid Bernoulli(p) trials var prob = 0.0; if (k != Math.floor(k)) { prob = Number.NaN; } else if (k < 1 || p === 0.0) { prob = 0.0; } else { prob = 1-Math.pow( 1-p, k); } return prob; } function geoTail( p, k) { // chance of k or more trials to the first success in iid Bernoulli(p) trials return 1 - geoCdf(p, k-1); } function geoInv(p, pt) { // geometric percentile function var t = 0; if (pt < 0 || pt > 1) { t = Math.NaN; } else if (pt === 0.0) { t = 0; } else if (pt == 1.0) { t = Math.POSITIVE_INFINITY; } else { t = 0; var pc = 0.0; while ( pc < pt ) { pc += geoPmf(p, t++); } } return t; } function hyperGeoPmf( N, M, n, m) { // chance of drawing m of M objects in a sample of size n from // N objects in all. p = (M C m)*(N-M C n-m)/(N C n) // EX = n*M/N; SD(X)= sqrt((N-n)/(N-1))*sqrt(np(1-p)); var p = 0.0; if (N != Math.floor(N) || M != Math.floor(M) || n != Math.floor(n) || m != Math.floor(m)) { p = Number.NaN; } else if ( n < m || N < M || M < m || m < 0 || N < 0) { p = 0.0; } else { p = binomialCoef(M,m)*binomialCoef(N-M,n-m)/binomialCoef(N,n); } return p; } function hyperGeoCdf( N, M, n, m) { // chance of drawing m or fewer of M objects in a sample of size n from // N objects in all var p=0.0; if (N != Math.floor(N) || M != Math.floor(M) || n != Math.floor(n) || m != Math.floor(m)) { p = Number.NaN; } else { var mMax = Math.min(m,M); mMax = Math.min(mMax,n); for (var i = 0; i <= mMax; i++) { p += hyperGeoPmf(N, M, n, i); } } return p; } function hyperGeoTail( N, M, n, m) { // chance of drawing m or more of M objects in a sample of size n from // N objects in all var p=0.0; if (N != Math.floor(N) || M != Math.floor(M) || n != Math.floor(n) || m != Math.floor(m)) { p = Number.NaN; } else { for (var i = m; i <= Math.min(M,n); i++) { p += hyperGeoPmf(N, M, n, i); } } return p; } function negBinomialPmf( p, s, t) { // chance that the sth success in iid Bernoulli trials is on the tth trial // EX = s/p; SD(X) = sqrt(s(1-p))/p var prob = 0.0; if (s != Math.floor(s) || t != Math.floor(t)) { prob = Number.NaN; } else if (s > t || s < 0) { prob = 0.0; } else { prob = p*binomialPmf(t-1,p,s-1); } return prob; } function negBinomialCdf( p, s, t) { // chance the sth success in iid Bernoulli trials is on or before the tth trial var prob = 0.0; if (s != Math.floor(s) || t != Math.floor(t)) { prob = Number.NaN; } else { for (var i = s; i <= t; i++) { prob += negBinomialPmf(p, s, i); } } return prob; } function pDieRolls(rolls,spots) { // chance that the sum of 'rolls' rolls of a die = 'spots' if (rolls > 4) { alert('Error #1 in irGrade.pDiceRolls: too many rolls ' + rolls + '. '); return Math.NaN; } else { // BRUTE FORCE! var found = 0; if (spots < rolls || spots > 6*rolls) {return 0.0;} var possible = Math.pow(6,rolls); var i = 0; var j = 0; var k = 0; if (rolls == 1) { return 1/possible; } else if (rolls == 2) { for (i=1; i <=6; i++ ) { for (j=1; j <= 6; j++ ) { if (i+j == spots ) {found++;} } } } else if (rolls == 3 ) { for (i=1; i <=6; i++ ) { for (j=1; j<=6; j++ ) { for (k=1; k<=6; k++ ) { if (i+j+k == spots ) {found++;} } } } } else if (rolls == 4 ) { for (i=1; i <=6; i++ ) { for (j=1; j<=6; j++ ) { for (k=1; k<=6; k++ ) { for (var m=1; m <=6; m++ ) { if (i+j+k+m == spots ) {found++;} } } } } } return found/possible; } return false; } function permutations(n,k) { // number of permutations of k of n things var coef; if ((Math.floor(n) != n ) || (Math.floor(k) != k)) { coef = Number.NaN; } else if (n < k || n < 0) { coef = 0; } else if ( k===0 || n === 0) { coef = 1; } else { coef=1; for (var j=0; j < k; j++) coef *= (n-j); } return Math.round(coef); } function sgn(x) { // signum function if (x >= 0) { return 1; } else if (x < 0) { return (-1); } } function linspace(lo,hi,n) { // n linearly spaced points between lo and hi var spaced = new Array(n); var dx =(hi-lo)/(n-1); for (var i=0; i < n; i++) { spaced[i] = lo + i*dx; } return spaced; } function rms(list) { // rms var r = 0; for (var i=0; i < list.length; i++) r += list[i]*list[i]; r /= list.length; return Math.sqrt(r); } function vMinMax(list){ // returns min and max of list var mn = list[0]; var mx = list[0]; for (var i=1; i < list.length; i++) { if (mn > list[i]) mn = list[i]; if (mx < list[i]) mx = list[i]; } var vmnmx = new Array(mn,mx); return vmnmx; } function vMinMaxIndices(list){ // returns min, max, index of min, index of max var mn = list[0]; var indMn = 0; var mx = list[0]; var indMx = 0; for (var i=1; i < list.length; i++) { if (mn > list[i]) { mn = list[i]; indMn = i; } if (mx < list[i]) { mx = list[i]; indMx = i; } } var vmnmx = new Array(mn,mx,indMn,indMx); return vmnmx; } function vMinMaxAbs(list) { // returns min and max of absolute values of a list's elements var mn = Math.abs(list[0]); var mx = Math.abs(list[0]); var val; for (var i=1; i < list.length; i++) { val = Math.abs(list[i]); if (mn > val) mn = val; if (mx < val) mx = val; } var vmnmx = new Array(mn,mx); return vmnmx; } function randBoolean(p){ // random boolean value, prob p that it is true if (typeof(p) == 'undefined' || p === null) { p = 0.5; } if (rand.next() <= p) { return false; } else { return true; } } function sortUnique(list,order) { // sort a list, remove duplicate entries var temp = list; if (typeof(order) != 'undefined' && order !== null) { temp.sort(order); } else { temp.sort(); } var temp2 = []; temp2[0] = temp[0]; var ix = 0; for (var i=1; i < temp.length; i++) { if (temp[i] != temp2[ix] ) { temp2[++ix] = temp[i]; } } return temp2; } function uniqueCount(list) { // unique elements and their counts var temp = {}; temp[list[0]] = list[0]; var j = 0; for (j=1; j < list.length; j++) { if (typeof(temp[list[j]]) == 'undefined' || temp[list[j]] === null) { temp[list[j]] = 1; } else { temp[list[j]]++; } } uc = new Array(2); uc[0] = []; uc[1] = []; var k = 0; for (j in temp) { uc[0][k] = j; uc[1][k++] = temp[j]; } return uc; } function unique(list) { return uniqueCount(list)[0]; } function randPermutation(list,index) { // returns a random permutation of list var randIndex = listOfDistinctRandInts(list.length,0,list.length-1); var thePermutation = new Array(list.length); var i = 0; for (i=0; i < list.length; i++) { thePermutation[i] = list[randIndex[i]]; } var p = 0; if (typeof(index) != 'undefined' && index == 'forward') { // original indices p = new Array(2); p[0] = thePermutation; p[1] = randIndex; thePermutation = p; } else if (typeof(index) != 'undefined' && index == 'inverse') { // inverse permutation p = new Array(2); p[0] = thePermutation; p[1] = new Array(list.length); for (i=0; i < list.length; i++) { p[1][randIndex[i]] = i; } thePermutation = p; } return thePermutation; } function cyclicPermutation(n, k) { // cyclic permutation by k of of the integers 0 to n-1 if (typeof(k) == 'undefined' || k === null) { k = 1; } var perm = new Array(n); for (var i = 0; i < n; i++) { perm[i] = (i+k)%n; } return perm; } function distinctPermutation(n, k) { // returns a permutation of the integers 0 to n-1 // in which no index maps to itself if (typeof(k) == 'undefined' || k === null) { k = Math.min(3, n-1); } return cyclicPermutation(n,k); } function distinctRandPermutation(n) { // returns a random permutation of the integers 0 to n-1 // in which no index maps to itself function isInPlace(x) { // is any index in its original place? v = false; for (var i=0; i < x.length; i++) { if (x[i] == i) { v = true; } } return v; } var x = new Array(n); for (var i = 0; i < n; i++) { x[i] = i; } x = randPermutation(x); while (isInPlace(x)) { x = randPermutation(x); } return x; } function fakeBivariateData(nPoints, funArray, heteroFac, snr, loEnd, hiEnd) { // returns a 2-d array of synthetic data generated from a polynomial, // according to the contents of funArray. // if funArray[0] == 'polynomial', uses the other elements of funArray as // the coefficients of a polynomial. // funArray[1] + funArray[2]*X + funArray[3]*X^2 + ... // 1/3 of the points have noise level heteroFac times larger than the rest. // Normalizes the errors to signal/noise ratio snr in 2-norm var data = new Array(2); data[0] = new Array(nPoints); data[1] = new Array(nPoints); if (snr === 0) { snr = 2; } var x; var fVal; var xPow; var i; if (funArray[0] == 'polynomial') { if (typeof(loEnd) == 'undefined' || loEnd === null) { // lower limit of X variable loEnd = -10; } if (typeof(hiEnd) == 'undefined' || hiEnd === null) { // upper limit of X variable hiEnd = 10; } var dX = (hiEnd - loEnd)/(nPoints - 1); for (i=0; i < nPoints; i++) { x = loEnd + i*dX; data[0][i] = x; fVal = 0.0; xPow = 1.0; for (var j=1; j < funArray.length; j++) { fVal += xPow*funArray[j]; xPow *= x; } data[1][i] = fVal; } } else { alert('Error #1 in irGrade.fakeBivariateData()!\n' + 'Unsupported function type: ' + funArray[0].toString()); return null; } // now add noise. var sigNorm = twoNorm(data[1]); var noise = new Array(nPoints); for (i=0; i < nPoints; i++) { noise[i] = rNorm(); } // pick a random set to perturb for heteroscedastic noise var segLen = Math.floor(nPoints/3); var startPt = Math.floor(2*nPoints/3*rand.next()); for (i=startPt; i < startPt+segLen; i++) { noise[i] = noise[i]*heteroFac; } var noiseNorm = twoNorm(noise); for (i=0; i < nPoints; i++) { data[1][i] += noise[i]*sigNorm/noiseNorm/snr; } return data; } function nextRand() { // generates next random number in a sequence var up = this.seed / this.Q; var lo = this.seed % this.Q; var trial = this.A * lo - this.R * up; if (trial > 0) { this.seed = trial; } else { this.seed = trial + this.M; } return (this.seed * this.oneOverM); } function rng(s) { if ( typeof(s)=='undefined' || s === null ){ var d = new Date(); this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF); } else { this.seed = s; } this.A = 48271; this.M = 2147483647; this.Q = this.M / this.A; this.R = this.M % this.A; this.oneOverM = 1.0 / this.M; this.next = nextRand; this.getSeed = getRandSeed; return this; } // Only define rand if it has not already been defined - the page may want to // define a rand using a custom seed. It can do this before the page loads // if it wants. jQuery(function() { if (typeof(rand) == 'undefined') { console.log('rand has not been defined already, creating in stat_utils'); rand = rng(); } else { console.log('Skipping creation of rand because it already exists.'); } }); function getRandSeed() { // get seed of random number generator return this.seed; } function crypt(s,t) { var slen = s.length; var tlen = t.length; var rad = 16; var r = 0; var i; var j = -1; var result = ''; if (s.substr(0,2) == '0x') { for (i=2; i < slen; i+=2) { if (++j >= tlen) {j = 0;} r = parseInt(s.substr(i,2),rad) ^ t.charCodeAt(j); result += String.fromCharCode(r); } } else { result +='0x'; for ( i=0; i < slen; i++) { if (++j >= tlen) {j = 0;} r = s.charCodeAt(i) ^ t.charCodeAt(j); result += (r < rad ? '0' : '') + r.toString(rad); } } return result; } // Author: James Eady <jeady@berkeley.edu> // // This file defines a javascript check box. // // Usage: // $('body').append(new SticiCheck({ // label: 'Check to turn on', // readonly: false, // value: false, // change: function(e, is_checked) { // if (is_checked) // alert('On!'); // }})); function SticiCheck(params) { var self = this; // Make this object jQuery compatible by making it a jQuery object. self = jQuery('<div/>').addClass('stici_check'); var check = jQuery('<input type="checkbox"/>'); var label = jQuery('<span/>'); // Options. var options = { label: 'Checkbox', readonly: false, value: false, change: function(e, is_checked) {} }; jQuery.extend(options, params); // Accessors. self.label = function(l) { if (l !== undefined) { options.label = l; label.text(options.label); return self; } return options.l; }; self.val = self.checked = function(val) { if (val !== undefined) { options.value = val; return self; } return options.value; }; self.change = function(c) { if (c !== undefined) { options.change = c; return self; } return options.change; }; // Build the UI. check.prop('checked', options.value); label.text(options.label); if (!options.readonly) self.append(check); self.append(label); check.change(function(e) { e.preventDefault(); self.val(check.is(':checked')); e.target = self; options.change(e, self.val()); }); return self; } // Author: James Eady <jeady@berkeley.edu> // // This file defines a javascript combo box. // // Usage: // $('body').append(new SticiComboBox({ // label: 'Choose your favorite pet: ', // options: { // 'cats' : 'MEOW!', // 'dogs' : 'WOOF!', // 'anything else': 'Fascinating...' // }, // selected: 'cats', // change: function(e, value, label) { // alert(value); // }})); function SticiComboBox(params) { var self = this; // Make this object jQuery compatible by making it a jQuery object. self = jQuery('<div/>').addClass('stici_combobox'); var label = jQuery('<span/>'); var select = jQuery('<select/>'); // Options. var options = { label: 'Choose: ', options: { 'foo': null, 'bar': null }, selected: null, value: null, change: function(e, value, label) {} }; jQuery.extend(options, params); // Accessors and modifiers. self.val = function(val) { if (val !== undefined) { jQuery.each(options.options, function(key, value) { if (val == value) self.selected(key); }); return self; } return options.options[select.val()]; }; self.selected = function(label) { if (label !== undefined) { select.val(label); return self; } return select.val(); }; self.options = function(opts) { if (opts !== undefined) { options.options = opts; select.children().remove(); jQuery.each(options.options, function(key, value) { var opt = jQuery('<option/>').data('value', value).text(key); select.append(opt); }); return self; } return options.options; }; self.change = function(c) { if (c !== undefined) { options.change = c; return self; } return options.change; }; // Put things together. label.text(options.label); self.append(label, select); // Fill in the combobox. self.options(options.options); if (options.value !== null) self.val(options.value); if (options.selected !== null) self.selected(options.selected); // Change handler, which will in turn dispatch the event to the user. select.change(function(e) { e.preventDefault(); e.target = self; options.change(e, self.val(), select.val()); }); return self; } // Author: James Eady <jeady@berkeley.edu> // // This file defines a javascript control that displays a histogram. // // Usage: // TODO(jmeady): Add Usage. function SticiHistogram(params) { var self = this; // Make this object jQuery compatible by making it a jQuery object. self = jQuery('<div/>').addClass('stici_histogram'); // Options. var options = { hiLiteLo: 0, hiLiteHi: 0, binEnds: [0, 10, 20, 30, 40, 50, 60], showCurves: true, // These can be either arrays of binCount arrays/curves or a single // binCount array and curve. binCounts: [[2, 8, 10, 17, 3, 5], [5, 10, 15, 12, 8, 2]], curves: [function(x) {return 7;}, function(x) {return 13;}] }; jQuery.extend(options, params); // Private handle to the overlay to adjust the highlighting. var overlay = null; // If the given binCounts/curves are not arrays, make them single-element // arrays. This means we can use the same code regardless of how many // overlain histograms there are. if (!(options.binCounts[0] instanceof Array)) options.binCounts = [options.binCounts]; if (!(options.curves) instanceof Array) options.curves = [options.curves]; // Accessor and modifier functions. self.hilite = function(lo, hi) { if (lo !== undefined) options.hiLiteLo = lo; if (hi !== undefined) options.hiLiteHi = hi; var bounds = calculateBounds(); var scale = self.width() / (bounds.x_hi - bounds.x_lo); var left = (options.hiLiteLo - bounds.x_lo) * scale; var right = (options.hiLiteHi - bounds.x_lo) * scale; overlay.css( 'clip', 'rect(0px,' + right + 'px,' + self.height() + 'px,' + left + 'px)'); if (lo !== undefined || hi !== undefined) return self; else return [options.hiLiteLo, options.hiLiteHi]; }; self.binEnds = function(ends) { if (ends !== undefined) { options.binEnds = ends; self.redraw(); return self; } return options.binEnds; }; self.showCurves = function(show) { if (show === undefined) show = true; options.showCurves = show; self.redraw(); return self; }; self.hideCurves = function() { options.showCurves = false; self.redraw(); return self; }; // The following functions are rather magical. If only a single histogram is // being displayed, 'i' can always be ommitted and all arguments/return // values will traffic in the binCounts array and curve function directly // (e.g. set([2,3,4], [5,6], function(x) {return 7;});). If multiple // histograms are being displayed, however, they will traffic in arrays // of bin count arays and curve functions. self.binCounts = function(i, counts) { if (counts === undefined && options.binCounts.length == 1) { // Single-histogram mode. counts = i; if (counts === undefined) return options.binCounts[0]; setBinCounts(counts); self.redraw(); return self; } else { // Multi-histogram mode if (i === undefined) return options.binCounts; if (!isFinite(i)) { setBinCounts(i); self.redraw(); return self; } if (counts === undefined) return options.binCounts[i]; options.binCounts[i] = counts; self.redraw(); return self; } }; self.curves = self.curve = function(i, curve) { if (curve === undefined && options.curves.length == 1) { // Single-histogram mode. curve = i; if (curve === undefined) return options.curves[0]; setCurves(curves); self.redraw(); return self; } else { // Multi-histogram mode if (i === undefined) return options.curves; if (!isFinite(i)) { setCurves(i); self.redraw(); return self; } if (curve === undefined) return options.curves[i]; options.curves[i] = curve; self.redraw(); return self; } }; self.set = function(binEnds, binCounts, curves) { if (curves === undefined) curves = []; options.binEnds = binEnds; setBinCounts(binCounts); setCurves(curves); self.redraw(); return self; }; // These methods provide validation so we prefer them over setting variables // directly. function setBinCounts(binCounts) { if (!(binCounts[0] instanceof Array)) binCounts = [binCounts]; binCounts = jQuery.map(binCounts, function(counts) { return [jQuery.map(counts, function(v) { if (v === undefined || v === null || isNaN(v)) return 0; return v; })]; }); options.binCounts = binCounts; } function setCurves(curves) { if (!(curves instanceof Array)) curves = [curves]; options.curves = curves; } // This can be called in the case that our size has changed. self.redraw = function() { // Clear everything. self.children().remove(); // Don't try to draw if we cannot be seen. if (!self.is(':visible') || self.width() === 0 || self.height() === 0) return; // Basic pieces. Use these to enforce ordering. overlay = jQuery('<div/>').addClass('chart_box overlay'); var chart = jQuery('<div/>').addClass('chart_box'); var curves = jQuery('<div/>').addClass('chart_box'); var axis = jQuery('<div/>').addClass('chart_box axis'); self.append(chart, overlay, curves, axis); // Hide the overlay. This clip will be controlled to highlight sections. overlay.css('clip', 'rect(0px, 0px, ' + height + 'px, 0px)'); // Basic chart parameters. var width = self.width(); var height = self.height() - axis.height(); var bounds = calculateBounds(); var yScale = bounds.y_hi / (height - 1); // Draw the axis if (!isNaN(bounds.width) && isFinite(bounds.width) && bounds.width > 0) { var scale = d3.scale.linear() .domain([bounds.x_lo, bounds.x_hi]) .range([0, width]); d3.select(axis.get(0)) .append('svg') .append('g').call(d3.svg.axis().scale(scale).orient('bottom')); } if (isNaN(bounds.width) || !isFinite(bounds.width) || bounds.width === 0 || isNaN(bounds.height) || !isFinite(bounds.height) || bounds.height === 0) { console.warn("Histogram not rendering invalid data."); return; } // Draw the bins. Iterate once for each set of binCounts, but draw them so // that the tallest bins regardless of binCounts set get rendered first, // and are therefore in the back. var interleaved = interleaveAndSortBinCounts(); jQuery.each(options.binCounts, function(i) { d3.select(chart.get(0)) .append('svg') .selectAll('div') .data(interleaved) .enter() .append('rect') .attr('y', function(d) { return height - d[i][0] / yScale; }) .attr('height', function(d) { var height = d[i][0] / yScale; if (height < 0.0001) height = 0; return height; }) .attr('x', function(d, i) { return (width * (options.binEnds[i] - bounds.x_lo) / bounds.width); }) .attr('width', function(d, i) { return width * (options.binEnds[i + 1] - options.binEnds[i]) / bounds.width; }) .attr('class', function(d) { return 'set' + d[i][1]; }); }); // Create the hilite overlay. overlay.append(chart.children().clone().detach()); // Draw the curves. if (options.showCurves) { jQuery.each(options.curves, function(i, curve) { if (curve === undefined || curve === null) return; var line = d3.svg.line() .x(function(d) {return d;}) .y(function(d) { // Convert the pixel offset to a usable x-coordinate that means // something to the curve. var x = bounds.x_lo + d * bounds.width / width; var y = height - (curve(x) / yScale); if (isNaN(y)) return 0; return y; }); d3.select(curves.get(0)) .append('svg') .append('path') .attr('class', 'set' + i) .data([range(0, width)]) .attr('d', line); }); } // Reset the highlight position. self.hilite(); }; // Calculates the chart bounds from the binCounts and curves. function calculateBounds() { var bounds = { x_lo: Infinity, x_hi: -Infinity, width: -Infinity, y_lo: 0, // We don't allow negative bin counts. y_hi: -Infinity, height: -Infinity }; // X range is easy to calculate. bounds.x_lo = options.binEnds.min(); bounds.x_hi = options.binEnds.max(); bounds.width = bounds.x_hi - bounds.x_lo; // Y height can be bounded either by the curve or by the bins. Check both, // and watch out for NaN. var boxHi = jQuery.map(options.binCounts, function(a) { if (a === undefined || a === null || a.length === 0) return -Infinity; return a.max(); }).max(); var curveHi = jQuery.map(options.curves, function(c) { if (c === undefined || c === null) return -Infinity; return jQuery.grep( jQuery.map(range(0, self.width()), function(i) { return c((i / self.width()) * (bounds.x_hi - bounds.x_lo) + bounds.x_lo); }), isNaN, true).max(); }).max(); if (isNaN(curveHi)) curveHi = -Infinity; bounds.y_hi = Math.max(curveHi, boxHi); bounds.height = bounds.y_hi - bounds.y_lo; return bounds; } // Interleaves the values from all of the binCounts. Each value is // transformed into a [value, binCountsIndex] pair. The resulting interleaved // elements are then sorted in descending order by value. For example: // Given: [[1, 5, 8], [3, 9, 7], [2, 6, 4]] // Outputs: [[[3, 1], [2, 2], [1, 0]], // [[9, 1], [6, 2], [5, 0]], // [[8, 0], [7, 1], [4, 2]]] function interleaveAndSortBinCounts() { return jQuery.map(range(0, options.binEnds.length - 1), function(i) { // jQuery requires that we double-wrap this array because it flattens map // results. return [jQuery.map(options.binCounts, function(binCounts, set) { // Just use 0 if there is no count for this bin. if (i >= binCounts.length) return [[0, set]]; return [[binCounts[i], set]]; }).sort(function(a, b) { return b[0] - a[0]; })]; }); } // Draw once before we return. All other draws will be triggered by events. self.redraw(); return self; } // Author: James Eady <jeady@berkeley.edu> // // This file defines a javascript control that consists of both a text input // and a jQuery-UI slider. // // Usage: // $('body').append(new SticiTextBar({ // label: 'Insanity', // min: 0, // max: 10000, // step: 1, // value: 2000, // change: function(e, value) { // if (value > 9000) // alert('OVER 9000!!!'); // }})); function SticiTextBar(params) { var self = this; // Make this object jQuery compatible by making it a jQuery object. self = jQuery('<div/>').addClass('stici_textbar'); // Options. var options = { min: 0, max: 100, step: 1, value: 25, label: '', change: function(e, value) {} }; jQuery.extend(options, params); // Create the basic pieces. var label = jQuery('<span/>').addClass('stici_textbar_label'); var input = jQuery('<input type="text"/>').addClass('stici_textbar_input'); var slider = jQuery('<div/>').addClass('stici_textbar_slider'); // Compose the pieces. self.append(jQuery('<div/>').append(label, input)); self.append(jQuery('<div/>').append(slider)); // Accessor and modifier functions. self.min = function(min) { if (min !== undefined) { options.min = min; slider.slider('option', 'min', options.min); self.val(self.val()); return self; } return options.min; }; self.max = function(max) { if (max !== undefined) { options.max = max; slider.slider('option', 'max', options.max); self.val(self.val()); return self; } return options.max; }; self.bounds = function(min, max) { self.min(min); self.max(max); return self; }; self.step = function(step) { if (step !== undefined) { options.step = step; slider.slider('option', 'step', options.step); self.val(self.val()); return self; } return options.step; }; self.val = function(val) { if (val !== undefined) { // Set the slider value first and allow it to do things like bounds // checking. slider.slider('value', val); val = slider.slider('value'); input.val(val); return self; } return slider.slider('value'); }; self.label = function(label) { if (label !== undefined) { options.label = label; label.text(options.label); return self; } return options.label; }; self.change = function(change) { if (change !== undefined) { options.change = change; return self; } return options.change; }; self.set = function(val, min, max, step) { return self.val(val).bounds(min, max).step(step); }; // Initialize the pieces. label.text(options.label); input.change(onChange); slider.slider({ min: options.min, max: options.max, step: options.step, value: options.value, slide: onChange }); input.val(slider.slider('value')); // This function receives a change event and sets both the slider and text // input equal to the event value. Used as the change callback for both // inputs. function onChange(e, ui) { var value; e.target = self; // If ui is undefined, we are coming from the text input. if (ui === undefined) value = jQuery(this).val(); else value = ui.value; // This method will do bounds and step checking so use it. self.val(value); options.change(e, self.val()); } return self; } // Author: James Eady <jeady@berkeley.edu> // // This file defines a javascript toggle button. // // Usage: // $('body').append(new SticiToggleButton({ // trueLabel: 'On', // falseLabel: 'Off', // value: false, // change: function(e, is_on) { // if (is_on) // alert('On!'); // }})); function SticiToggleButton(params) { var self = this; // Make this object jQuery compatible by making it a jQuery object. self = jQuery('<button/>').addClass('stici_togglebutton'); // Options. var options = { trueLabel: 'On', falseLabel: 'Off', value: false, change: function(e, is_on) {} }; jQuery.extend(options, params); // Accessors. self.val = self.is_true = self.is_toggled = self.toggled = function() { return options.value; }; self.is_false = function() { return !options.value; }; self.set = function(val) { options.value = val; return self; }; self.change = function(c) { if (c !== undefined) { options.change = c; return self; } return options.change; }; // Helper method. function setLabel() { if (options.value) self.text(options.trueLabel); else self.text(options.falseLabel); } // Set up the button. self.click(function(e) { e.preventDefault(); e.target = self; options.value = !options.value; setLabel(); options.change(e, options.value); }); setLabel(); return self; } (function(){ $.fn.popbox = function(options){ var settings = $.extend({ selector : this.selector, open : '.open', box : '.box', arrow : '.arrow', arrow_border : '.arrow-border', close : '.close' }, options); var methods = { open: function(event){ event.preventDefault(); var pop = $(this); var box = $(this).parent().find(settings['box']); box.find(settings['arrow']).css({'left': box.width()/2 - 10}); box.find(settings['arrow_border']).css({'left': box.width()/2 - 10}); if(box.css('display') == 'block'){ methods.close(); } else { box.css({'display': 'block', 'top': 10, 'left': ((pop.parent().width()/2) -box.width()/2 )}); } if (typeof $(this).parent().data('onPopBox') != 'undefined') $(this).parent().data('onPopBox')(); }, close: function(){ $(settings.selector).find(settings['box']).fadeOut("fast"); } }; $(document).bind('keyup', function(event){ if(event.keyCode == 27){ methods.close(); } }); $(document).bind('click', function(event){ if(!$(event.target).closest(settings['selector']).length){ methods.close(); } }); return this.each(function(){ if ($(this).data('isPopBox') === true) return; $(this).data('isPopBox', true); if (!$(this).css('width')) $(this).css({'width': $(settings['box']).width()}); // Width needs to be set otherwise popbox will not move when window resized. $(settings['open'], this).bind('click', methods.open); $(settings['open'], this).parent().find(settings['close']).bind('click', function(event){ event.preventDefault(); methods.close(); }); }); } }).call(this);