}
+ */
+ setFocusGroup: function (iAttData, iValue) {
+ const theName = iAttData.name;
+ const theValues = [...iAttData.valueSet]; // possible values for groups
+ const defaultValue = this.state.focusGroupDictionary[theName] ?
+ this.state.focusGroupDictionary[theName] :
+ theValues[0];
+
+ const theValue = theValues.includes(iValue) ? iValue : defaultValue;
+
+ this.state.focusGroupDictionary[theName] = theValue;
+
+ return theValue;
+ },
+
+ setLogisticFocusGroup: async function(iAttData, iValue) {
+
+ const theValue = this.setFocusGroup(iAttData, iValue);
+
+ // if this is logistic regression
+ const theConfig = Test.configs[testimate.state.testID];
+ const theAxis = theConfig.groupAxis; // only exists for logistic regression
+ if (theAxis) {
+ const f = await connect.updateDatasetForLogisticGroups(theValue, theAxis);
+ console.log(`changing logistic grouping: new formula : [${f}]`);
+ }
+ // done with special logistic treatment
+ return theValue;
+
+ },
+
+ predictorExists: function () {
+ return (testimate.state.y && testimate.state.y.name);
+ },
+
+ emptyAttribute: {
+ name: "",
+ title: "",
+ id: -1,
+ },
+
+ constants: {
+ pluginName: `testimate`,
+ version: `2024g`,
+ dimensions: {height: 555, width: 444},
+
+ emittedDatasetName: `tests and estimates`, // for receiving emitted test and estimate results
+ logisticGroupAttributeName: `_logisticGroup`, // to add to the original dataset
+ logisticGraphName: "logistic graph",
+
+ defaultState: {
+ lang: `en`,
+ dataset: null, // whole dataset info, includes .name
+ dataTypes: {}, // {'gender' : 'categorical', 'height' : 'numeric', ...}
+ x: null, // attribute info, complete
+ y: null,
+ randomEmitNumber: 10, // number of times you re-randomize by default
+ testID: null,
+ testParams: {},
+ mostRecentEmittedTest: null,
+ focusGroupDictionary : {},
+ testParamDictionary : {},
+ valueDictionary : {}, // records the number in the "value" box
+ }
+ }
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/anova.js b/eepsmedia/plugins/testimate/src/tests/anova.js
new file mode 100644
index 00000000..6a0adadd
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/anova.js
@@ -0,0 +1,190 @@
+class ANOVA extends Test {
+
+ constructor(iID) {
+ super(iID);
+ this.results.expected = {};
+ this.results.observed = {};
+ this.results.values = [];
+ }
+
+ updateTestResults() {
+
+ const A = data.xAttData.theArray;
+ const tempG = data.yAttData.theArray;
+ const G = tempG.map( n => String(n)); // make string values for group names
+
+ const tempNames = [...data.yAttData.valueSet];
+ this.results.groupNames = tempNames.map( n => String(n)); // make string values for group names
+
+ this.results.N = A.length;
+
+ if (this.results.N) {
+ this.results.sum = 0;
+ this.results.groupNs = new Array(this.results.groupNames.length).fill(0);
+
+
+ // calculate group means
+ this.results.groupSums = new Array(this.results.groupNames.length).fill(0);
+ this.results.groupMeans = new Array(this.results.groupNames.length).fill(0);
+
+ for (let ix = 0; ix < A.length; ix++) {
+ let group = this.results.groupNames.indexOf(G[ix]);
+ this.results.groupNs[group]++;
+ this.results.groupSums[group] += A[ix];
+ this.results.sum += A[ix];
+ }
+
+ this.results.mean = this.results.sum / this.results.N; // grand mean
+
+ // calculate group means (loop over groups...)
+ for (let ix = 0; ix < this.results.groupNames.length; ix++) {
+ if (this.results.groupNs[ix]) {
+ const theGM = this.results.groupSums[ix] / this.results.groupNs[ix];
+ this.results.groupMeans[ix] = theGM;
+ } else {
+ this.results.groupMeans[ix] = null; // the group mean is null if there are no cases in the group.
+ }
+ }
+
+ // calculate within-group errors, add between-group errors
+
+ this.results.SSR = 0; // between-group error (sum of squares of regression)
+ this.results.SSE = 0; // sum of squares of error (within group)
+
+ for (let ix = 0; ix < A.length; ix++) {
+ let group = this.results.groupNames.indexOf(G[ix]);
+ const treat = this.results.groupMeans[group] - this.results.mean; // between
+ const err = A[ix] - this.results.groupMeans[group]; // within
+ this.results.SSE += err * err;
+ this.results.SSR += treat * treat;
+ }
+
+ this.results.SST = this.results.SSR + this.results.SSE;
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2; // the large number
+
+ this.results.dfTreatment = this.results.groupNames.length - 1; // "numerator" between groups
+ this.results.dfError = this.results.N - this.results.groupNames.length; // "denominator" within
+ this.results.dfTotal = this.results.dfError + this.results.dfTreatment;
+
+ this.results.MSTreatment = this.results.SSR / this.results.dfTreatment;
+ this.results.MSError = this.results.SSE / this.results.dfError;
+
+ this.results.F = this.results.MSTreatment / this.results.MSError;
+
+ this.results.FCrit = jStat.centralF.inv(theCIparam, this.results.dfTreatment, this.results.dfError); //
+ this.results.P = 1 - jStat.centralF.cdf(this.results.F, this.results.dfTreatment, this.results.dfError);
+ }
+ }
+
+ static toggleDS() {
+ this.openDS = !this.openDS;
+ console.log(`descriptive details now ${this.openDS ? 'open' : 'closed'}.`);
+ }
+
+ makeResultsString() {
+
+ const N = this.results.N;
+ const F = ui.numberToString(this.results.F);
+ const FCrit = ui.numberToString(this.results.FCrit);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+
+ const DSdetails = document.getElementById("DSdetails");
+ const DSopen = DSdetails && DSdetails.hasAttribute("open");
+ const Fdetails = document.getElementById("Fdetails");
+ const Fopen = Fdetails && Fdetails.hasAttribute("open");
+
+ let out = "";
+ out += localize.getString("tests.anova.testQuestion",
+ testimate.state.x.name, testimate.state.y.name);
+ out += ` N = ${N}, F = ${F}, ${P} `;
+ out += ``;
+ out += localize.getString("tests.anova.detailsSummary1");
+ out += this.makeDescriptiveTable();
+ out += ` `;
+ out += ``;
+ out += localize.getString("tests.anova.detailsSummary2");
+ out += this.makeANOVATable();
+ out += ` α = ${alpha}, F* = ${FCrit}`;
+ out += ` `;
+ out += ` `;
+ return out;
+ }
+
+ makeANOVATable() {
+ const dfT = this.results.dfTreatment;
+ const dfE = this.results.dfError;
+ const dfTotal = this.results.dfTotal;
+ const SSR = ui.numberToString(this.results.SSR, 5);
+ const SSE = ui.numberToString(this.results.SSE, 5);
+ const SST = ui.numberToString(this.results.SST, 5);
+ const MST = ui.numberToString(this.results.MSTreatment, 5);
+ const MSE = ui.numberToString(this.results.MSError, 5);
+ const F = ui.numberToString(this.results.F);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+
+ // const treatmentString = `Treatment (i.e., ${data.yAttData.name})`;
+ const treatmentString = `${data.yAttData.name}`;
+ const errorString = localize.getString("error");
+ const totalString = localize.getString("total");
+
+ let theHTML = "";
+ theHTML += "Source (SS) df (MS) F P ";
+ theHTML += `${treatmentString} ${SSR} ${dfT} ${MST} ${F} ${P} `
+ theHTML += `${errorString} ${SSE} ${dfE} ${MSE} `
+ theHTML += `${totalString} ${SST} ${dfTotal} `
+ theHTML += `
`
+
+ return theHTML;
+ }
+
+ makeDescriptiveTable() {
+ const meanOfX = localize.getString("tests.anova.meanOfX",testimate.state.x.name)
+
+ let nameRow = `${data.yAttData.name} → `;
+ let countRow = `${localize.getString("count")} `;
+ let meanRow = `${meanOfX} `;
+
+ for (let ix = 0; ix < this.results.groupNames.length; ix++) {
+ nameRow += `${this.results.groupNames[ix]} `;
+ countRow += `${this.results.groupNs[ix]} `;
+ meanRow += `${ui.numberToString(this.results.groupMeans[ix], 3)} `;
+ }
+
+ nameRow += ` `;
+ countRow += ``;
+ meanRow += ``;
+
+ return `${nameRow}${meanRow}${countRow}
`;
+
+ }
+
+ makeTestDescription() {
+ return `ANOVA: ${testimate.state.x.name} by ${testimate.state.y.name}`;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.anova.menuString",
+ testimate.state.x.name, testimate.state.y.name)
+ // return `ANOVA: ${testimate.state.x.name} by ${testimate.state.y.name}`;
+ }
+
+ makeConfigureGuts() {
+ const configStart = localize.getString("tests.anova.configStart",
+ testimate.state.x.name, testimate.state.y.name)
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ let theHTML = `${configStart}: ${conf}`;
+
+ return theHTML;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/beta.js b/eepsmedia/plugins/testimate/src/tests/beta.js
new file mode 100644
index 00000000..44fc9d3b
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/beta.js
@@ -0,0 +1,43 @@
+// Implementation of the Beta probability density function
+// Without the use of factorials to overcome the problem of getting inf values
+// No external libraries required
+// For naive implementation, see beta_naive.js (removed by terickson)
+// Roy Hung 2019
+
+
+const beta = {
+ PDF: function (x, a, b) {
+ // Beta probability density function impementation
+ // using logarithms, no factorials involved.
+ // Overcomes the problem with large integers
+ return Math.exp(this.lnPDF(x, a, b))
+ },
+
+ lnPDF: function (x, a, b) {
+ // Log of the Beta Probability Density Function
+ return ((a - 1) * Math.log(x) + (b - 1) * Math.log(1 - x)) - this.lnFunc(a, b);
+ },
+
+ lnFunc: function (a, b) {
+ // Log Beta Function
+ // ln(Beta(x,y))
+ let foo = 0.0;
+
+ for (let i = 0; i < a - 2; i++) {
+ foo += Math.log(a - 1 - i);
+ }
+ for (let i = 0; i < b - 2; i++) {
+ foo += Math.log(b - 1 - i);
+ }
+ for (let i = 0; i < a + b - 2; i++) {
+ foo -= Math.log(a + b - 1 - i);
+ }
+ return foo;
+ },
+
+ func: function (x, y) {
+ // Beta Function
+ // Beta(x,y) = e^(ln(Beta(x,y))
+ return Math.exp(this.lnFunc(x, y));
+ }
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/binomial.js b/eepsmedia/plugins/testimate/src/tests/binomial.js
new file mode 100644
index 00000000..2b19069d
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/binomial.js
@@ -0,0 +1,197 @@
+const binomial =
+ {
+
+ CIbeta: function (n, k, alpha) {
+ let upper = 1;
+ let lower = 0;
+ let upperP = 1 - alpha/2;
+ let lowerP = alpha/2;
+
+ // calling order: jStat.beta.inv( p, alpha, beta );
+
+ if (k === 0) {
+ upper = jStat.beta.inv(upperP, k + 1, n - k);
+ } else if (k === n) {
+ lower = jStat.beta.inv(lowerP, k, n - k + 1);
+ } else {
+ upper = jStat.beta.inv(upperP, k + 1, n - k);
+ lower = jStat.beta.inv(lowerP, k, n - k + 1);
+ }
+ return [lower, upper];
+ },
+
+
+ findCI: function (n, k, pValue) {
+ let upper = 1;
+ let lower = 0;
+
+ if (k === 0) {
+ upper = this.findInverseCDFValue(n, k, pValue, true);
+ } else if (k === n) {
+ lower = this.findInverseCDFValue(n, k, pValue, false);
+ } else {
+ upper = this.findInverseCDFValue(n, k, pValue / 2, true);
+ lower = this.findInverseCDFValue(n, k, pValue / 2, false);
+ }
+
+ return [lower, upper]; // If no exact match is found within the precision limit
+ },
+
+ findInverseCDFValue: function (n, k, target, kOrFewer) {
+ const epsilon = 0.001;
+ const maxIter = 20;
+ let low = 0;
+ let high = 1.0;
+ let p;
+
+ let itercount = 0;
+ console.log(`starting iteration N = ${n}, k = ${k}, target = ${target}, ${kOrFewer ? "k or fewer" : "k or more"}`);
+ while (itercount < maxIter) {
+ const mid = (low + high) / 2;
+ p = mid;
+
+ console.log(`it ${itercount} [${low.toFixed(4)}, ${high.toFixed(4)}]`);
+ const cdfp = binomial.CDF(n, k, p, kOrFewer);
+
+ if (kOrFewer) {
+ if (Math.abs(target - cdfp) < epsilon) {
+ console.log(`iteration ${itercount} converges at CDF(${p}) = ${cdfp}`);
+ return p;
+ } else if (cdfp > target) {
+ low = mid;
+ } else {
+ high = mid;
+ }
+ } else { // k or more
+ if (Math.abs(target - cdfp) < epsilon) {
+ console.log(`iteration ${itercount} converges at CDF(${p}) = ${cdfp}`);
+ return p;
+ } else if (cdfp < target) {
+ low = mid;
+ } else {
+ high = mid;
+ }
+
+ }
+ itercount++;
+ }
+ alert(`fell out of loop itercount = ${itercount}`);
+ return null; // oops
+ },
+
+ /*
+ inverseCDF: function (p, n, successProbability) {
+ const epsilon = 1e-10;
+ let low = 0;
+ let high = n;
+
+ while (low <= high) {
+ const mid = Math.floor((low + high) / 2);
+ const cdfMid = binomial.CDF(mid, n, successProbability);
+
+ if (Math.abs(cdfMid - p) < epsilon) {
+ return mid;
+ } else if (cdfMid < p) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ return null; // If no exact match is found within the precision limit
+ },
+ */
+
+ /**
+ * What is the probability that you have k or fewer (or more) successes in
+ * n Bernoulli trials
+ * with probability P
+ *
+ * @param n
+ * @param k
+ * @param trueP
+ * @param kOrFewer
+ * * @returns {number}
+ * @constructor
+ */
+ CDF: function (n, k, trueP, kOrFewer) {
+ // Function to calculate the cumulative binomial distribution
+ /*
+ const binomialCoefficient = binomial.choose(n, k);
+ const probability = binomialCoefficient * Math.pow(trueP, k) * Math.pow(1 - trueP, n - k);
+ */
+
+ let cumulativeProbability = 0;
+
+ if (kOrFewer) {
+ for (let i = 0; i <= k; i++) {
+ cumulativeProbability += binomial.choose(n, i) * Math.pow(trueP, i) * Math.pow(1 - trueP, n - i);
+ }
+ } else { // we're calculating the probability for k or MORE
+ for (let i = n; i >= k; i--) {
+ cumulativeProbability += binomial.choose(n, i) * Math.pow(trueP, i) * Math.pow(1 - trueP, n - i);
+ }
+ }
+
+ return cumulativeProbability;
+ },
+
+ choose: function (n, k) {
+ // Function to calculate binomial coefficient (n choose k)
+ if (k === 0 || k === n) {
+ return 1;
+ } else {
+ return binomial.choose(n - 1, k - 1) + binomial.choose(n - 1, k);
+ }
+ },
+
+ //` testing
+
+ calculateCDF: function () {
+ const P = Number(document.getElementById("trueProb").value);
+ const k = Number(document.getElementById("successes").value);
+ const N = Number(document.getElementById("samples").value);
+
+ const NKResult = this.choose(N, k);
+ const CDFResultFewer = this.CDF(N, k, P, true);
+
+ const NPtext = `(${N} ${k}) = ${NKResult}`;
+ const CDFtext = `result: CDF(${N}, ${k}, at P = ${P}) = ${CDFResultFewer}`;
+
+ document.getElementById("result").innerHTML = `${NPtext} ${CDFtext}`;
+
+ let data = "p,cdf-,cdf+\n";
+ for (let p = 0.05; p < 1.0; p += 0.05) {
+ const cdfFewer = this.CDF(N, k, p, true);
+ const cdfMore = this.CDF(N, k, p, false);
+ data += `${p},${cdfFewer},${cdfMore} \n`;
+ }
+
+ console.log(data);
+ },
+
+ calculateCI: function () {
+ const k = Number(document.getElementById("successes").value);
+ const N = Number(document.getElementById("samples").value);
+ const pValue = Number(document.getElementById("pValue").value);
+
+ // const result = binomial.findCI(N, k, pValue);
+ const resultBeta = binomial.CIbeta(N, k, pValue);
+ let CItext = `result: CI(${N}, ${k}, p-hat = (${k / N}) for P-value = ${pValue})`;
+ // CItext += ` = [${result[0].toFixed(4)}, ${result[1].toFixed(4)}] (brute force)`;
+ CItext += ` = [${resultBeta[0].toFixed(4)}, ${resultBeta[1].toFixed(4)}] (beta)`;
+
+ document.getElementById("result").innerHTML = `${CItext}`;
+
+ }
+ }
+
+
+/*
+// Example usage:
+const pValue = 0.8; // Probability
+const nValue = 10; // Number of trials
+const probSuccess = 0.5; // Probability of success in each trial
+
+const inverseCDFResult = binomialInverseCDF(pValue, nValue, probSuccess);
+console.log("Inverse of Cumulative Binomial Distribution:", inverseCDFResult);*/
diff --git a/eepsmedia/plugins/testimate/src/tests/correlation.js b/eepsmedia/plugins/testimate/src/tests/correlation.js
new file mode 100644
index 00000000..6de79d14
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/correlation.js
@@ -0,0 +1,133 @@
+class Correlation extends Test {
+
+ constructor(iID, iGrouping) {
+ super(iID);
+ }
+
+ updateTestResults() {
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+
+ let sumXY = 0;
+ let sumX = 0;
+ let sumXX = 0;
+ let sumYY = 0;
+ let sumY = 0;
+ let N = data.xAttData.theArray.length;
+ const df = N - 2;
+
+ if (N > 2) {
+ for (let i = 0; i < N; i++) {
+ // Note how these definitions are REVERSED.
+ // we want to look at the var in the first position (xAttData) as the dependent variable (Y)
+ const X = data.yAttData.theArray[i];
+ const Y = data.xAttData.theArray[i];
+ sumX += X;
+ sumY += Y;
+ sumXY += X * Y;
+ sumXX += X * X;
+ sumYY += Y * Y;
+ }
+
+ const slope = (N * sumXY - sumX * sumY) / (N * sumXX - sumX ** 2);
+ const intercept = (sumY - slope * sumX) / N;
+ const SDsqError = 1 / (N * (N - 2)) * (N * sumYY - sumY ** 2 - slope ** 2 * (N * sumXX - sumX ** 2));
+ const SDsqSlope = N * SDsqError / (N * sumXX - sumX ** 2);
+ const SDsqIntercept = SDsqSlope / N * sumXX;
+ const rho = (N * sumXY - sumX * sumY) /
+ Math.sqrt((N * sumXX - sumX ** 2) * (N * sumYY - sumY ** 2));
+ const rsq = rho * rho;
+
+ // test for rho ≠ 0 from https://online.stat.psu.edu/stat501/lesson/1/1.9
+
+ this.results.N = N;
+ this.results.df = df;
+ this.results.tCrit = jStat.studentt.inv(theCIparam, df); // 1.96-ish for 0.95
+ this.results.rho = rho;
+ this.results.rsq = rsq;
+
+ // test correlation against ZERO
+ this.results.t = rho * Math.sqrt(df/(1 - rsq));
+
+ // CI calculations, see https://www.statology.org/confidence-interval-correlation-coefficient/
+ const zr = Math.log((1 + rho)/(1 - rho)) / 2.0 ;
+ const halfWidth = this.results.tCrit / Math.sqrt(N - 3);
+ const L = zr - halfWidth;
+ const U = zr + halfWidth;
+
+ this.results.CImin = (Math.exp(2 * L) - 1) / (Math.exp(2 * L) + 1); // numeric value
+ this.results.CImax = (Math.exp(2 * U) - 1) / (Math.exp(2 * U) + 1); // numeric value
+
+ const tAbs = Math.abs(this.results.t);
+ this.results.P = jStat.studentt.cdf(-tAbs, this.results.df);
+ if (testimate.state.testParams.sides === 2) this.results.P *= 2;
+ }
+
+ }
+
+ makeResultsString() {
+ // const testDesc = `mean of ${testimate.state.x.name}`;
+ const N = this.results.N;
+
+ const rho = ui.numberToString(this.results.rho); // correlation
+ const rsq = ui.numberToString(this.results.rsq); // r^2, coeff of deter
+
+ const CImin = ui.numberToString(this.results.CImin); // CI of correlation
+ const CImax = ui.numberToString(this.results.CImax);
+
+ const df = ui.numberToString(this.results.df);
+
+ const t = ui.numberToString(this.results.t, 3);
+ const tCrit = ui.numberToString(this.results.tCrit, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+
+ const theSign = rho >= 0 ? "+" : '-';
+
+ const X = testimate.state.x.name;
+ const Y = testimate.state.y.name;
+
+ const DSdetails = document.getElementById("DSdetails");
+ const DSopen = DSdetails && DSdetails.hasAttribute("open");
+
+ const testingSlopePhrase = localize.getString("tests.regression.testingSlope");
+ const slopeWord = localize.getString("slope");
+ const interceptWord = localize.getString("intercept");
+
+ let out = "";
+
+ // out += `How does (${X}) depend on (${Y})?`
+ out += localize.getString("tests.correlation.testQuestion",
+ X, Y, testimate.state.testParams.theSidesOp, testimate.state.testParams.value.toString());
+ out += ` ρ = ${rho}, r2 = ${rsq}, N = ${N}`; // note reversal!
+ out += ` t = ${t}, ${P}`;
+ out += ` ${localize.getString("CI")} = [${CImin}, ${CImax}]`;
+ out += ` df = ${df}, α = ${alpha}, t* = ${tCrit}, `
+ out += ` `;
+
+ return out;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.correlation.menuString",testimate.state.x.name, testimate.state.y.name);
+ }
+
+ makeConfigureGuts() {
+ const testingCorrelationPhrase = localize.getString("tests.correlation.testingCorrelation");
+
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = "0"; // ui.valueBoxHTML(testimate.state.testParams.value);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ let theHTML = `${testingCorrelationPhrase} ${sides} ${value}, ${conf}`;
+
+ return theHTML;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/goodness.js b/eepsmedia/plugins/testimate/src/tests/goodness.js
new file mode 100644
index 00000000..d6d2c024
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/goodness.js
@@ -0,0 +1,220 @@
+class Goodness extends Test {
+
+ constructor(iID) {
+ super(iID);
+ this.results.expected = {};
+ this.results.observed = {};
+ this.results.groupNames = [];
+ if (!testimate.restoringFromSave) {
+ testimate.state.testParams.groupProportions = {};
+ }
+
+ // testimate.state.testParams.sides = 1;
+ }
+
+ updateTestResults() {
+
+ const A = data.xAttData.theArray;
+ this.results.N = A.length;
+ const tempNames = [...data.xAttData.valueSet];
+ this.results.groupNames = tempNames.map( n => String(n));
+
+ testimate.state.testParams.groupProportions = this.getExpectations();
+
+ this.results.groupNames.forEach( v => {
+ this.results.observed[v] = 0;
+ this.results.expected[v] = this.results.N * testimate.state.testParams.groupProportions[v];
+ })
+
+ //`count the observed values in each category
+ A.forEach( a => {
+ this.results.observed[a]++;
+ })
+
+ // counts array now has all counts.
+
+ this.results.chisq = 0;
+
+ this.results.groupNames.forEach( v => {
+ const cellValue = (this.results.observed[v] - this.results.expected[v])**2
+ / this.results.expected[v];
+ this.results.chisq += cellValue;
+ })
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / testimate.state.testParams.sides; // the large number
+ this.results.df = this.results.groupNames.length - 1;
+ this.results.chisqCrit = jStat.chisquare.inv(theCIparam, this.results.df); //
+ this.results.P = 1 - jStat.chisquare.cdf(this.results.chisq, this.results.df);
+ }
+
+ makeResultsString() {
+
+ const N = this.results.N;
+ const chisq = ui.numberToString(this.results.chisq);
+ const chisqCrit = ui.numberToString(this.results.chisqCrit);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const df = ui.numberToString(this.results.df, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+
+ const GFdetails = document.getElementById("GFdetails");
+ const GFopen = GFdetails && GFdetails.hasAttribute("open");
+
+ let out = "";
+ out += localize.getString("tests.goodness.testQuestion", data.xAttData.name);
+ // out += `Are the proportions of ${data.xAttData.name} as hypothesized?`;
+ out += ` N = ${N}, ${this.results.groupNames.length} ${localize.getString("groups")}, χ2 = ${chisq}, ${P}`;
+ out += ``;
+ out += localize.getString("tests.goodness.detailsSummary1", testimate.state.testParams.sides);
+ out += this.makeGoodnessTable();
+ out += ` df = ${df}, α = ${alpha}, χ2 * = ${chisqCrit} `;
+ out += ` `;
+
+ out += ` `;
+ return out;
+ }
+
+ makeGoodnessTable() {
+
+ let nameRow = `${data.xAttData.name} = `;
+ let observedRow = `${localize.getString("observed")} `;
+ let expectedRow = `${localize.getString("expected")} `;
+
+ this.results.groupNames.forEach( v => {
+ nameRow += `${v} `;
+ observedRow += `${this.results.observed[v]} `;
+ expectedRow += `${ui.numberToString(this.results.expected[v], 3)} `;
+ })
+
+ nameRow += ` `;
+ observedRow += ``;
+ expectedRow += ``;
+
+ return `${nameRow}${observedRow}${expectedRow}
`;
+ }
+
+ getExpectations() {
+ let out = {};
+
+ let needFresh = false;
+
+ const oldGroups = Object.keys(testimate.state.testParams.groupProportions);
+ // problem here: oldGroups is now an array of STRINGS, even if the keys were numbers.
+ // (Titanic "Class", {1,2,3} rendered as categorical, now we're doing goodness of fit.)
+
+ const newGroups = this.results.groupNames;
+
+ let sum = 0;
+
+ /*
+ for each old group, if it's also a new group,
+ give it that old proportion as a first guess
+ */
+ oldGroups.forEach( old => {
+ if (newGroups.includes(old)) { // there is a match!
+ let newVal = testimate.state.testParams.groupProportions[old];
+ if (sum + newVal > 1) {
+ newVal = 1 - sum;
+ }
+ out[old] = newVal;
+ sum += newVal;
+ }
+ })
+
+ // how many do we still have to find?
+ const leftOut = newGroups.length - Object.keys(out).length;
+
+ /*
+ for each new group, is it left out?
+ if so, give it that fraction of what's left to be allocated.
+ */
+ newGroups.forEach(n => {
+ if (!out.hasOwnProperty(n)) { // haven't done it yet!
+ out[n] = (1 - sum)/leftOut;
+ }
+ })
+
+ return out;
+ }
+
+ makeTestDescription( ) {
+ return `goodness of fit: ${testimate.state.x.name}`;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.goodness.menuString",testimate.state.x.name);
+ // return `goodness of fit for ${testimate.state.x.name}`;
+ }
+
+ makeConfigureGuts() {
+ const sides12Button = ui.sides12ButtonHTML(testimate.state.testParams.sides);
+ const alpha = ui.alphaBoxHTML(testimate.state.testParams.alpha);
+
+ let theHTML = `${localize.getString("tests.goodness.configurationStart")}`;
+ theHTML += ` ${alpha} ${sides12Button}`;
+
+
+ let nameRow = `${testimate.state.x.name} → `;
+ let valueRow = `${this.equalExpectationsButton()} `;
+
+ // is the goodness-of-fit configuration details element [extant and] open?
+
+ const GFConfigDetails = document.getElementById("GFConfigDetails");
+ const GFConfigOpen = GFConfigDetails && GFConfigDetails.hasAttribute("open");
+
+ // start the GF details element
+
+ theHTML += ``;
+ theHTML += localize.getString("tests.goodness.detailsSummary2");
+
+ // start the table of values. These are not results per se, but we class the table that way.
+
+ theHTML += ``;
+ theHTML += ` `;
+
+ return theHTML;
+ }
+
+ equalExpectationsButton( ) {
+ const theTip = localize.getString("tips.equalize");
+ const theLabel = localize.getString("equalize") + " →";
+ return ` `
+ }
+
+ static equalizeExpectations() {
+ const theProportions = testimate.state.testParams.groupProportions;
+ const theShares = Object.keys(theProportions).length;
+ const theEqualShare = 1.0 / theShares;
+ for (let group in theProportions) {
+ if (theProportions.hasOwnProperty(group)) {
+ theProportions[group] = theEqualShare;
+ }
+ }
+ testimate.refreshDataAndTestResults();
+ }
+
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/independence.js b/eepsmedia/plugins/testimate/src/tests/independence.js
new file mode 100644
index 00000000..a807ed1e
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/independence.js
@@ -0,0 +1,157 @@
+class Independence extends Test {
+
+ constructor(iID) {
+ super(iID);
+ this.results.rowLabels = [];
+ this.results.columnLabels = [];
+ this.results.observed = null;
+ this.results.expected = null;
+
+ // testimate.state.testParams.sides = 1;
+ }
+
+ updateTestResults() {
+
+ const X = data.xAttData.theArray; // row-attribute data
+ const Y = data.yAttData.theArray; // column-attribute data
+ this.results.N = X.length;
+
+ this.results.rowLabels = [...data.xAttData.valueSet]; // x is vertical, row labels
+ this.results.columnLabels = [...data.yAttData.valueSet];
+
+ this.results.observed = this.makeZeroMatrix(this.results.columnLabels.length, this.results.rowLabels.length);
+ this.results.expected = this.makeZeroMatrix(this.results.columnLabels.length, this.results.rowLabels.length);
+
+ this.results.rowTotals = new Array(this.results.rowLabels.length).fill(0);
+ this.results.columnTotals = new Array(this.results.columnLabels.length).fill(0);
+
+ for (let r = 0; r < this.results.rowLabels.length; r++) {
+ for (let c = 0; c < this.results.columnLabels.length; c++) {
+ this.results.observed[c][r] = 0;
+ }
+ }
+
+ // loop over all data
+ // count the observed values in each cell, update row and column totals
+
+ for (let ix = 0; ix < X.length; ix++) {
+ const row = this.results.rowLabels.indexOf(X[ix]);
+ const column = this.results.columnLabels.indexOf(Y[ix]);
+ this.results.observed[column][row]++;
+ this.results.rowTotals[row]++
+ this.results.columnTotals[column]++;
+ }
+
+ // calculate expected values and chisquare contributions
+ this.results.chisq = 0;
+
+ for (let r = 0; r < this.results.rowLabels.length; r++) {
+ for (let c = 0; c < this.results.columnLabels.length; c++) {
+ this.results.expected[c][r] = this.results.columnTotals[c] * this.results.rowTotals[r] / this.results.N;
+ const contrib = (this.results.observed[c][r] - this.results.expected[c][r]) ** 2
+ / this.results.expected[c][r];
+ this.results.chisq += contrib
+ }
+ }
+
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / testimate.state.testParams.sides; // 2; // the large number
+ this.results.df = (this.results.rowLabels.length - 1) * (this.results.columnLabels.length - 1);
+ this.results.chisqCrit = jStat.chisquare.inv(theCIparam, this.results.df); //
+ this.results.P = 1 - jStat.chisquare.cdf(this.results.chisq, this.results.df);
+ }
+
+ makeResultsString() {
+ const N = this.results.N;
+ const chisq = ui.numberToString(this.results.chisq);
+ const chisqCrit = ui.numberToString(this.results.chisqCrit);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const df = ui.numberToString(this.results.df, 3);
+ // const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+
+ const TIdetails = document.getElementById("TIdetails");
+ const TIopen = TIdetails && TIdetails.hasAttribute("open");
+
+ let out = "";
+ out += localize.getString("tests.independence.testQuestion",
+ testimate.state.y.name, testimate.state.x.name);
+ out += ` N = ${N}, ${this.results.columnLabels.length} columns by ${this.results.rowLabels.length} rows, `
+ out += `χ2 = ${chisq}, ${P}`;
+ out += ``;
+ out += localize.getString("tests.independence.detailsSummary", testimate.state.testParams.sides);
+ out += this.makeIndependenceTable();
+ out += ` df = ${df}, α = ${alpha}, χ2 * = ${chisqCrit} `;
+ out += ` `;
+
+ out += ` `;
+ return out;
+ }
+
+ makeIndependenceTable() {
+
+ let headerRow = `${localize.getString("observed")} ${localize.getString("expected")} ${data.yAttData.name} = `;
+ let tableRows = "";
+/*
+ let observedRow = `${localize.getString("observed")} `;
+ let expectedRow = `${localize.getString("expected")} `;
+*/
+
+ // construct a header
+
+ for (let c = 0; c < this.results.columnLabels.length; c++) {
+ const col = this.results.columnLabels[c]; // the string label
+ headerRow += `${col} `; // column value in the header
+ }
+ headerRow += ` `;
+
+ // now loop over rows, making a column inside each...
+
+ for (let r = 0; r < this.results.rowLabels.length; r++) {
+ const row = this.results.rowLabels[r]; // the string row label
+ const attLabel = (r === 0) ? `${data.xAttData.name} = ` : ` `;
+ let thisRow = `${attLabel}${row} `;
+ for (let c = 0; c < this.results.columnLabels.length; c++) {
+ const exp = ui.numberToString(this.results.expected[c][r], 4);
+ const col = this.results.columnLabels[c]; // the string label
+ thisRow += `${this.results.observed[c][r]} ${exp} `; // observed value in the cell
+ }
+ thisRow += ``;
+ tableRows += thisRow;
+ }
+
+ return ``;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.independence.menuString",
+ testimate.state.y.name,testimate.state.x.name);
+ }
+
+ makeConfigureGuts() {
+ const sides12Button = ui.sides12ButtonHTML(testimate.state.testParams.sides);
+
+ const start = localize.getString("tests.independence.configurationStart",
+ testimate.state.y.name, testimate.state.x.name);
+ // const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ const alpha = ui.alphaBoxHTML(testimate.state.testParams.alpha);
+ let theHTML = `${start}: ${alpha} ${sides12Button}`;
+
+ return theHTML;
+ }
+
+ makeZeroMatrix(cols, rows) {
+ let A = new Array(cols);
+ for (let c = 0; c < cols; c++) {
+ A[c] = new Array(rows).fill(0);
+ }
+ return A;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/logistic.js b/eepsmedia/plugins/testimate/src/tests/logistic.js
new file mode 100644
index 00000000..79b7865f
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/logistic.js
@@ -0,0 +1,370 @@
+/**
+ * Methods for logistic regression.
+ *
+ * Math notes! We will be using the function `logisticregression()` below to iterate on this function:
+ *
+ * f(x) = 1/(1 + exp(-(b + wx))
+ *
+ * finding values for b and w, which are kinda-sorta slope and intercept, that is,
+ * a large value for w means that the logistic curve is steeper,
+ * and a large b means that the place where the curve passes 1/2 and inflects is farther from 0.
+ *
+ * For thinking purposes, we can transform that function, using
+ *
+ * p = -(b/w) and m = (w/4). (so b = -4mp and w = 4m)
+ *
+ * This gives
+ *
+ * f(x) = 1/(1 + exp(-4m(x-p))
+ *
+ * which has the happy result that p is the (x) position of that inflection point
+ * and m is the slope of the curve at that point.
+ *
+ * p becomes this.results.pos
+ *
+ */
+class Logistic extends Test {
+
+ constructor(iID) {
+ super(iID);
+
+ //if (!testimate.restoringFromSave) {
+ testimate.state.testParams.rate = 0.1;
+ testimate.state.testParams.iter = 100;
+ testimate.state.testParams.probe = null; // what value of the predictor do we want to find a probability for?
+ testimate.state.testParams.focusGroupX = null; // what value gets cast as "1"? The rest are "0"
+
+ //}
+
+ this.graphShowing = false;
+ this.newRegression = true; // would be false if we were addiing on additional iterations
+ this.moreIterations = 0; // and that's how many!
+
+ if (!testimate.restoringFromSave || !testimate.state.testParams.focusGroupX) {
+ testimate.state.testParams.focusGroupX = testimate.state.focusGroupDictionary[data.xAttData.name];
+ }
+
+
+ }
+
+ async updateTestResults() {
+ testimate.OKtoRespondToCaseChanges = false;
+
+ const X0 = data.xAttData.theArray;
+ const Y = data.yAttData.theArray;
+ const N = X0.length;
+ this.results.N = N;
+
+ if (N !== Y.length) {
+ alert(`Paired arrays are not the same length! Bogus results ahead!`);
+ }
+
+ // this will also make the extra column of coded data values if it did not exist before
+ await testimate.setLogisticFocusGroup(data.xAttData, testimate.state.testParams.focusGroupX); // the first, by default
+
+ const X = X0.map(x => {
+ return (x === testimate.state.testParams.focusGroupX) ? 1 : 0;
+ })
+
+ let iterations = testimate.state.testParams.iter;
+
+ if (this.newRegression) {
+ // compute mean of Y to give initial value for pos
+ let theMax = -Infinity;
+ let theMin = Infinity;
+ let pos0 = 0;
+ Y.forEach(y => {
+ pos0 += y;
+ if (y > theMax) theMax = y;
+ if (y < theMin) theMin = y;
+ }) // add up all the pos
+ pos0 /= N; // to get the mean position
+
+ console.log(` logistic regression: initial critical position: ${pos0}`);
+ if (!testimate.state.testParams.probe) testimate.state.testParams.probe = pos0;
+ this.results.pos = pos0;
+ this.results.LSlope = 0;
+ this.results.iterations = 0;
+ this.results.rangeX = theMax - theMin;
+
+ // note: results.iterations is the total number of iterations (and its get emitted);
+ // testParams.iter is the number we're running right now
+ } else {
+ iterations = this.moreIterations;
+ this.newRegression = true; // reset!
+ }
+
+ const theResult
+ = await this.logisticRegressionUsingCurvature(
+ X, Y,
+ testimate.state.testParams.rate,
+ iterations, // how many we're running now
+ this.results.LSlope, this.results.pos
+ );
+
+ if (this.graphShowing) {
+ content.showLogisticGraph(this.makeFormulaString().longFormula);
+ }
+
+ this.results.iterations += Number(iterations);
+ this.results.LSlope = theResult.currentSlope;
+ this.results.pos = theResult.currentPos;
+ this.results.cost = theResult.currentCost;
+
+ testimate.OKtoRespondToCaseChanges = true;
+ }
+
+ makeFormulaString() {
+ const longSlope = this.results.LSlope;
+ const shortSlope = ui.numberToString(this.results.LSlope, 4);
+ const shortPos = ui.numberToString(this.results.pos, 4);
+ const longPos = this.results.pos;
+
+ // shortFormula is for screen display, so has the attribute name
+ // longFormula is for actual use, and uses "x". Avoids trying to insert backtick...
+ const shortFormula = `1/(1 + e^(-4 * ${shortSlope} * (${data.yAttData.name} - ${shortPos})))`;
+ const longFormula = `1/(1 + e^(-4 * ${longSlope} * (x - ${longPos})))`;
+
+ return {shortFormula, longFormula};
+ }
+
+ makeResultsString() {
+ const N = this.results.N;
+ const cost = ui.numberToString(this.results.cost, 4);
+ const LSlope = ui.numberToString(this.results.LSlope, 4);
+ const pos = ui.numberToString(this.results.pos, 4);
+ const LRPbox = ui.logisticRegressionProbeBoxHTML(testimate.state.testParams.probe);
+ const graphButton = ui.makeLogisticGraphButtonHTML();
+ const theFormulas = this.makeFormulaString();
+ const theShortFormula = theFormulas.shortFormula;
+ const theLongFormula = theFormulas.longFormula;
+
+ console.log(theLongFormula);
+
+ const more10button = ` N = ${N}, ${this.results.iterations} ${localize.getString("iterations")}, ${localize.getString("cost")} = ${cost} ${more10button} `;
+
+ // model
+ out += ` ${localize.getString("tests.logistic.model1", testimate.state.y.name, pos)}.`
+ out += ` ${localize.getString("tests.logistic.model2", LSlope)}`;
+ out += ` ${localize.getString("tests.logistic.probFunctionHead")}`
+ out += ` prob(${data.xAttData.name} = ${testimate.state.testParams.focusGroupX}) = ${theShortFormula}`;
+
+ out += ` ${graphButton} `;
+ out += ` `;
+
+ out += ` `;
+ out += localize.getString("tests.logistic.probQuery1", testimate.state.x.name, testimate.state.testParams.focusGroupX);
+ out += ` ${localize.getString("tests.logistic.probQuery2", testimate.state.y.name)} = ${LRPbox}`;
+
+ if (testimate.state.testParams.probe) {
+ const z = 4 * LSlope * (testimate.state.testParams.probe - pos);
+ const probNumber = this.sigmoid(z);
+ let probString = "0.000"
+ if (probNumber > 0.0000001) {
+ probString = ui.numberToString(probNumber, 3);
+ }
+
+ out += ` P(${testimate.state.testParams.focusGroupX}) = ${probString}`;
+ }
+ out += ``;
+ return out;
+ }
+
+ makeTestDescription() {
+ return `logistic regression: ${data.xAttData.name} as a function of ${data.yAttData.name}`;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.logistic.menuString",
+ testimate.state.x.name, testimate.state.y.name);
+ // return `logistic regression: ${data.xAttData.name} as a function of ${data.yAttData.name}`;
+ }
+
+ makeConfigureGuts() {
+ const rate = ui.rateBoxHTML(testimate.state.testParams.rate, 1.0, 0.01);
+ const iter = ui.iterBoxHTML(testimate.state.testParams.iter);
+ const group = ui.focusGroupButtonXHTML(testimate.state.testParams.focusGroupX);
+ const showGraph = ui.makeLogisticGraphButtonHTML();
+
+ const rateWord = localize.getString("rate");
+ const iterationsWord = localize.getString("iterations");
+
+ let theHTML = localize.getString("tests.logistic.configStart",
+ testimate.state.x.name, group, testimate.state.y.name);
+
+ theHTML += ` ${rateWord} = ${rate} ${iterationsWord} = ${iter}`;
+ return theHTML;
+ }
+
+ sigmoid(z) {
+ return 1 / (1 + Math.exp(-z));
+ }
+
+
+ async logisticRegressionUsingCurvature(outcome, predictor, alpha, iterations, slope0 = 0, pos0 = 0) {
+
+ function sigmoid(z) {
+ return 1 / (1 + Math.exp(-z));
+ }
+
+ function oneCost(xx, yy, slope, pos) {
+ const z = 4 * slope * (xx - pos);
+ const prediction = sigmoid(z);
+ let dCost = 0
+ if (prediction !== 0 && prediction !== 1) {
+ dCost = yy * Math.log(prediction) + (1 - yy) * Math.log(1 - prediction);
+ }
+ return dCost;
+ }
+
+ function cost(slope, pos) {
+ let cost = 0;
+
+ for (let i = 0; i < outcome.length; i++) {
+ cost -= oneCost(predictor[i], outcome[i], slope, pos);
+ }
+ return cost;
+ }
+
+ function getCost(slope, pos) {
+ const theCost = cost(slope, pos);
+ return theCost;
+ }
+
+ function gradientPartials(slope, pos, hs, hp) {
+
+ const theCost = getCost(slope, pos),
+ costPlusSlope = getCost(slope + hs, pos),
+ costMinusSlope = getCost(slope - hs, pos),
+ costPlusPos = getCost(slope, pos + hp),
+ costMinusPos = getCost(slope, pos - hp);
+
+ const dCostdSlope = (costPlusSlope - costMinusSlope) / (2 * hs),
+ dCostdSlopePlus = (costPlusSlope - theCost) / hs,
+ dCostdSlopeMinus = (theCost - costMinusSlope) / hs;
+ const dCostdPos = (costPlusPos - costMinusPos) / (2 * hp),
+ dCostdPosPlus = (costPlusPos - theCost) / hp,
+ dCostdPosMinus = (theCost - costMinusPos) / hp;
+
+
+ const d2CostdSlope2 = (dCostdSlopePlus - dCostdSlopeMinus) / hs;
+ const d2CostdPos2 = (dCostdPosPlus - dCostdPosMinus) / hp;
+
+ return {theCost, dCostdSlope, d2CostdSlope2, dCostdPos, d2CostdPos2};
+ }
+
+ function descendPartialOneIteration(slope, pos, alpha) {
+
+ const theResults = testimate.theTest.results;
+
+ const hs = 1 / theResults.rangeX / 1.0e4; // h for slope calculations
+ const hp = theResults.rangeX / 1.0e4; // h for p (pos) calculations
+
+ const gradientStuff = gradientPartials(slope, pos, hs, hp);
+ const projectedDSlope = (gradientStuff.d2CostdSlope2 !== 0) ? -gradientStuff.dCostdSlope / gradientStuff.d2CostdSlope2 : 0,
+ projectedDPos = (gradientStuff.d2CostdPos2 !== 0) ? -gradientStuff.dCostdPos / gradientStuff.d2CostdPos2 : 0;
+
+ const
+ newSlope = slope + projectedDSlope * alpha,
+ newPos = pos + projectedDPos * alpha,
+ theCost = gradientStuff.theCost;
+
+ return {newSlope, newPos, theCost, hs, hp};
+ }
+
+ // Done with defining functions. Actual method starts here!
+
+ let record = "iter, m, p, cost, hm, hp";
+ let currentSlope = slope0;
+ let currentPos = pos0;
+ let currentCost = 0;
+
+ for (let iter = 1; iter <= iterations; iter++) {
+ const newVals = descendPartialOneIteration(currentSlope, currentPos, alpha);
+
+ currentSlope = newVals.newSlope;
+ currentPos = newVals.newPos;
+ currentCost = newVals.theCost;
+
+ if (iter % 17 === 0 || iter < 6) {
+ record += `\n${iter}, ${currentSlope}, ${currentPos}, ${currentCost}, ${newVals.hs}, ${newVals.hp}`;
+ }
+ }
+
+ console.log('\n' + record);
+ return {currentSlope, currentPos, currentCost};
+ }
+
+ /*
+ GPT_LogisticRegression(x, y, alpha, iterations) {
+ // Initialize weights and bias
+ let w = 0;
+ let b = 10;
+ let slope = w / 4;
+ let pos = -b / w;
+ let record = "";
+
+ // Number of samples
+ const N = x.length;
+
+ record += "iter, m, p, costper, hs, hp";
+
+ for (let iter = 1; iter < iterations; iter++) {
+ let cost = 0;
+ let dw = 0;
+ let db = 0;
+
+ for (let i = 0; i < N; i++) {
+ const xi = x[i];
+ const yi = y[i];
+
+ // Compute prediction using the sigmoid function
+ const z = w * xi + b;
+ const prediction = this.sigmoid(z);
+
+ // Compute cost. It's the log of the absolute distance of the point from the model
+ // note that yi is either zero or one, so only one term survives.
+ //
+ cost -= yi * Math.log(prediction) + (1 - yi) * Math.log(1 - prediction);
+
+ // Compute gradients
+ const gradient = prediction - yi;
+ dw += xi * gradient;
+ db += gradient;
+ }
+
+ // Update weights and bias
+ slope = w / 4;
+ pos = -b / w;
+
+
+ if (iter % 100 === 0) {
+ record += `\n${iter},${slope},${pos},${cost / N}`;
+ }
+ // Print the cost for every 1000 iterations
+ /!*
+ if (iter % 1000 === 0) {
+ console.log(`Iteration ${iter}: Cost = ${cost / N}`);
+ }
+ *!/
+ }
+
+ console.log('\n' + record);
+
+ return {w, b};
+ }
+ */
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/one-sample-p.js b/eepsmedia/plugins/testimate/src/tests/one-sample-p.js
new file mode 100644
index 00000000..7f49b9e3
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/one-sample-p.js
@@ -0,0 +1,159 @@
+
+class OneSampleP extends Test {
+
+ usingBinomial = false;
+
+ constructor(iID) {
+ super(iID);
+
+ // get a default "group" -- the value we count as "success" for proportions
+ if (!testimate.restoringFromSave || !testimate.state.testParams.focusGroupX) {
+ testimate.state.testParams.focusGroupX = testimate.state.focusGroupDictionary[data.xAttData.name];
+ /*
+ testimate.state.testParams.value
+ = testimate.state.valueDictionary[this.testID]
+ ? testimate.state.valueDictionary[this.testID] : 0.5;
+ */
+ }
+
+ }
+
+ async updateTestResults() {
+ // todo: use exact binomial for small N, prop near 0 or 1
+ const A = data.xAttData.theArray;
+ const G = testimate.state.testParams.focusGroupX;
+
+ let N = 0;
+ this.results.successes = 0;
+ A.forEach( x => {
+ N++;
+ if (x === G) this.results.successes++;
+ })
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+
+ if (N > 0) {
+ const p0 = testimate.state.testParams.value;
+ const pHat = this.results.successes / N; // sample proportion p-hat
+ this.results.N = N;
+ this.results.prop = pHat;
+
+ this.usingBinomial = (N * pHat < 10) || (N * (1 - pHat) < 10); // must have ≥ 10 successes AND 10 failures
+
+ if (this.usingBinomial) {
+
+ /**
+ * jStat.binomial.cdf(k, N, p) is the probability that you get between 0 and k successes in N trials
+ */
+ if (pHat > p0) { // the sample prop is high, we'll find the upper tail
+ this.results.P = 1 - jStat.binomial.cdf(this.results.successes - 1, this.results.N, p0); //
+ } else { // the sample prop is LOW, we'll find the lower tail
+ this.results.P = jStat.binomial.cdf(this.results.successes, this.results.N, p0); //
+ }
+ if (testimate.state.testParams.sides === 2) this.results.P *= 2;
+ if (this.results.P > 1) this.results.P = 1.00;
+
+ this.results.SE = Math.sqrt((this.results.prop) * (1 - this.results.prop) / this.results.N);
+ this.results.z = "";
+ this.results.zCrit = "";
+
+ const binomialResult = binomial.CIbeta(N, this.results.successes, testimate.state.testParams.alpha);
+ this.results.CImin = binomialResult[0];
+ this.results.CImax = binomialResult[1];
+
+ } else { // not using binomial, using z
+
+ this.results.SE = Math.sqrt((pHat) * (1 - pHat) / N);
+ const SEnull = Math.sqrt((p0) * (1 - p0) / N);
+
+ // Note: test uses the SE of the null hypothesis (value); CI uses the SE of the sample.
+ this.results.z = (pHat - p0) / SEnull; // this.results.SE;
+
+ this.results.zCrit = jStat.normal.inv(theCIparam, 0, 1); // 1.96-ish for 0.95
+ const zAbs = Math.abs(this.results.z);
+ this.results.P = jStat.normal.cdf(-zAbs, 0, 1);
+ if (testimate.state.testParams.sides === 2) this.results.P *= 2;
+
+ // Note: CI uses the SE of the sample (this.results.SE)
+ this.results.CImax = pHat + this.results.zCrit * this.results.SE;
+ this.results.CImin = pHat - this.results.zCrit * this.results.SE;
+ }
+ }
+ }
+
+ makeResultsString() {
+
+ const N = this.results.N;
+ const successes = ui.numberToString(this.results.successes);
+ const prop = ui.numberToString(this.results.prop, 4);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const CImin = ui.numberToString(this.results.CImin);
+ const CImax = ui.numberToString(this.results.CImax);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+ const value = ui.numberToString(testimate.state.testParams.value);
+ const sidesOp = testimate.state.testParams.theSidesOp;
+
+ let out = "";
+ const testQuestion = localize.getString("tests.oneSampleP.testQuestion",
+ data.xAttData.name, testimate.state.testParams.focusGroupX, sidesOp, value);
+ const r1 = localize.getString( "tests.oneSampleP.resultsLine1", prop, successes, N);
+
+ out += testQuestion;
+ out += ` ${r1}`;
+
+ if (this.usingBinomial) {
+ out += ` ${P}`;
+ out += ` ${conf}% ${localize.getString("CI")} = [${CImin}, ${CImax}]`;
+ out += ` (${localize.getString("tests.oneSampleP.usingBinomialProc")})`;
+
+ } else {
+ const SE = ui.numberToString(this.results.SE);
+ const zCrit = ui.numberToString(this.results.zCrit, 3);
+ const z = ui.numberToString(this.results.z, 3);
+
+ out += ` z = ${z}, ${P}`;
+ out += ` ${conf}% ${localize.getString("CI")} = [${CImin}, ${CImax}]`;
+ out += ` SE = ${SE}, α = ${alpha}, z* = ${zCrit}`;
+ out += ` (${localize.getString("tests.oneSampleP.usingZProc")})`;
+ }
+
+ out += ` `;
+ return out;
+ }
+
+ makeTestDescription(iTestID, includeName) {
+ return `mean of ${testimate.state.x.name}`;
+ return
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ if(!testimate.state.focusGroupDictionary[data.xAttData.name]) {
+ testimate.setFocusGroup(data.xAttData, null);
+ }
+ const rememberedGroup = testimate.state.focusGroupDictionary[data.xAttData.name];
+
+ return localize.getString("tests.oneSampleP.menuString",
+ testimate.state.x.name, rememberedGroup);
+ }
+
+ makeConfigureGuts() {
+ const configStart = localize.getString("tests.oneSampleP.configurationStart");
+
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = ui.valueBoxHTML(testimate.state.testParams.value, 0.0, 1.0, 0.05);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ const group = ui.focusGroupButtonXHTML(testimate.state.testParams.focusGroupX);
+ let theHTML = `${configStart}(${data.xAttData.name} = ${group}) ${sides} ${value} ${conf}`;
+
+ return theHTML;
+ }
+
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/one-sample-t.js b/eepsmedia/plugins/testimate/src/tests/one-sample-t.js
new file mode 100644
index 00000000..89652a2f
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/one-sample-t.js
@@ -0,0 +1,90 @@
+class OneSampleT extends Test {
+
+ constructor(iID) {
+ super(iID);
+
+ testimate.state.testParams.value
+ = testimate.state.valueDictionary[this.testID]
+ ? testimate.state.valueDictionary[this.testID] : 0;
+
+ }
+
+ updateTestResults() {
+ const jX = jStat(data.xAttData.theArray); // jStat version of x array
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+
+ this.results.N = jX.cols();
+ this.results.df = this.results.N - 1;
+ this.results.mean = jX.mean();
+ this.results.s = jX.stdev(true); // true means SAMPLE SD
+ this.results.SE = this.results.s / Math.sqrt(this.results.N);
+ this.results.P = jX.ttest(testimate.state.testParams.value, testimate.state.testParams.sides);
+ this.results.tCrit = jStat.studentt.inv(theCIparam, this.results.df); // 1.96-ish for 0.95
+ this.results.CImax = this.results.mean + this.results.tCrit * this.results.SE;
+ this.results.CImin = this.results.mean - this.results.tCrit * this.results.SE;
+ this.results.t = (this.results.mean - testimate.state.testParams.value) / this.results.SE;
+ }
+
+ makeResultsString() {
+
+ const testDesc = `mean of ${testimate.state.x.name}`;
+
+ const N = this.results.N;
+ const mean = ui.numberToString(this.results.mean, 3);
+ const s = ui.numberToString(this.results.s);
+ const SE = ui.numberToString(this.results.SE);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const CImin = ui.numberToString(this.results.CImin);
+ const CImax = ui.numberToString(this.results.CImax);
+ const tCrit = ui.numberToString(this.results.tCrit, 3);
+ const df = ui.numberToString(this.results.df, 3);
+ const t = ui.numberToString(this.results.t, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+ const value = ui.numberToString(testimate.state.testParams.value);
+
+ const testQuestion = localize.getString("tests.oneSampleT.testQuestion",
+ data.xAttData.name, testimate.state.testParams.theSidesOp, value);
+ const r2 = localize.getString("tests.oneSampleT.resultsLine2", mean, conf, CImin, CImax);
+
+ let out = "";
+
+ out += testQuestion;
+ out += ` N = ${N}, t = ${t}, ${P}`;
+ out += ` ${r2}`;
+ out += ` s = ${s}, SE = ${SE}, df = ${df}, α = ${alpha}, t* = ${tCrit}`;
+ out += ` `;
+
+ out += ` `;
+ return out;
+ }
+
+ makeTestDescription( ) {
+ return `mean of ${testimate.state.x.name}`;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.oneSampleT.menuString", testimate.state.x.name);
+
+ // return `one-sample t mean of ${testimate.state.x.name}`;
+ }
+
+ makeConfigureGuts() {
+ const configStart = localize.getString("tests.oneSampleT.configurationStart");
+
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = ui.valueBoxHTML(testimate.state.testParams.value);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ let theHTML = `${configStart}(${data.xAttData.name}) ${sides} ${value} ${conf}`;
+
+ return theHTML;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/paired.js b/eepsmedia/plugins/testimate/src/tests/paired.js
new file mode 100644
index 00000000..69c098e5
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/paired.js
@@ -0,0 +1,107 @@
+class Paired extends Test {
+
+
+ constructor(iID) {
+ super(iID);
+ testimate.state.testParams.reversed = false;
+ }
+
+ updateTestResults() {
+ const X = data.xAttData.theArray;
+ const Y = data.yAttData.theArray;
+ const N = X.length;
+ if (N !== Y.length) {
+ alert(`Paired arrays are not the same length! Bogus results ahead!`);
+ }
+ let Z = [];
+
+ for (let i = 0; i < N; i++) {
+ Z[i] = testimate.state.testParams.reversed ? Y[i] - X[i] : X[i] - Y[i];
+ }
+
+ const jX = jStat(Z); // jStat version of difference array
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+
+ this.results.N = jX.cols();
+ this.results.df = this.results.N - 1;
+ this.results.mean = jX.mean();
+ this.results.s = jX.stdev(true); // true means SAMPLE SD
+ this.results.SE = this.results.s / Math.sqrt(this.results.N);
+ this.results.P = jX.ttest(testimate.state.testParams.value, testimate.state.testParams.sides);
+ this.results.tCrit = jStat.studentt.inv(theCIparam, this.results.df); // 1.96-ish for 0.95
+ this.results.CImax = this.results.mean + this.results.tCrit * this.results.SE;
+ this.results.CImin = this.results.mean - this.results.tCrit * this.results.SE;
+ this.results.t = (this.results.mean - testimate.state.testParams.value) / this.results.SE;
+ }
+
+ makeResultsString() {
+ const N = this.results.N;
+ const mean = ui.numberToString(this.results.mean, 3);
+ const s = ui.numberToString(this.results.s);
+ const SE = ui.numberToString(this.results.SE);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const CImin = ui.numberToString(this.results.CImin);
+ const CImax = ui.numberToString(this.results.CImax);
+ const tCrit = ui.numberToString(this.results.tCrit, 3);
+ const df = ui.numberToString(this.results.df, 3);
+ const t = ui.numberToString(this.results.t, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+ const value = ui.numberToString(testimate.state.testParams.value);
+
+ const testQuestion = testimate.state.testParams.reversed ?
+ localize.getString("tests.paired.testQuestion",
+ testimate.state.y.name, testimate.state.x.name, testimate.state.testParams.theSidesOp, value) :
+ localize.getString("tests.paired.testQuestion",
+ testimate.state.x.name, testimate.state.y.name, testimate.state.testParams.theSidesOp, value) ;
+ const r2 = localize.getString( "tests.paired.resultsLine2", mean, conf, CImin, CImax);
+
+ let out = "";
+
+ out += testQuestion;
+ out += ` N = ${N}, t = ${t}, ${P}`;
+ out += ` ${r2}`;
+ out += ` s = ${s}, SE = ${SE}, df = ${df}, α = ${alpha}, t* = ${tCrit} `;
+ out += ` `;
+
+ out += ` `;
+ return out;
+ }
+
+ makeTestDescription( ) {
+ return `paired test of ${data.xAttData.name} - ${data.yAttData.name}`;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ // return `paired test of ${data.xAttData.name} - ${data.yAttData.name}`;
+ if (testimate.state.testParams.reversed) {
+ return localize.getString("tests.paired.menuString", testimate.state.y.name, testimate.state.x.name);
+ } else {
+ return localize.getString("tests.paired.menuString", testimate.state.x.name, testimate.state.y.name);
+ }
+ }
+
+ makeConfigureGuts() {
+ const configStart = localize.getString("tests.paired.configurationStart");
+
+ const chicletGuts = (testimate.state.testParams.reversed) ?
+ `${testimate.state.y.name} – ${testimate.state.x.name}` :
+ `${testimate.state.x.name} – ${testimate.state.y.name}` ;
+
+ const chiclet = ui.chicletButtonHTML(chicletGuts);
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = ui.valueBoxHTML(testimate.state.testParams.value);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ let theHTML = `${configStart} ${chiclet} ${sides} ${value} ${conf}`;
+
+ return theHTML;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/regression.js b/eepsmedia/plugins/testimate/src/tests/regression.js
new file mode 100644
index 00000000..c5682e8d
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/regression.js
@@ -0,0 +1,142 @@
+class Regression extends Test {
+
+ constructor(iID, iGrouping) {
+ super(iID);
+ }
+
+ updateTestResults() {
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+
+ let sumXY = 0;
+ let sumX = 0;
+ let sumXX = 0;
+ let sumYY = 0;
+ let sumY = 0;
+ let N = data.xAttData.theArray.length;
+
+ if (N > 2) {
+ for (let i = 0; i < N; i++) {
+ // Note how these definitions are REVERSED.
+ // we want to look at the var in the first position (xAttData) as the dependent variable (Y)
+ const X = data.yAttData.theArray[i];
+ const Y = data.xAttData.theArray[i];
+ sumX += X;
+ sumY += Y;
+ sumXY += X * Y;
+ sumXX += X * X;
+ sumYY += Y * Y;
+ }
+
+ const slope = (N * sumXY - sumX * sumY) / (N * sumXX - sumX ** 2);
+ const intercept = (sumY - slope * sumX) / N;
+ const SDsqError = 1 / (N * (N - 2)) * (N * sumYY - sumY ** 2 - slope ** 2 * (N * sumXX - sumX ** 2));
+ const SDsqSlope = N * SDsqError / (N * sumXX - sumX ** 2);
+ const SDsqIntercept = SDsqSlope / N * sumXX;
+ const r = (N * sumXY - sumX * sumY) /
+ Math.sqrt((N * sumXX - sumX ** 2) * (N * sumYY - sumY ** 2));
+ const rsq = r * r;
+
+
+ this.results.N = N;
+ this.results.slope = slope;
+ this.results.intercept = intercept;
+ this.results.df = N - 2;
+ this.results.tCrit = jStat.studentt.inv(theCIparam, this.results.df); // 1.96-ish for 0.95
+ this.results.SEslope = SDsqSlope;
+ this.results.SEintercept = SDsqIntercept;
+ this.results.rho = r;
+ this.results.rsq = rsq;
+
+ const SDslope = Math.sqrt(SDsqSlope);
+ const SDintercept = Math.sqrt(SDsqIntercept);
+
+ this.results.slopeCImin = slope - this.results.tCrit * SDslope;
+ this.results.slopeCImax = slope + this.results.tCrit * SDslope;
+ this.results.interceptCImin = intercept - this.results.tCrit * SDintercept;
+ this.results.interceptCImax = intercept + this.results.tCrit * SDintercept;
+
+ // test slope against value
+ this.results.t = (this.results.slope - testimate.state.testParams.value) / SDslope;
+ const tAbs = Math.abs(this.results.t);
+ this.results.P = jStat.studentt.cdf(-tAbs, this.results.df);
+ if (testimate.state.testParams.sides === 2) this.results.P *= 2;
+ }
+
+ }
+
+ makeResultsString() {
+ // const testDesc = `mean of ${testimate.state.x.name}`;
+ const N = this.results.N;
+
+ const slope = ui.numberToString(this.results.slope); // CI of slope
+ const intercept = ui.numberToString(this.results.intercept); // CI of slope
+ const CISmin = ui.numberToString(this.results.slopeCImin); // CI of slope
+ const CISmax = ui.numberToString(this.results.slopeCImax);
+ const CIImin = ui.numberToString(this.results.interceptCImin); // CI of intercept
+ const CIImax = ui.numberToString(this.results.interceptCImax);
+ const df = ui.numberToString(this.results.df);
+ const rho = ui.numberToString(this.results.rho);
+ const rsq = ui.numberToString(this.results.rsq);
+ const t = ui.numberToString(this.results.t, 3);
+ const tCrit = ui.numberToString(this.results.tCrit, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+
+ const theSign = intercept >= 0 ? "+" : '-';
+
+ const X = testimate.state.x.name;
+ const Y = testimate.state.y.name;
+
+ const DSdetails = document.getElementById("DSdetails");
+ const DSopen = DSdetails && DSdetails.hasAttribute("open");
+
+ const testingSlopePhrase = localize.getString("tests.regression.testingSlope");
+ const slopeWord = localize.getString("slope");
+ const interceptWord = localize.getString("intercept");
+
+ let out = "";
+
+ // out += `How does (${X}) depend on (${Y})?`
+ out += localize.getString("tests.regression.testQuestion", X, Y);
+ out += ` LSRL: ${X} = ${slope} (${Y}) ${theSign} ${Math.abs(intercept)} `; // note reversal!
+ out += ` N = ${N}, ρ = ${rho}, r2 = ${rsq} `;
+ out += ``;
+ out += localize.getString("tests.regression.detailsSummary", X, Y);
+ out += `${slopeWord} ${slope} ${conf}% ${localize.getString("CI")} = [${CISmin}, ${CISmax}] `;
+ out += `${interceptWord} ${intercept} ${conf}% ${localize.getString("CI")} = [${CIImin}, ${CIImax}]
`;
+ out += ` `;
+ out += `${testingSlopePhrase} ${testimate.state.testParams.theSidesOp} ${testimate.state.testParams.value} `
+ out += ` t = ${t}, ${P}`;
+ out += ` df = ${df}, α = ${alpha}, t* = ${tCrit}, `
+ out += ` `;
+ out += ` `;
+ out += ` `;
+
+ return out;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString() {
+ return localize.getString("tests.regression.menuString",testimate.state.x.name, testimate.state.y.name);
+ // return `linear regression of (${testimate.state.x.name}) as a function of (${testimate.state.y.name})`;
+ }
+
+ makeConfigureGuts() {
+ const testingSlopePhrase = localize.getString("tests.regression.testingSlope");
+
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = ui.valueBoxHTML(testimate.state.testParams.value);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ let theHTML = `${testingSlopePhrase} ${sides} ${value} ${conf}`;
+
+ return theHTML;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/two-sample-p.js b/eepsmedia/plugins/testimate/src/tests/two-sample-p.js
new file mode 100644
index 00000000..64fa26f1
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/two-sample-p.js
@@ -0,0 +1,230 @@
+class TwoSampleP extends Test {
+
+ constructor(iID, iGrouping) {
+ super(iID);
+ this.grouping = iGrouping;
+ this.results.successValueA = null; // label for principal value for group A
+ this.results.successValueB = null; // label for principal value for B
+
+ // get a default "group" -- the value we count as "success" for proportions
+ if (!testimate.restoringFromSave || !testimate.state.testParams.focusGroupX) {
+ testimate.state.testParams.focusGroupX = testimate.state.focusGroupDictionary[data.xAttData.name];
+ testimate.state.testParams.focusGroupY = testimate.state.focusGroupDictionary[data.yAttData.name];
+ }
+ testimate.state.testParams.value
+ = testimate.state.valueDictionary[this.testID]
+ ? testimate.state.valueDictionary[this.testID] : 0;
+
+ }
+
+
+ updateTestResults() {
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+
+ let A = data.xAttData.theArray;
+ let B = data.yAttData.theArray;
+
+ if (this.grouping) {
+ // A (X) holds the data and values
+ // B (Y) holds the group membership.
+
+ this.results.labelA = testimate.state.testParams.focusGroupY; // theGroups[0];
+ this.results.labelB = Test.getComplementaryValue( data.yAttData, this.results.labelA);
+
+ this.results.successValueA = testimate.state.testParams.focusGroupX;
+ // this.results.successValueA || theValues[0]; // the default principal group = the first, by default
+ this.results.successValueB = testimate.state.testParams.focusGroupX; // must be the same as for A if we're grouped
+
+ [A, B] = Test.splitByGroup(A, B, this.results.labelA);
+
+ } else {
+ this.results.labelA = data.xAttData.name;
+ this.results.labelB = data.yAttData.name;
+
+ // const theAValues = [...data.xAttData.valueSet];
+ this.results.successValueA = testimate.state.testParams.focusGroupX; // this.results.successValueA || theAValues[0]; // the default principal group = the first, by default
+ const theBValues = [...data.yAttData.valueSet];
+ if (theBValues.includes(this.results.successValueA)) {
+ // we don't do the "or" here so that if the value exists in A,
+ // a change will "drag" B along.
+ // There is a chance this is not what the user wants.
+ this.results.successValueB = this.results.successValueA;
+ } else {
+ this.results.successValueB = testimate.state.testParams.focusGroupY;
+ }
+ }
+
+ // count cases and successes in "A"
+ this.results.N1 = 0;
+ this.results.successesA = 0;
+ A.forEach( a => {
+ this.results.N1++;
+ if (a === this.results.successValueA) this.results.successesA++
+ })
+
+ // count cases and successes in "B"
+ this.results.N2 = 0;
+ this.results.successesB = 0;
+ B.forEach( b => {
+ this.results.N2++;
+ if (b === this.results.successValueB) this.results.successesB++
+ })
+
+ this.results.N = this.results.N1 + this.results.N2;
+ if (this.results.N1 > 0 && this.results.N2 > 0) {
+ const pHat = (this.results.successesA + this.results.successesB) / this.results.N; // p (pooled)
+ const qHat = 1 - pHat;
+ this.results.prop = pHat;
+
+ this.results.prop1 = this.results.successesA / this.results.N1;
+ this.results.prop2 = this.results.successesB / this.results.N2;
+ this.results.SE1 = Math.sqrt(this.results.prop1 * (1 - this.results.prop1) / this.results.N1);
+ this.results.SE2 = Math.sqrt(this.results.prop2 * (1 - this.results.prop2) / this.results.N2);
+
+ // pooled standard error
+ this.results.SE = Math.sqrt((pHat * qHat) * (1/ this.results.N1 + 1 / this.results.N2));
+
+ this.results.SEinterval = Math.sqrt(
+ this.results.prop1 * (1 - this.results.prop1) / this.results.N1 +
+ this.results.prop2 * (1 - this.results.prop2) / this.results.N2
+ )
+
+ // the test p1 - p2
+ this.results.pDiff = this.results.prop1 - this.results.prop2;
+
+ // test statistic = z
+ this.results.z = (this.results.pDiff - testimate.state.testParams.value) / this.results.SE;
+ this.results.zCrit = jStat.normal.inv(theCIparam, 0, 1); // 1.96-ish for 0.95
+
+ const zAbs = Math.abs(this.results.z);
+ this.results.P = jStat.normal.cdf(-zAbs, 0, 1);
+ if (testimate.state.testParams.sides === 2) this.results.P *= 2;
+
+ this.results.CImax = this.results.pDiff + this.results.zCrit * this.results.SEinterval;
+ this.results.CImin = this.results.pDiff - this.results.zCrit * this.results.SEinterval;
+ }
+ }
+
+ makeResultsString() {
+ const N = this.results.N;
+ const N2 = this.results.N2;
+ const N1 = this.results.N1;
+ const pDiff = ui.numberToString(this.results.pDiff, 3);
+ const SE = ui.numberToString(this.results.SE);
+ const SEinterval = ui.numberToString(this.results.SEinterval);
+
+ const p1 = ui.numberToString(this.results.prop1);
+ const p2 = ui.numberToString(this.results.prop2);
+
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const CImin = ui.numberToString(this.results.CImin);
+ const CImax = ui.numberToString(this.results.CImax);
+ const zCrit = ui.numberToString(this.results.zCrit, 3);
+
+ const z = ui.numberToString(this.results.z, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+
+ const DSdetails = document.getElementById("DSdetails");
+ const DSopen = DSdetails && DSdetails.hasAttribute("open");
+ let out = "";
+
+ const groupingPhrase = `(${testimate.state.x.name} = ${this.results.successValueA}): ${this.results.labelA} - ${this.results.labelB}`;
+ const nonGroupingPhrase = `(${testimate.state.x.name} = ${this.results.successValueA}) - (${testimate.state.y.name} = ${this.results.successValueB})`;
+
+ const comparison = `${testimate.state.testParams.theSidesOp} ${testimate.state.testParams.value}`;
+ const resultHed = (this.grouping) ?
+ `${localize.getString("tests.twoSampleP.testQuestionHead")} ${groupingPhrase} ${comparison}?` :
+ `${localize.getString("tests.twoSampleP.testQuestionHead")} ${nonGroupingPhrase} ${comparison}?`;
+
+ out += `${resultHed} `;
+ out += ` N = ${N}, diff = ${pDiff}, z = ${z}, ${P}`;
+ out += ` ${conf}% CI = [${CImin}, ${CImax}], SE(CI) = ${SEinterval} `;
+
+ out += ``;
+ out += localize.getString("tests.twoSampleP.detailsSummary");
+ out += this.makeTwoSampleTable();
+ out += ` α = ${alpha}, z* = ${zCrit}`
+ out += ` `;
+
+ out += ` `;
+
+ return out;
+ }
+
+ makeTwoSampleTable() {
+ const SE1 = ui.numberToString(this.results.SE1);
+ const SE2 = ui.numberToString(this.results.SE2);
+ const SE = ui.numberToString(this.results.SE);
+ const N2 = this.results.N2;
+ const N1 = this.results.N1;
+ const N = this.results.N;
+ const succA = this.results.successesA;
+ const succB = this.results.successesB;
+ const p1 = ui.numberToString(this.results.prop1);
+ const p2 = ui.numberToString(this.results.prop2);
+ const prop = ui.numberToString(this.results.prop);
+
+ const groupColHead = this.grouping ? `${data.yAttData.name}` : localize.getString("group");
+ const propColHead = this.grouping ?
+ `${localize.getString("proportion")} ${data.xAttData.name} = ${this.results.successValueA}` :
+ `${localize.getString("proportion")}`;
+ const pooled = localize.getString("pooled");
+
+ let out = "";
+
+ const groupRowLabelA = this.grouping ? this.results.labelA : `${this.results.labelA} = ${this.results.successValueA}`;
+ const groupRowLabelB = this.grouping ? this.results.labelB : `${this.results.labelB} = ${this.results.successValueB}`;
+
+ out += ``;
+ out += ``;
+ out += `${groupRowLabelA} ${succA} / ${N1} ${p1} ${SE1} `;
+ out += `${groupRowLabelB} ${succB} / ${N2} ${p2} ${SE2} `;
+ out += `${pooled} ${succA + succB} / ${N} ${prop} ${SE} `;
+ out += `
`;
+
+ return out
+ }
+
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString(iID) {
+ if (iID === `BB02`) {
+ return localize.getString("tests.twoSampleP.menuString1", testimate.state.x.name, testimate.state.y.name);
+ } else {
+ return localize.getString("tests.twoSampleP.menuString2", testimate.state.x.name, testimate.state.y.name);
+ }
+ }
+
+ makeConfigureGuts() {
+ const configStart = localize.getString("tests.twoSampleP.configStart");
+
+ const intro = (this.grouping) ?
+ `${configStart}: (${testimate.state.x.name} = ${ui.focusGroupButtonXHTML(testimate.state.testParams.focusGroupX)} ) : ${ui.focusGroupButtonYHTML(testimate.state.testParams.focusGroupY)} - ${this.results.labelB}` :
+ `${configStart}: (${testimate.state.x.name} = ${ui.focusGroupButtonXHTML(testimate.state.testParams.focusGroupX)}) - (${testimate.state.y.name} = ${ui.focusGroupButtonYHTML(testimate.state.testParams.focusGroupY)}) `;
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = ui.valueBoxHTML(testimate.state.testParams.value, 0.0, 1.0, .05);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+ let theHTML = `${intro} ${sides} ${value} ${conf}`;
+
+ return theHTML;
+ }
+
+/*
+ successValueButtonA( ) {
+ return ` `
+ }
+
+ successValueButtonB( ) {
+ return ` `
+ }
+*/
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/tests/two-sample-t.js b/eepsmedia/plugins/testimate/src/tests/two-sample-t.js
new file mode 100644
index 00000000..9e93e143
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/tests/two-sample-t.js
@@ -0,0 +1,216 @@
+/**
+ * Implements two forms of a two-sample t test.
+ *
+ * **Two separate attributes**: We compare the mean value in "X" to the mean value in "Y."
+ * This is perfect if you have weights of cats in one column and dogs in another,
+ * *and they are not paired*. (There could be different numbers of animals...)
+ *
+ * **Y is a grouping attribute**: We split the values of "X" according to values in "Y."
+ * Use this if you have weights of all animals in the "X" column and the values `cat` or `dog`
+ * in "Y". (i.e., tidy)
+ *
+ * The member `this.grouping` tells which kind of test it is.
+ */
+class TwoSampleT extends Test {
+
+ constructor(iID, iGrouping) {
+ super(iID);
+ this.grouping = iGrouping; // is a grouping value in "Y"?
+ this.results.groupNames = []; // names of the two groups to be displayed (depends on grouping)
+ if (this.grouping) {
+ if (!testimate.restoringFromSave || !testimate.state.testParams.focusGroupY) {
+ testimate.state.testParams.focusGroupY = testimate.state.focusGroupDictionary[data.yAttData.name];
+ }
+
+ } else {
+ testimate.state.testParams.focusGroupY = null;
+ }
+ testimate.state.testParams.value
+ = testimate.state.valueDictionary[this.testID]
+ ? testimate.state.valueDictionary[this.testID] : 0;
+
+ testimate.state.testParams.reversed = false;
+ }
+
+ updateTestResults() {
+
+ const theCIparam = 1 - testimate.state.testParams.alpha / 2;
+ let A = data.xAttData.theArray;
+ let B = data.yAttData.theArray;
+ this.results.group1Name = data.xAttData.name;
+ this.results.group2Name = data.yAttData.name;
+
+ if (this.grouping) {
+ [A, B] = Test.splitByGroup(A, B, testimate.state.testParams.focusGroupY);
+ console.log(`A = ${A}, B = ${B}`);
+ this.results.group1Name = testimate.state.testParams.focusGroupY; // the name of a value in the second att
+ this.results.group2Name = data.yAttData.isBinary() ?
+ handlers.nextValueInList([...data.yAttData.valueSet], testimate.state.testParams.focusGroupY) : // the OTHER value
+ `not ${testimate.state.testParams.focusGroupY}` // or a more general label, NOT "a"
+ }
+
+ const j0 = jStat(A);
+ const j1 = jStat(B);
+
+ this.results.N1 = j0.cols();
+ this.results.N2 = j1.cols();
+ this.results.N = this.results.N1 + this.results.N2;
+
+ this.results.df = this.results.N1 + this.results.N2 - 2;
+ this.results.mean1 = j0.mean();
+ this.results.mean2 = j1.mean();
+ this.results.s1 = j0.stdev(true); // true means SAMPLE SD
+ this.results.s2 = j1.stdev(true); // true means SAMPLE SD
+ this.results.SE1 = this.results.s1 / Math.sqrt(this.results.N1);
+ this.results.SE2 = this.results.s2 / Math.sqrt(this.results.N2);
+
+ /*
+ See https://en.wikipedia.org/wiki/Student%27s_t-test#Independent_two-sample_t-test.
+ I'm using "Equal or unequal sample sizes, similar variance."
+ Maybe we should go one further and use Welch's, which follows
+ in that wikipedia article.
+ */
+ const sArg = ((this.results.N1 - 1) * this.results.s1 ** 2 +
+ (this.results.N2 - 1) * this.results.s2 ** 2) /
+ (this.results.N1 + this.results.N2 - 2);
+ this.results.s = Math.sqrt(sArg); // pooled SD
+ this.results.SE = this.results.s * Math.sqrt((1 / this.results.N1) + (1 / this.results.N2));
+ this.results.diff = testimate.state.testParams.reversed ?
+ this.results.mean2 - this.results.mean1 : this.results.mean1 - this.results.mean2;
+ this.results.t = (this.results.diff - testimate.state.testParams.value) / this.results.SE;
+
+ const var1oN = j0.variance(true) / this.results.N1;
+ const var2oN = j1.variance(true) / this.results.N2; // sample variance/N = s^2/N
+ // const df2 = (var1oN + var2oN) ** 2 / (var1oN ** 2 / (this.results.N1 - 1) + var2oN ** 2 / (this.results.N2)); // variance for
+ // const df1 = this.results.N1 + this.results.N2 - 1;
+
+ // this.results.df = df2; // just use the df calculated earlier: N1 + N2 - 2.
+
+ this.results.tCrit = jStat.studentt.inv(theCIparam, this.results.df); // 1.96-ish for 0.95
+ const tAbs = Math.abs(this.results.t);
+ this.results.P = jStat.studentt.cdf(-tAbs, this.results.df);
+ if (testimate.state.testParams.sides === 2) this.results.P *= 2;
+
+ this.results.CImax = this.results.diff + this.results.tCrit * this.results.SE;
+ this.results.CImin = this.results.diff - this.results.tCrit * this.results.SE;
+
+ }
+
+ makeResultsString() {
+
+ const N = this.results.N;
+ const diff = ui.numberToString(this.results.diff, 3);
+ const s = ui.numberToString(this.results.s);
+ const SE = ui.numberToString(this.results.SE);
+
+ const mean1 = ui.numberToString(this.results.mean1);
+ const mean2 = ui.numberToString(this.results.mean2);
+ const P = (this.results.P < 0.0001) ?
+ `P < 0.0001` :
+ `P = ${ui.numberToString(this.results.P)}`;
+ const CImin = ui.numberToString(this.results.CImin);
+ const CImax = ui.numberToString(this.results.CImax);
+ const tCrit = ui.numberToString(this.results.tCrit, 3);
+ const df = ui.numberToString(this.results.df, 3);
+ const t = ui.numberToString(this.results.t, 3);
+ const conf = ui.numberToString(testimate.state.testParams.conf);
+ const alpha = ui.numberToString(testimate.state.testParams.alpha);
+
+ const DSdetails = document.getElementById("DSdetails");
+ const DSopen = DSdetails && DSdetails.hasAttribute("open");
+
+ const comparison = `${testimate.state.testParams.theSidesOp} ${testimate.state.testParams.value}`;
+
+ const resultHed = (this.grouping) ?
+ localize.getString("tests.twoSampleT.testQuestion1", testimate.state.x.name,this.results.group1Name,this.results.group2Name,comparison) :
+ testimate.state.testParams.reversed ?
+ localize.getString("tests.twoSampleT.testQuestion2", testimate.state.y.name,testimate.state.x.name,comparison) :
+ localize.getString("tests.twoSampleT.testQuestion2", testimate.state.x.name,testimate.state.y.name,comparison) ;
+
+ let out = "";
+
+ out += `${resultHed} `;
+ out += ` N = ${N}, t = ${t}, ${P}`;
+ out += ` diff = ${diff}, ${conf}% ${localize.getString("CI")} = [${CImin}, ${CImax}] `;
+
+ out += ``;
+ out += localize.getString("tests.twoSampleT.detailsSummary"); // `Difference of means, t procedure `;
+ out += this.makeTwoSampleTable();
+ out += ` df = ${df}, α = ${alpha}, t* = ${tCrit}`
+ out += ` `;
+
+ out += ` `;
+
+ return out;
+ }
+
+ makeTwoSampleTable() {
+ const N2 = this.results.N2;
+ const N1 = this.results.N1;
+ const s1 = ui.numberToString(this.results.s1);
+ const s2 = ui.numberToString(this.results.s2);
+ const SE1 = ui.numberToString(this.results.SE1);
+ const SE2 = ui.numberToString(this.results.SE2);
+ const mean1 = ui.numberToString(this.results.mean1);
+ const mean2 = ui.numberToString(this.results.mean2);
+
+ const N = this.results.N;
+ const diff = ui.numberToString(this.results.diff, 3);
+ const s = ui.numberToString(this.results.s);
+ const SE = ui.numberToString(this.results.SE);
+
+ const mean = localize.getString("mean");
+ const group = localize.getString("group");
+
+ const groupColHed = this.grouping ? `${testimate.state.y.name}` : group;
+ const meanColHead = this.grouping ? `${mean}(${testimate.state.x.name})` : mean;
+
+ let out = "";
+ out += ``;
+ out += `${this.results.group1Name} ${N1} ${mean1} ${s1} ${SE1} `;
+ out += `${this.results.group2Name} ${N2} ${mean2} ${s2} ${SE2} `;
+ out += `pooled ${N} diff = ${diff} ${s} ${SE} `;
+ out += `
`;
+ return out;
+ }
+
+ /**
+ * NB: This is a _static_ method, so you can't use `this`!
+ * @returns {string} what shows up in a menu.
+ */
+ static makeMenuString(iID) {
+ if (iID === `NN02`) {
+ return localize.getString("tests.twoSampleT.menuString1", testimate.state.x.name, testimate.state.y.name);
+ } else {
+ return localize.getString("tests.twoSampleT.menuString2", testimate.state.x.name, testimate.state.y.name);
+ }
+ }
+
+ makeConfigureGuts() {
+
+ const yComplement = Test.getComplementaryValue(data.yAttData, testimate.state.testParams.focusGroupY);
+ const configStart = (this.grouping) ?
+ localize.getString("tests.twoSampleT.configStartPaired", testimate.state.x.name) :
+ localize.getString("tests.twoSampleT.configStartUnpaired");
+
+ const chicletGuts = (testimate.state.testParams.reversed) ?
+ `mean(${testimate.state.y.name}) – mean(${testimate.state.x.name})` :
+ `mean(${testimate.state.x.name}) – mean(${testimate.state.y.name})` ;
+
+ const chiclet = ui.chicletButtonHTML(chicletGuts);
+
+ const configContinues = (this.grouping) ?
+ `[${ui.focusGroupButtonYHTML(testimate.state.testParams.focusGroupY)}]–[${yComplement}]` :
+ chiclet ;
+
+
+ const sides = ui.sidesBoxHTML(testimate.state.testParams.sides);
+ const value = ui.valueBoxHTML(testimate.state.testParams.value);
+ const conf = ui.confBoxHTML(testimate.state.testParams.conf);
+
+ let theHTML = `${configStart}: ${configContinues} ${sides} ${value} ${conf}`;
+
+ return theHTML;
+ }
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/src/ui.js b/eepsmedia/plugins/testimate/src/ui.js
new file mode 100644
index 00000000..6873bdff
--- /dev/null
+++ b/eepsmedia/plugins/testimate/src/ui.js
@@ -0,0 +1,400 @@
+let ui;
+
+ui = {
+
+ xDIV: null,
+ xNameDIV: null,
+ yDIV: null,
+ yNameDIV: null,
+ xType: null,
+ yType: null,
+
+ datasetDIV: null,
+ datasetSPAN: null,
+ testHeaderDIV: null,
+ resultsDIV: null, // results DIV
+ configDIV: null,
+ emitControls: null,
+
+ emitMode: "single",
+
+ initialize: function () {
+ this.xDIV = document.getElementById(`xDIV`);
+ this.yDIV = document.getElementById(`yDIV`);
+ this.xType = document.getElementById(`xCNbutton`);
+ this.yType = document.getElementById(`yCNbutton`);
+
+ this.xNameDIV = document.getElementById(`xAttributeName`);
+ this.yNameDIV = document.getElementById(`yAttributeName`);
+
+ this.datasetDIV = document.getElementById(`datasetDIV`);
+ this.datasetSPAN = document.getElementById(`datasetSPAN`);
+ this.testHeaderDIV = document.getElementById(`testHeaderDIV`);
+ this.resultsDIV = document.getElementById(`resultsDIV`);
+ this.configDIV = document.getElementById(`configureDIV`);
+
+ this.emitControls = document.getElementById(`emitControls`);
+ this.emitMode = "single";
+ },
+
+ /**
+ * Main UI function. Redraws the screen.
+ *
+ * @returns {Promise}
+ */
+ redraw: async function () {
+
+ if (testimate.state.dataset) {
+
+ if (testimate.theTest && testimate.theTest.testID) {
+
+ // create the text and other display information for the results
+ this.datasetSPAN.innerHTML = await this.makeDatasetGuts();
+ this.testHeaderDIV.innerHTML = this.makeTestHeaderGuts(); // includes making the choice menu
+ this.resultsDIV.innerHTML = testimate.theTest.makeResultsString();
+ this.configDIV.innerHTML = testimate.theTest.makeConfigureGuts();
+ document.getElementById("randomEmitNumberBox").value = testimate.state.randomEmitNumber;
+ this.adjustEmitGuts();
+ }
+ }
+
+ this.updateAttributeBlocks();
+ this.setVisibility();
+ },
+
+ setVisibility: function () {
+
+ // many things are invisible if there is no x-variable, therefore no test
+
+ document.getElementById('Ybackdrop').style.display = (testimate.state.x) ? 'inline' : 'none';
+ // document.getElementById('xCNbutton').style.display = (testimate.state.x) ? 'inline' : 'none';
+ document.getElementById('testHeaderDIV').style.display = (testimate.state.x) ? 'block' : 'none';
+ document.getElementById('emitDIV').style.display = (testimate.state.x) ? 'block' : 'none';
+ document.getElementById('resultsDIV').style.display = (testimate.state.x) ? 'block' : 'none';
+ document.getElementById('configureDIV').style.display = (testimate.state.x) ? 'block' : 'none';
+
+ document.getElementById('emitSingleGroup').style.display = "block"; // always show single
+ document.getElementById('emitRandomGroup').style.display = (data.hasRandom) ? "block" : "none";
+ document.getElementById('emitHierarchicalGroup').style.display = (data.isGrouped) ? "block" : "none";
+
+ // emit mode visibility
+
+ switch (this.emitMode) {
+ case "single":
+ document.getElementById('emitSingleButton').style.display = 'inline';
+ document.getElementById('chooseEmitSingle').checked = true;
+ document.getElementById('emitRandomButton').style.display = 'none';
+ // document.getElementById('chooseEmitRandomLabel').style.display = 'inline';
+ document.getElementById('randomEmitNumberBox').style.display = 'none';
+ document.getElementById('randomEmitNumberBoxLabel').style.display = 'none';
+ document.getElementById('emitHierarchyButton').style.display = 'none';
+ break;
+ case "random":
+ document.getElementById('chooseEmitRandom').checked = true;
+ document.getElementById('emitSingleButton').style.display = 'none';
+ document.getElementById('emitRandomButton').style.display = 'inline';
+ document.getElementById('chooseEmitRandomLabel').style.display = 'inline';
+ document.getElementById('randomEmitNumberBox').style.display = 'inline';
+ document.getElementById('randomEmitNumberBoxLabel').style.display = 'inline';
+ document.getElementById('emitHierarchyButton').style.display = 'none';
+ break;
+ case "hierarchy":
+ document.getElementById('chooseEmitHierarchy').checked = true;
+ document.getElementById('emitSingleButton').style.display = 'none';
+ document.getElementById('emitRandomButton').style.display = 'none';
+ // document.getElementById('chooseEmitRandomLabel').style.display = 'inline';
+ document.getElementById('randomEmitNumberBox').style.display = 'none';
+ document.getElementById('randomEmitNumberBoxLabel').style.display = 'none';
+ document.getElementById('emitHierarchyButton').style.display = 'inline';
+ break;
+ default:
+ alert(`unexpected emit mode: [${this.emitMode}]`);
+ break;
+ }
+
+
+ },
+
+ updateAttributeBlocks: function () {
+ const xType = document.getElementById(`xCNbutton`);
+ const yType = document.getElementById(`yCNbutton`);
+ const xTrash = document.getElementById(`xTrashAttButton`);
+ const yTrash = document.getElementById(`yTrashAttButton`);
+
+ if (testimate.state.x && testimate.state.x.name) {
+ this.xNameDIV.textContent = testimate.state.x.name;
+ xType.value = testimate.state.dataTypes[testimate.state.x.name] === 'numeric' ? '123' : 'abc';
+ xTrash.style.display = "inline";
+ xType.style.display = "inline";
+ this.xDIV.className = "drag-none";
+ } else { // x attribute waiting for drop!
+ this.xNameDIV.textContent = localize.getString("dropAttributeHere");
+ xTrash.style.display = "none";
+ xType.style.display = "none";
+ this.xDIV.className = "drag-empty";
+ }
+ if (testimate.state.y && testimate.state.y.name) {
+ this.yNameDIV.textContent = testimate.state.y.name;
+ yType.value = testimate.state.dataTypes[testimate.state.y.name] === 'numeric' ? '123' : 'abc';
+ yTrash.style.display = "inline";
+ yType.style.display = "inline";
+ this.yDIV.className = "drag-none";
+ } else {
+ this.yNameDIV.textContent = localize.getString("dropAttributeHere");
+ yTrash.style.display = "none";
+ yType.style.display = "none";
+ this.yDIV.className = "drag-empty";
+ }
+
+ },
+
+ numberToString: function (iValue, iFigs = 4) {
+ let out = "";
+ let multiplier = 1;
+ let suffix = "";
+ let exponential = false;
+
+ if (iValue === "" || iValue === null || typeof iValue === "undefined") {
+ out = "";
+ } else if (iValue === 0) {
+ out = "0";
+ } else {
+ if (Math.abs(iValue) > 1.0e15) {
+ exponential = true;
+ } else if (Math.abs(iValue) < 1.0e-4) {
+ exponential = true;
+ } else if (Math.abs(iValue) > 1.0e10) {
+ multiplier = 1.0e9;
+ iValue /= multiplier;
+ suffix = " B";
+ } else if (Math.abs(iValue) > 1.0e7) {
+ multiplier = 1.0e6;
+ iValue /= multiplier;
+ suffix = " M";
+ }
+ out = new Intl.NumberFormat(
+ testimate.constants.lang,
+ {maximumSignificantDigits: iFigs, useGrouping: false}
+ ).format(iValue);
+
+ if (exponential) {
+ out = Number.parseFloat(iValue).toExponential(iFigs);
+ }
+ }
+ return `${out}${suffix}`; // empty if null or empty
+ },
+
+ /**
+ * returns the "sides" button HTML, which controls whether this is a 1- or 2-sided test.
+ * The button therefore changes from "≠" to either ">" or "<", and back again.
+ * This is in the form of a clickable button so you can change it.
+ *
+ * @param iSides
+ * @returns string containing the html for that button
+ */
+ sidesBoxHTML: function (iSides) {
+ const theParams = testimate.state.testParams;
+ theParams.theSidesOp = "≠";
+ if (iSides === 1) {
+ const testStat = testimate.theTest.results[testimate.theTest.theConfig.testing]; // testing what? mean? xbar? diff? slope?
+ theParams.theSidesOp = (testStat > theParams.value ? ">" : "<");
+ }
+
+ return ` `
+ },
+
+ /**
+ * Button that changes which group is compared to everybody else
+ * (we will call this group the "focusGroup"
+ * (when a categorical app needs to be made binary)
+ * @param iGroup
+ * @returns {` `}
+ */
+ focusGroupButtonXHTML: function (iGroup) {
+ return ` `
+ },
+
+ focusGroupButtonYHTML: function (iGroup) {
+ return ` `
+ },
+
+ chicletButtonHTML : function(iGuts) {
+ return ` `
+ },
+
+ sides12ButtonHTML : function(iSides) {
+ const buttonTitle = localize.getString("Nsided", iSides);
+ return ` `
+ },
+
+ getFocusGroupName: function () {
+ if (!testimate.state.testParams.focusGroup) {
+ testimate.setFocusGroup(data.xAttData, null);
+ }
+ return testimate.state.focusGroupDictionary[data.xAttData.name];
+ },
+
+ makeLogisticGraphButtonHTML: function (iGroup) {
+ const theLabel = localize.getString("showGraph");
+ return ` `
+ },
+
+ /**
+ * Construct a number to receive a value such as
+ * the value to be compared to
+ *
+ * @param iVal
+ * @param iMax
+ * @param iStep
+ * @returns {` `}
+ */
+ valueBoxHTML: function (iVal, iMin, iMax, iStep) {
+ const minPhrase = iMin ? `min="${iMin}"` : "";
+ const maxPhrase = iMax ? `max="${iMax}"` : "";
+ const stepPhrase = iStep ? `step="${iStep}"` : "";
+ return ` `;
+ },
+
+ iterBoxHTML: function (iVal, iMax, iStep) {
+ const maxPhrase = iMax ? `max="${iMax}"` : "";
+ const stepPhrase = iStep ? `step="${iStep}"` : "";
+ return ` `;
+ },
+
+ rateBoxHTML: function (iVal, iMax, iStep) {
+ const maxPhrase = iMax ? `max="${iMax}"` : "";
+ const stepPhrase = iStep ? `step="${iStep}"` : "";
+ return ` `;
+ },
+
+ logisticRegressionProbeBoxHTML: function (iVal, iMax, iStep) {
+ const maxPhrase = iMax ? `max="${iMax}"` : "";
+ const stepPhrase = iStep ? `step="${iStep}"` : "";
+ return ` `;
+ },
+
+ /**
+ * Construct a number to receive a
+ * a confidence level. Also includes a
+ *
+ * @param iConf
+ * @returns {`conf =
+ %`}
+ */
+ confBoxHTML: function (iConf) {
+ return `conf =
+ %`;
+ },
+
+ alphaBoxHTML: function (iConf) {
+ return `α =
+ `;
+ },
+
+ /*
+ updateConfig: function () {
+ const theConfig = Test.configs[testimate.theTest.testID];
+ const theParams = testimate.state.testParams;
+
+ document.getElementById(`configStart`).textContent = `${testimate.theTest.makeTestDescription(this.theTestID, false)} `;
+ document.getElementById(`valueBox`).value = theParams.value;
+ document.getElementById(`sidesButton`).value = theParams.theSidesOp;
+ },
+ */
+
+ makeTestHeaderGuts: function () {
+ let out = ``;
+
+ if (testimate.theTest) {
+ const theTestConfig = Test.configs[testimate.theTest.testID];
+ let thePhrase = theTestConfig.makeMenuString();
+
+ if (testimate.compatibleTestIDs.length === 1) {
+ out += thePhrase;
+ } else if (testimate.compatibleTestIDs.length > 1) {
+ let theMenu = ``;
+ out += theMenu;
+ }
+
+ } else {
+ out = "no tests available with these settings!";
+ }
+
+ out += `
`; // close the hBox DIV
+ return out;
+ },
+
+ /**
+ * Make the contents of the top DIV, which lists the dataset name plus some other stuff.
+ * @returns {Promise}
+ */
+ makeDatasetGuts: async function () {
+ const randomPhrase = ui.hasRandom ? localize.getString('hasRandom') : localize.getString('noRandom');
+
+ return localize.getString("datasetDIV", testimate.state.dataset.title, data.allCODAPitems.length, randomPhrase);
+ },
+
+ adjustEmitGuts: function () {
+ const summaryClause = `${localize.getString("tests.emitSummary")} `
+ const singleEmitButtonTitle = localize.getString("emit");
+ const randomEmitButtonTitle = localize.getString("emitRR", testimate.state.randomEmitNumber);
+ const hierarchyEmitButtonTitle = localize.getString("emitHierarchy", data.topCases.length);
+
+ document.getElementById("emitSingleButton").value = singleEmitButtonTitle;
+ document.getElementById("emitRandomButton").value = randomEmitButtonTitle;
+ document.getElementById("emitHierarchyButton").value = hierarchyEmitButtonTitle;
+
+ /* const emitClause = `
+ `;
+ const emitRRButton = ` `;
+ const emitRRBox = `
+ times
+ `;
+
+ let randomClause = "";
+ if (ui.hasRandom) {
+ randomClause = `${emitRRButton} ${emitRRBox}`;
+ }
+
+ let hierarchicalClause = "";
+ if (this.hierarchyInfo && this.hierarchyInfo.nCollections > 1) {
+ const emitHierarchyButtonTitle = localize.getString("emitHierarchy", this.hierarchyInfo.topLevelCases.length);
+ const emitHierarchyButton =
+ `
+ `;
+ hierarchicalClause = emitHierarchyButton;
+ }*/
+ },
+
+
+}
diff --git a/eepsmedia/plugins/testimate/strings/localize.js b/eepsmedia/plugins/testimate/strings/localize.js
new file mode 100644
index 00000000..69433638
--- /dev/null
+++ b/eepsmedia/plugins/testimate/strings/localize.js
@@ -0,0 +1,137 @@
+
+let DG = {
+ plugins : null,
+};
+
+function replaceSubstrings(originalString, ...substitutions) {
+ // Use a regular expression to find substrings of the form "•n•"
+ const regex = /•(\d+)•/g;
+
+ // Replace each match with the corresponding substitution
+ const resultString = originalString.replace(regex, (match, index) => {
+ const substitutionIndex = parseInt(index, 10) - 1; // Adjust index to zero-based
+ return substitutions[substitutionIndex] || match; // Use substitution or original match if not available
+ });
+
+ return resultString;
+}
+
+
+const localize = {
+
+ defaultStrings : {},
+ languages : [],
+
+ fileNameMap : {
+ en : "strings/testimate_English.json",
+ es : "strings/testimate_Spanish.json",
+ de : "strings/testimate_German.json",
+ fr : "strings/testimate_French.json",
+ it : "strings/testimate_Italian.json",
+ },
+
+ initialize : async function(iLang) {
+ DG.plugins = await this.loadLanguage(iLang);
+ this.defaultStrings = await this.loadLanguage('en'); // defaults to English; may not be necessary
+
+ console.log("done loading language strings");
+ this.setStaticStrings();
+
+ },
+
+ loadLanguage : async function(iLang) {
+ let theFileName = this.fileNameMap[iLang];
+ const response = await fetch(theFileName);
+ const theText = await response.text();
+ return JSON.parse(theText)
+ },
+
+ getString : function(iID, ...theArgs) {
+ const theRawString = eval(`DG.plugins.testimate.${iID}`);
+ let out = "";
+ if (theRawString) {
+ out = replaceSubstrings(theRawString, ...theArgs);
+ } else {
+ const theDefaultString = eval(`this.defaultStrings.testimate.${iID}`);
+ if (theDefaultString) {
+ out = replaceSubstrings(theDefaultString, ...theArgs);
+ }
+ }
+ return `${out}`; // add gunk to this statement to check if we're localizing correctly!
+ },
+
+ setStaticStrings: async function () {
+
+ // substitute all the static strings in the UI (by `id`)
+ const theStaticStrings = DG.plugins.testimate.staticStrings;
+ for (const theID in theStaticStrings) {
+ if (theStaticStrings.hasOwnProperty(theID)) {
+ const theValue = this.getString(`staticStrings.${theID}`); // theStaticStrings[theID];
+ try {
+ document.getElementById(theID).innerHTML = theValue;
+ // console.log(`Set string for ${theID} in ${iLang}`);
+ } catch (msg) {
+ console.log(msg + ` on ID = ${theID}`);
+ }
+ }
+ }
+ },
+
+ /**
+ * Get a two-letter language code from a variety of sources.
+ *
+ * @param iDefaultLanguage the default laguage in case none of the following work
+ * @param iSupportedLanguages an array of two-letter codes for the languages the plugin supports
+ * @returns {*} resulting two-letter code
+ */
+ figureOutLanguage: function (iDefaultLanguage) {
+
+ this.languages = Object.keys(this.fileNameMap);
+ let lOut = iDefaultLanguage;
+
+ // find the user's favorite language that's actually in our list
+
+ const userLanguages = Array.from(navigator.languages).reverse();
+
+ userLanguages.forEach((L) => {
+ console.log(`user has lang ${L}`);
+ const twoLetter = L.slice(0, 2).toLowerCase();
+ if (this.languages.includes(twoLetter)) {
+ if (lOut !== twoLetter) {
+ lOut = twoLetter;
+ console.log(` change lang to ${lOut} from user preferences`);
+ }
+ }
+ })
+
+ lOut = this.getLangFromURL() || lOut; // lang from URL has priority
+
+ console.log(`localize: use language "${lOut}"`);
+
+ // final catch
+ if (!this.languages.includes(lOut)) {
+ lOut = iDefaultLanguage;
+ console.log(`localize: final catch, use language "${lOut}"`);
+ }
+
+ return lOut;
+ },
+
+ /**
+ * Finds the two-letter code in a `lang` URL parameter if it exists. Returns `null` if none.
+ * @returns {null}
+ */
+ getLangFromURL : function() {
+ const params = new URLSearchParams(document.location.search.substring(1));
+ const langParam = params.get("lang");
+
+ if (langParam) {
+ console.log(`Got language ${langParam} from input parameters`);
+ } else {
+ console.log(`No "lang" parameter in URL`);
+ }
+ return langParam;
+ },
+
+
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/strings/testimate_English.json b/eepsmedia/plugins/testimate/strings/testimate_English.json
new file mode 100644
index 00000000..7646c8f5
--- /dev/null
+++ b/eepsmedia/plugins/testimate/strings/testimate_English.json
@@ -0,0 +1,241 @@
+{
+ "testimate": {
+ "flags": "🇬🇧,🇺🇸",
+ "language": "English",
+ "staticStrings": {
+ "outcomePrimaryHint": "outcome/primary attribute",
+ "predictorSecondaryHint": "predictor/secondary attribute",
+ "emitDetailsSummary" : "emit test results into CODAP",
+ "chooseEmitSingleLabel" : "current test",
+ "chooseEmitRandomLabel" : "rerandomizing",
+ "chooseEmitHierarchyLabel" : "each subgroup",
+ "randomEmitNumberBoxLabel" : "times"
+ },
+ "datasetName" : "tests and estimates",
+ "datasetDIV": "Dataset: •1• , •2• cases (•3•)",
+ "dropAttributeHere": "drop attribute here",
+ "mean": "mean",
+ "proportion": "proportion",
+ "group": "group",
+ "groups": "groups",
+ "slope": "slope",
+ "intercept": "intercept",
+ "value": "value",
+ "values": "values",
+ "pooled": "pooled",
+ "equalize": "equalize",
+ "observed": "observed",
+ "expected": "expected",
+ "count": "count",
+ "error": "error",
+ "total": "total",
+ "iterations": "iterations",
+ "showGraph": "show graph",
+ "copyFormula": "copy formula",
+ "emit": "emit these results",
+ "emitRR": "re-randomize and emit •1•x",
+ "emitHierarchy": "emit results from •1• subgroups",
+ "at" : "at",
+ "rate" : "rate",
+ "cost": "cost",
+ "notP" : "not •1•",
+ "nMore" : "•1• more",
+ "hasRandom" : "has randomness",
+ "noRandom" : "no randomness",
+ "CI" : "CI",
+ "conf" : "conf",
+ "Nsided" : "•1•-sided",
+
+ "attributeNames": {
+ "N": "N",
+ "N1" : "N1",
+ "N2": "N2",
+ "prop": "prop",
+ "prop1": "prop1",
+ "prop2": "prop2",
+ "P": "P",
+ "df": "df",
+ "CImin": "CImin",
+ "CImax": "CImax",
+ "slopeCImin": "slope_CImin",
+ "slopeCImax": "slope_CImax",
+ "alpha": "alpha",
+ "conf": "conf",
+ "t": "t",
+ "z": "z",
+ "chisq": "chisq",
+ "F": "F",
+ "tCrit": "tCrit",
+ "zCrit": "zCrit",
+ "chisqCrit": "chisqCrit",
+ "FCrit": "FCrit",
+ "sign": "sign",
+ "value": "value",
+ "sides" : "sides",
+ "outcome": "outcome",
+ "predictor": "predictor",
+ "procedure": "procedure",
+ "SSR": "SSR",
+ "SSE": "SSE",
+ "SST": "SST",
+ "dfTreatment": "dfTreatment",
+ "dfError": "dfError",
+ "s": "s",
+ "mean": "mean",
+ "mean1": "mean1",
+ "mean2": "mean2",
+ "diff": "diff",
+ "pDiff": "diff",
+ "SE": "SE",
+ "slope" : "slope",
+ "intercept" : "intercept",
+ "correlation" : "correlation",
+ "rho" : "rho",
+ "rsq" : "rSquared",
+ "pos" : "pos",
+ "LSlope" : "slope",
+ "cost" : "cost",
+ "rate" : "rate",
+ "iterations" : "iterations"
+ },
+ "attributeDescriptions": {
+ "N": "the sample size",
+ "N1": "the sample size, first group",
+ "N2": "the sample size, second group",
+ "prop": "the sample proportion you are studying",
+ "prop1": "the sample proportion, first group",
+ "prop2": "the sample proportion, second group",
+ "P": "the \"P-value,\" the probability that, if the null hypothesis were true, we would see a value of the test statistic this extreme.",
+ "df": "degrees of freedom",
+ "CImin": "the lower bound to the confidence interval",
+ "CImax": "the upper bound to the confidence interval",
+ "slopeCImin": "the lower bound to the confidence interval for slope",
+ "slopeCImax": "the upper bound to the confidence interval for slope",
+ "alpha": "the probability we use to decide if a P-value is \"significant.\" Use significance with caution.",
+ "conf": "the value, in percent, used to compute the confidence interval",
+ "t": "the t statistic: the residual divided by the standard error",
+ "z": "the z statistic: the difference from the hypothesized value divided by the sample standard deviation",
+ "chisq": "the chi-squared statistic. It measures how far a set of counts is from the \"expected\" value.",
+ "F": "the F statistic. It measures how much variation is between groups as opposed to within groups.",
+ "tCrit": "a critical value for t: the value that corresponds to the probability alpha",
+ "zCrit": "a critical value for z: the value that corresponds to the probability alpha",
+ "chisqCrit": "a critical value for chi-squared: the value that corresponds to the probability alpha",
+ "FCrit": "a critical value for the F statistic",
+ "sign" : "relationship of the test statistic to the value in the box",
+ "value": "the hypothesized value you are comparing your test statistic to",
+ "sides" : "how many sides in your test? One or two?",
+ "outcome": "the name of the left-hand (outcome) variable",
+ "predictor": "the name of the right-hand (predictor) variable, even if it's not being used to predict anything",
+ "procedure": "what kind of statistical procedure was used",
+ "SSR": "the sum of squares of residuals calculated between the groups",
+ "SSE": "the sum of squares of errors, that is, calculated within the groups relative to the group means",
+ "SST": "the total sum of squares, SSR + SSE",
+ "dfTreatment": "the degrees of freedom among the groups, i.e., the number of groups minus one.",
+ "dfError": "the degrees of freedom within the groups, a little less than the number of cases.",
+ "s": "sample standard deviation",
+ "mean": "the mean of the attribute you're looking at",
+ "mean1": "the mean of the left attribute",
+ "mean2": "the mean of the right attribute",
+ "diff": "the difference of means you are studying",
+ "pDiff": "the difference of proportions you are studying",
+ "SE": "the standard error of the mean (or proportion)",
+ "slope" : "the slope of the least-squares line",
+ "intercept" : "the intercept of the least-squares line",
+ "correlation" : "the correlation between the two attributes",
+ "rho" : "the correlation between the two attributes",
+ "rsq" : "the square of the correlation coefficient",
+ "pos" : "the x-position of the inflection point (P = 1/2)",
+ "LSlope" : "the slope of the curve at x = pos",
+ "cost" : "the abstract cost of the fit (think sum of squares), to be minimized",
+ "rate" : "fit iteration parameter",
+ "iterations" : "number of iterations"
+ },
+ "tips": {
+ "equalize": "make all proportions equal"
+ },
+ "tests": {
+
+ "emitSummary" : "Emit data into CODAP",
+ "oneSampleT": {
+ "menuString": "mean of •1•",
+ "testQuestion": "Is the mean of •1• •2• •3• ?",
+ "resultsLine2": "sample mean = •1•, •2•% CI = [•3•, •4•]",
+ "configurationStart": "testing mean"
+ },
+ "paired": {
+ "menuString": "paired test of (•1• – •2•)",
+ "testQuestion": "Is the (paired) mean difference of each (•1• – •2•) •3• •4• ?",
+ "resultsLine2": "mean paired difference = •1•, •2•% CI = [•3•, •4•]",
+ "configurationStart": "paired test: mean of"
+ },
+ "twoSampleT": {
+ "menuString1": "difference of means: •1• vs •2•",
+ "menuString2": "difference of means: •1• grouped by •2•",
+ "testQuestion1": "Is the difference mean(•1•) : group •2• – group •3• •4• ?",
+ "testQuestion2": "Is the difference mean(•1•) – mean(•2•) •3• ?",
+ "detailsSummary": "Difference of means, two-sample t procedure ",
+ "configStartPaired" : "testing mean(•1•)",
+ "configStart1": "group •1• – group •2•",
+ "configStartUnpaired": "unpaired test"
+ },
+ "regression": {
+ "menuString": "linear regression of (•1•) as a function of (•2•)",
+ "testQuestion": "How does (•1•) depend on (•2•) ?",
+ "detailsSummary": "Regression details ",
+ "testingSlope": "testing slope"
+ },
+ "correlation": {
+ "menuString": "correlation of (•1•) with (•2•)",
+ "testQuestion": "Is the correlation ρ between (•1•) and (•2•) •3• •4•?",
+ "detailsSummary": "Correlation details ",
+ "testingCorrelation": "testing correlation"
+ },
+ "oneSampleP": {
+ "menuString": "one-sample proportion of •1• = •2•",
+ "testQuestion": "Is the proportion of (•1• = •2•) •3• •4• ?",
+ "resultsLine1": "sample proportion = •1•, (•2• out of N = •3•)",
+ "configurationStart": "testing prop",
+ "usingZProc" : "using the z procedure",
+ "usingBinomialProc" : "using the exact binomial procedure"
+ },
+ "twoSampleP": {
+ "menuString1": "difference of proportions: •1• vs •2•",
+ "menuString2": "difference of proportions: •1• grouped by •2•",
+ "testQuestionHead": "Is the difference of proportions of",
+ "detailsSummary": "Difference of proportions, two-sample z procedure ",
+ "configStart": "testing difference of proportions"
+ },
+ "goodness": {
+ "menuString": "goodness of fit for •1•",
+ "testQuestion": "Are the proportions of (•1•) as hypothesized?",
+ "detailsSummary1": "Testing goodness of fit, •1•-sided χ2 procedure ",
+ "detailsSummary2": "set hypothesized proportions ",
+ "configurationStart": "configure goodness-of-fit test"
+ },
+ "independence": {
+ "menuString": "test independence of •1• from •2•",
+ "testQuestion": "Are (•1•) and (•2•) independent?",
+ "detailsSummary": "Testing independence, •1•-sided χ2 procedure ",
+ "configurationStart": "configure test for independence of (•1•) from (•2•)"
+ },
+ "anova" : {
+ "menuString": "ANOVA: •1• by •2•",
+ "testQuestion": "Is the mean of (•1•) the same across (•2•) ?",
+ "detailsSummary1": "Descriptive statistics ",
+ "detailsSummary2": "One-way ANOVA, F procedure ",
+ "configStart": "ANOVA on (•1•) across (•2•)",
+ "meanOfX": "mean(•1•)"
+ },
+ "logistic": {
+ "menuString": "logistic regression: •1• as a function of •2•",
+ "intro": "This plugin does simple logistic regression",
+ "model1": "Model: Probability = 1/2 at •1• = •2•",
+ "model2": "There, slope = •1•",
+ "probFunctionHead": "so the probability function is",
+ "probQuery1": "Find the probability P(•1• = •2•)",
+ "probQuery2": "at (•1•)",
+ "configStart": "Logistic regression predicting prob(•1• = •2•) from •3•"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/strings/testimate_German.json b/eepsmedia/plugins/testimate/strings/testimate_German.json
new file mode 100644
index 00000000..1c289a9a
--- /dev/null
+++ b/eepsmedia/plugins/testimate/strings/testimate_German.json
@@ -0,0 +1,239 @@
+{
+ "testimate": {
+ "flags": "🇩🇪,🇦🇹,🇨🇭",
+ "language": "Deutsch",
+ "attributeDescriptions": {
+ "N": "Stichprobe",
+ "prop": "der untersuchte Stichprobenanteil",
+ "P": "der \"p-Wert\", also die Wahrscheinlichkeit, diesen oder einen noch extremeren Teststatistikwert zu erhalten, wenn die Nullhypothese wahr ist.",
+ "CImin": "die untere Grenze des Konfidenzintervalls",
+ "CImax": "die obere Grenze des Konfidenzintervalls",
+ "alpha": "die festgelegte Wahrscheinlichkeit, um zu entscheiden, ob der p-Wert \"signifikant\" ist. Vorsicht bei Signifikanz.",
+ "conf": "der Wert, in Prozent, der genutzt wird, um das Konfidenzintervall zu bestimmen",
+ "t": "die t-Statistik, das Residuum geteilt durch durch den Standardfehler",
+ "z": "die z-Statistik: Die Differenz vom hypothetischen Wert, geteilt durch die Stichprobenstandardsabweichung",
+ "chisq": "die Chi²-Statistik. Sie misst, wie weit die Zelleinträge von den \"erwarteten\" Werten abweichen.",
+ "F": "die F-Statistik. Sie misst, wie groß die Varianz zwischen den Gruppen im Gegensatz zu denen innerhalb der Gruppen ist.",
+ "tCrit": "ein kritischer Wert für t: Der Wert, der zum Signifikanzniveau alpha gehört",
+ "zCrit": "ein kritischer Wert für z: Der Wert, der zum Signifikanzniveau alpha gehört",
+ "chisqCrit": "ein kritischer Wert für Chi²: Der Wert, der zum Signifikanzniveau alpha gehört",
+ "value": "der hypothetische Wert, der mit dem Teststatistikwert verglichen wird",
+ "outcome": "der Name der Zielvariable (links)",
+ "predictor": "der Name der Prädiktorvariable (rechts), auch wenn sie nicht zur Prädiktion genutzt wird",
+ "procedure": "welches statistische Verfahren genutzt wird",
+ "SSR": "die Summe der quadratischen Abweichungen zwischen den Gruppen",
+ "SSE": "die Summe der quadratischen Abweichungen innerhalb der Gruppe, relativ zu den Gruppenmittelwerten",
+ "SST": "die Summe der quatrischen Abweichungen, SQA + SQR",
+ "dfTreatment": "die Anzahl der Freiheitsgrade zwischen den Gruppen, d.h. die Anzahl der Gruppen minus 1.",
+ "dfError": "die Anzahl der Freiheitsgrade innerhalb der Gruppen, etwas weniger als die Anzahl der Fälle.",
+ "s": "Stichprobenstandardabweichung",
+ "mean": "das arithmetische Mittel des betrachteten Merkmals",
+ "diff": "die Differenz der betrachteten Mittelwerte",
+ "SE": "der Standardfehler vom arithmetischen Mittel (oder dem Anteil)",
+ "FCrit": "ein kritischer Wert für die F Statistik",
+ "N1": "die Stichprobengröße, erste Gruppe",
+ "N2": "die Stichprobengröße, zweite Gruppe",
+ "prop1": "der Anteil in der Stichprobe, erste Gruppe",
+ "prop2": "der Anteil in der Stichprobe, zweite Gruppe",
+ "df": "Freiheitsgrade",
+ "slopeCImin": "die untere Grenze des Konfidenzintervalls für die Steigung",
+ "slopeCImax": "die obere Grenze des Konfidenzintervalls für die Steigung",
+ "sign": "Zusammenhang des Teststatistikwertes zu dem Wert in der Box",
+ "sides": "Wie viele Seiten im Test? Eine oder zwei?",
+ "mean1": "Der Mittelwert des linken Merkmals",
+ "mean2": "Der Mittelwert des rechten Merkmals",
+ "pDiff": "Der Unterschied in den betrachteten Anteilen",
+ "slope": "Die Steigung der Regressionsgeraden",
+ "intercept": "Der y-Achsenabschnitt der Regressionsgeraden",
+ "correlation": "Die Korrelation zwischen den beiden Merkmalen",
+ "rho": "Die Korrelation zwischen den beiden Merkmalen",
+ "rsq": "Das Quadrat des Korrelationskoeffizienten",
+ "pos": "Die x-Koordinate des Wendepunkts (P=1/2)",
+ "LSlope": "Die Steigung der Kurve bei x = pos",
+ "cost": "die abstrakten Kosten der Anpassung (man denke an die Summe der Quadrate), die es zu minimieren gilt",
+ "rate": "Iterationsparameter für die Anpassung",
+ "iterations": "Anzahl der Wiederholungen"
+ },
+ "dropAttributeHere": "Merkmal hier ablegen",
+ "tests": {
+ "oneSampleP": {
+ "menuString": "1-Stichproben-Test auf Anteilswert von •1• = •2•",
+ "testQuestion": "Ist der Anteil von (•1• = •2•) •3• •4• ?",
+ "resultsLine1": "Anteil in der Stichprobe = •1•, (•2• von N = •3•)",
+ "configurationStart": "Test auf Anteilswert",
+ "usingZProc": "nach dem Gauß-Test (z)",
+ "usingBinomialProc": "nach dem exakten Binomial-Test"
+ },
+ "oneSampleT": {
+ "menuString": "Arithmetisches Mittel von •1•",
+ "testQuestion": "Ist das arithmetische Mittel von •1• •2• •3• ?",
+ "resultsLine2": "Arithmetisches Mittel = •1•, •2•% CI = [•3•, •4•]",
+ "configurationStart": "Test auf Mittelwert"
+ },
+ "paired": {
+ "menuString": "Test auf (•1• – •2•) für abhängige Stichproben",
+ "testQuestion": "Ist die (gepaarte) Mittelwertsdifferenz of jedem (•1• – •2•) •3• •4• ?",
+ "resultsLine2": "Gepaare Mittelwertdifferenz = = •1•, •2•% CI = [•3•, •4•]",
+ "configurationStart": "Test für abhängige Stichproben: Mittelwert von"
+ },
+ "twoSampleT": {
+ "menuString1": "Differenz der Mittelwerte: •1• vs •2•",
+ "menuString2": "Differenz der Mittelwerte •1• gruppiert durch •2•",
+ "testQuestion1": "Ist die Differenz arithemtisches Mittel (•1•) : Gruppe •2• – Gruppe •3• •4• ?",
+ "testQuestion2": "Ist die Differenz arithmetisches Mittel (•1•) – arithmetisches Mittel(•2•) •3• ?",
+ "detailsSummary": "Differenz der Mittelwerte, 2-Stichprobenfall t Verfahren ",
+ "configStart1": "Gruppe •1• – Gruppe •2•",
+ "configStartPaired": "Test auf Mittelwert(•1•)",
+ "configStartUnpaired": "unabhängiger Test"
+ },
+ "regression": {
+ "menuString": "Lineare Regression von (•1•) als eine Funktion von (•2•)",
+ "testQuestion": "Wie hängt (•1•) von (•2•) ab?",
+ "detailsSummary": "Regression Zusammenfassung ",
+ "testingSlope": "Test auf Steigung"
+ },
+ "twoSampleP": {
+ "menuString1": "Unterschied der Anteile: •1• vs •2•",
+ "menuString2": "Unterschied der Anteile: •1• gruppiert durch •2•",
+ "testQuestionHead": "Ist der Unterschied der Anteile von",
+ "detailsSummary": "Unterschied der Anteile, 2-Stichprobenfall z Zusammenfassung ",
+ "configStart": "Test auf Unterschied der Anteile"
+ },
+ "goodness": {
+ "menuString": "Anpassungstest für •1•",
+ "testQuestion": "Sind die Anteile für (•1•) wie erwartet?",
+ "detailsSummary1": "Anpassungstest, •1•-seitig Χ2 procedure ",
+ "detailsSummary2": "hypothetische Anteile festlegen ",
+ "configurationStart": "konfiguriere Anpassungstest"
+ },
+ "independence": {
+ "menuString": "Test auf Unabhängigkeit von •1• und •2•",
+ "testQuestion": "Sind (•1•) und (•2•) unabhängig?",
+ "detailsSummary": "Test auf Unabhängigkeit, •1•-seitig χ2 Zusammenfassung ",
+ "configurationStart": "konfiguriere Test auf Unabhängigkeit von (•1•) und (•2•)"
+ },
+ "anova": {
+ "menuString": "ANOVA: •1• durch •2•",
+ "testQuestion": "Ist das arithmetische Mittel von (•1•) gleich für (•2•) ?",
+ "detailsSummary1": "Deskriptive Statistiken ",
+ "detailsSummary2": "Einfaktorielle ANOVA, F Zusammenfassung ",
+ "configStart": "ANOVA von (•1•) für (•2•)",
+ "meanOfX": "arithmetisches Mittel(•1•)"
+ },
+ "logistic": {
+ "menuString": "Logistische Regression: •1• als eine Funktion von •2•",
+ "intro": "Dieses PlugIn führt einfache logistische Regression durch",
+ "model1": "Modell: Wahrscheinlichkeit = 1/2 für •1• = •2•",
+ "model2": "Hier, Steigung = •1•",
+ "probFunctionHead": "also ist die Wahrscheinlichkeitsfunktion",
+ "probQuery1": "Finde die Wahrscheinlichkeit P(•1• = •2•)",
+ "probQuery2": "durch (•1•)",
+ "configStart": "Logistische Regression zur Vorhersage von P(•1• = •2•) unter •3•"
+ },
+ "emitSummary": "Daten an CODAP übermitteln",
+ "correlation": {
+ "menuString": "Korrelation von (•1•) mit (•2•)",
+ "testQuestion": "Ist die Korrelation ρ zwischen (•1•) und (•2•) •3• •4•?",
+ "detailsSummary": "Korrelation Zusammenfassung ",
+ "testingCorrelation": "Teste Korrelation"
+ }
+ },
+ "attributeNames": {
+ "N": "N",
+ "prop": "Anteil",
+ "P": "p",
+ "CImin": "KI_min",
+ "CImax": "KI_max",
+ "alpha": "alpha",
+ "conf": "Konfidenzniveau",
+ "t": "t",
+ "z": "z",
+ "chisq": "Chi²",
+ "F": "F",
+ "tCrit": "tkrit",
+ "zCrit": "zkrit",
+ "chisqCrit": "Chi²krit",
+ "FCrit": "Fkrit",
+ "value": "Wert",
+ "outcome": "Ergebnis",
+ "predictor": "Prädiktor",
+ "procedure": "Prozedere",
+ "SSR": "SQA",
+ "SSE": "SQR",
+ "SST": "SQT",
+ "dfTreatment": "dfExperimental",
+ "dfError": "dfFehler",
+ "s": "s",
+ "mean": "Arithmetisches Mittel",
+ "diff": "diff",
+ "SE": "SE",
+ "N1": "N1",
+ "N2": "N2",
+ "prop1": "Anteil1",
+ "prop2": "Anteil2",
+ "df": "df",
+ "slopeCImin": "Steigung_KImin",
+ "slopeCImax": "Steigung_KImax",
+ "sign": "Zeichen",
+ "sides": "Seite",
+ "mean1": "Mittelwert1",
+ "mean2": "Mittelwert2",
+ "pDiff": "diff",
+ "slope": "Steigung",
+ "intercept": "y-Achsenabschnitt",
+ "correlation": "Korrelation",
+ "rho": "rho",
+ "rsq": "rQuadrat",
+ "pos": "pos",
+ "LSlope": "Steigung",
+ "cost": "cost",
+ "rate": "rate",
+ "iterations": "Wiederholungen"
+ },
+ "staticStrings": {
+ "outcomePrimaryHint": "Zielvariable/erstes Merkmal",
+ "predictorSecondaryHint": "Prädiktor/zweites Merkmal",
+ "emitDetailsSummary": "Testergebnisse an CODAP übermitteln",
+ "chooseEmitSingleLabel": "vorliegender Test",
+ "chooseEmitRandomLabel": "Neue Stichprobe ziehen",
+ "chooseEmitHierarchyLabel": "jede Untergruppe",
+ "randomEmitNumberBoxLabel": "mal"
+ },
+ "datasetDIV": "Datensatz: •1• , •2• Fälle (•3•)",
+ "mean": "arithmetisches Mittel",
+ "proportion": "Anteil",
+ "group": "Gruppe",
+ "slope": "Steigung",
+ "intercept": "y-Achsenabschnitt",
+ "value": "Wert",
+ "values": "Werte",
+ "pooled": "gepoolt",
+ "equalize": "angleichen",
+ "observed": "beobachtet",
+ "expected": "erwartet",
+ "tips": {
+ "equalize": "alle Anteile angleichen"
+ },
+ "count": "Anzahl",
+ "error": "Fehler",
+ "total": "gesamt",
+ "iterations": "Wiederholungen",
+ "showGraph": "zeige Graphen",
+ "copyFormula": "kopiere die Formel",
+ "emit": "senden",
+ "emitRR": "Neue Stichprobe(n) ziehen und sende •1•x",
+ "at": "unter",
+ "rate": "Rate",
+ "cost": "Kosten",
+ "nMore": "•1• mehr",
+ "groups": "Gruppen",
+ "emitHierarchy": "Testergebnisse von •1• Untergruppe(n)",
+ "hasRandom": "ist zufällig",
+ "noRandom": "ist nicht zufällig",
+ "CI": "KI",
+ "conf": "konf",
+ "notP": "nicht •1•",
+ "datasetName": "Testergebnisse und Schätzer",
+ "Nsided": "•1•-seitig"
+ }
+}
\ No newline at end of file
diff --git a/eepsmedia/plugins/testimate/strings/testimate_Spanish.json b/eepsmedia/plugins/testimate/strings/testimate_Spanish.json
new file mode 100644
index 00000000..c42c562d
--- /dev/null
+++ b/eepsmedia/plugins/testimate/strings/testimate_Spanish.json
@@ -0,0 +1,142 @@
+{
+ "testimate": {
+ "flags": "🇲🇽,🇪🇸,🇨🇷,🇨🇱",
+ "language": "Español",
+ "attributeDescriptions": {
+ "N": "the sample size",
+ "prop": "the sample proportion you are studying",
+ "P": "the \"P-value,\" the probability that, if the null hypothesis were true, we would see a value of the test statistic this extreme.",
+ "CImin": "the lower bound to the confidence interval",
+ "CImax": "the upper bound to the confidence interval",
+ "alpha": "the probability we use to decide if a P-value is \"significant.\" Use significance with caution.",
+ "conf": "the value, in percent, used to compute the confidence interval",
+ "t": "the t statistic, the z value divided by the square root of N",
+ "z": "the z statistic: the difference from the hypothesized value divided by the sample standard deviation",
+ "chisq": "the chi-squared statistic. It measures how far a set of counts is from the \"expected\" value.",
+ "F": "the F statistic. It measures how mauch variation is between groups as opposed to within groups.",
+ "tCrit": "a critical value for t: the value that corresponds to the probability alpha",
+ "zCrit": "a critical value for z: the value that corresponds to the probability alpha",
+ "chisqCrit": "a critical value for chi-squared: the value that corresponds to the probability alpha",
+ "value": "the hypothesized value you are comparing your test statistic to",
+ "outcome": "the name of the left-hand (outcome) variable",
+ "predictor": "the name of the right-hand (predictor) variable, even if it's not being used to predict anything",
+ "procedure": "what kind of statistical procedure was used",
+ "SSR": "the sum of squares of residuals calculated between the groups",
+ "SSE": "the sum of squares of errors, that is, calculated within the groups relative to the group means",
+ "SST": "the total sum of squares, SSR + SSE",
+ "dfTreatment": "the degrees of freedom among the groups, i.e., the number of groups minus one.",
+ "dfError": "the degrees of freedom within the groups, a little less than the number of cases.",
+ "s": "desviación estándar (DE) de la muestra",
+ "mean": "the mean of the attribute you're looking at",
+ "diff": "the difference of means (or proportions) you are studying",
+ "SE": "the standard error of the mean (or proportion)",
+ "FCrit": "a critical value for the F statistic"
+ },
+ "dropAttributeHere": "ponga aquí el atributo",
+ "tests": {
+ "oneSampleP": {
+ "menuString": "one-sample proportion of •1• = •2•",
+ "testDescription": "proporción de (•1• = •2•) •3• •4•",
+ "testQuestion": "Is the proportion of (•1• = •2•) •3• •4• ?",
+ "resultsLine1": "sample proportion = •1•, (•2• out of N = •3•)",
+ "configurationStart": "testing prop"
+ },
+ "oneSampleT": {
+ "menuString": "mean of •1•",
+ "testQuestion": "Is the mean of •1• •2• •3• ?",
+ "resultsLine2": "sample mean = •1•, •2•% CI = [•3•, •4•]",
+ "configurationStart": "testing mean"
+ },
+ "paired": {
+ "menuString": "paired test of (•1• - •2•)",
+ "testQuestion": "Is the (paired) mean difference of each (•1• – •2•) •3• •4• ?",
+ "resultsLine2": "paired mean difference = •1•, •2•% CI = [•3•, •4•]",
+ "configurationStart": "paired test of (•1• – •2•)"
+ },
+ "twoSampleT": {
+ "menuString1": "difference of means: •1• vs •2•",
+ "menuString2": "difference of means: •1• grouped by •2•",
+ "testQuestion1": "Is the difference mean(•1•) : group •2• – group •3• •4• ?",
+ "testQuestion2": "Is the difference mean(•1•) – mean(•2•) •3• ?",
+ "detailsSummary": "Difference of means, two-sample t procedure ",
+ "configStart1": "testing mean(•1•) : group •2• – group •3•",
+ "configStart2": "testing mean(•1•) – mean (•2•)"
+ },
+ "regression": {
+ "menuString": "linear regression of (•1•) as a function of (•2•)",
+ "testQuestion": "How does (•1•) depend on (•2•) ?",
+ "detailsSummary": "Regression details ",
+ "testingSlope": "testing slope"
+ },
+ "twoSampleP": {
+ "menuString1": "difference of proportions: •1• vs •2•",
+ "menuString2": "difference of proportions: •1• grouped by •2•",
+ "testQuestionHead": "Is the difference of proportions of",
+ "detailsSummary": "Difference of proportions, two-sample z procedure ",
+ "configStart": "testing difference of proportions"
+ },
+ "goodness": {
+ "menuString": "goodness of fit for •1•",
+ "testQuestion": "Are the proportions of (•1•) as hypothesized?",
+ "detailsSummary1": "Testing goodness of fit, χ2 procedure ",
+ "detailsSummary2": "set hypothesized proportions ",
+ "configurationStart": "configure goodness-of-fit test"
+ },
+ "independence": {
+ "menuString": "test independence of •1• from •2•",
+ "testQuestion": "Are (•1•) and (•2•) independent?",
+ "detailsSummary": "Testing independence, χ2 procedure ",
+ "configurationStart": "configure test for independence of (•1•) from (•2•)"
+ }
+ },
+ "attributeNames": {
+ "N": "N",
+ "prop": "prop",
+ "P": "P",
+ "CImin": "ICmin",
+ "CImax": "ICmax",
+ "alpha": "alfa",
+ "conf": "conf",
+ "t": "t",
+ "z": "z",
+ "chisq": "chisq",
+ "F": "F",
+ "tCrit": "tCrit",
+ "zCrit": "zCrit",
+ "chisqCrit": "chisqCrit",
+ "FCrit": "FCrit",
+ "value": "valor",
+ "outcome": "outcome",
+ "predictor": "predictor",
+ "procedure": "procedure",
+ "SSR": "SSR",
+ "SSE": "SSE",
+ "SST": "SST",
+ "dfTreatment": "dfTreatment",
+ "dfError": "dfError",
+ "s": "s",
+ "mean": "mean",
+ "diff": "diff",
+ "SE": "SE"
+ },
+ "staticStrings": {
+ "outcomePrimaryHint": "outcome/primary attribute",
+ "predictorSecondaryHint": "predictor/secondary attribute"
+ },
+ "datasetDIV": "Dataset: •1• , •2• cases",
+ "mean": "mean",
+ "proportion": "proportion",
+ "group": "grupo",
+ "slope": "slope",
+ "intercept": "intercept",
+ "value": "valor",
+ "values": "valores",
+ "pooled": "pooled",
+ "equalize": "equalize",
+ "observed": "observado",
+ "expected": "esperado",
+ "tips": {
+ "equalize": "make all proportions equal"
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/build-num.txt b/lib/build-num.txt
index 236ef3cd..9cf632ba 100644
--- a/lib/build-num.txt
+++ b/lib/build-num.txt
@@ -1 +1 @@
-0063
+0064
diff --git a/onboarding/source/index.html b/onboarding/source/index.html
index ad6bef64..24c8aed2 100644
--- a/onboarding/source/index.html
+++ b/onboarding/source/index.html
@@ -10,7 +10,7 @@
- If you can see this, React is not working right.
+ Loading ...
diff --git a/onboarding/source/js/localeManager.js b/onboarding/source/js/localeManager.js
index e7520fef..8b2923da 100644
--- a/onboarding/source/js/localeManager.js
+++ b/onboarding/source/js/localeManager.js
@@ -112,4 +112,8 @@ function tr(sID, args) {
let s = resolve(sID);
let ix = 0;
return s.replace(/%@[0-9]?/g, replacer);
+}
+
+function resourceDir(){
+ return (locale !== "en-us" ? locale + "/" : "");
}
\ No newline at end of file
diff --git a/onboarding/source/js/onboarding.js b/onboarding/source/js/onboarding.js
index c2cdc8ab..fc4e5952 100644
--- a/onboarding/source/js/onboarding.js
+++ b/onboarding/source/js/onboarding.js
@@ -95,7 +95,7 @@ class DraggableLink extends React.Component {
handleDragStart(event) {
let dt = event.dataTransfer,
- tUrl = window.location.href.replace(/\/[^\/]*$/, "") + "/resources/"+tr("~onboarding1.csv.filename");
+ tUrl = window.location.href.replace(/\/[^\/]*$/, "") + "/resources/" + resourceDir() + "mammals.csv";
let ix;
for (let i = 0; i < dt.items.length; i++) {
if (dt.items[i].kind === 'file') {
@@ -403,12 +403,13 @@ function getStarted() {
console.log(msg);
});
- if (!hasMouse) {
+ if ((!hasMouse && onboarding1) || (!onboarding1)) {
+ csvToLoad = (onboarding1 ? "mammals.csv" : "nhanes.csv");
codapInterface.sendRequest({
action: 'create',
resource: 'dataContextFromURL',
values: {
- URL: window.location.href.replace(/\/[^\/]*$/, "") + "/resources/"+tr("~onboarding1.csv.filename")
+ URL: window.location.href.replace(/\/[^\/]*$/, "") + "/resources/" + resourceDir() + csvToLoad
}
}).then(function (iResult) {
console.log('Created data context from URL');
diff --git a/onboarding/source/js/task_descriptions.js b/onboarding/source/js/task_descriptions.js
index a2314b40..a1d606f8 100644
--- a/onboarding/source/js/task_descriptions.js
+++ b/onboarding/source/js/task_descriptions.js
@@ -1,8 +1,9 @@
hasMouse = !('ontouchstart' in window);
+onboarding1 = true;
taskDescriptions = {
descriptions: [hasMouse ? {
- key: 'Drag', label: tr("~onboarding1.drag.task"), url: './resources/'+tr("~onboarding1.drag.movie.filename"),
+ key: 'Drag', label: tr("~onboarding1.drag.task"), url: './resources/' + resourceDir() + "DragCSV.mp4",
operation: 'dataContextCountChanged',
feedback: React.createElement(
'div',
@@ -24,7 +25,7 @@ taskDescriptions = {
)
)
} : {
- key: 'MakeTable', label: tr("~onboarding1.make.table.task"), url: './resources/' + tr("~onboarding1.make.table.movie.filename"),
+ key: 'MakeTable', label: tr("~onboarding1.make.table.task"), url: './resources/' + resourceDir() + "MakeTable.mp4",
feedback: React.createElement(
'div',
null,
@@ -45,7 +46,7 @@ taskDescriptions = {
)
)
}, {
- key: 'MakeGraph', label: tr("~onboarding1.graph.task"), url: './resources/' + tr("~onboarding1.graph.movie.filename"),
+ key: 'MakeGraph', label: tr("~onboarding1.graph.task"), url: './resources/' + resourceDir() + "MakeGraph.mp4",
feedback: React.createElement(
'div',
null,
@@ -75,7 +76,7 @@ taskDescriptions = {
)
)
}, {
- key: 'MoveComponent', label: tr("~onboarding1.move.table.task"), url: './resources/' + tr("~onboarding1.move.table.movie.filename"),
+ key: 'MoveComponent', label: tr("~onboarding1.move.table.task"), url: './resources/' + resourceDir() + "MoveGraph.mp4",
operation: 'move', type: ['DG.GraphView', 'DG.TableView'],
feedback: React.createElement(
'div',
@@ -103,7 +104,7 @@ taskDescriptions = {
*/
{
- key: 'AssignAttribute', label: tr("~onboarding1.drag.attribute.task"), url: './resources/' + tr("~onboarding1.drag.attribute.movie.filename"),
+ key: 'AssignAttribute', label: tr("~onboarding1.drag.attribute.task"), url: './resources/' + resourceDir() + "DragAttribute.mp4",
feedback: React.createElement(
'div',
null,
@@ -124,7 +125,7 @@ taskDescriptions = {
)
)
}, {
- key: 'SecondAttribute', label: tr("~onboarding1.drag.second.attribute.task"), url: './resources/' + tr("~onboarding1.drag.second.attribute.movie.filename"),
+ key: 'SecondAttribute', label: tr("~onboarding1.drag.second.attribute.task"), url: './resources/' + resourceDir() + "Drag2ndAttribute.mp4",
feedback: React.createElement(
'div',
null,
diff --git a/onboarding/source/js/task_descriptions_2.js b/onboarding/source/js/task_descriptions_2.js
index 4d467201..4b1eaacb 100644
--- a/onboarding/source/js/task_descriptions_2.js
+++ b/onboarding/source/js/task_descriptions_2.js
@@ -1,9 +1,10 @@
-hasMouse = true; // This is a kludge to prevent loading of Mammals on touch devices
+hasMouse = !('ontouchstart' in window);
+onboarding1 = false;
taskDescriptions = {
descriptions: [{
key: 'MakeScatterplot', label: tr("~onboarding2.make.scatterplot.task"),
- url: './resources/' + tr("~onboarding2.make.scatterplot.movie.filename"),
+ url: './resources/' + resourceDir() + "MakeScatterplot.mp4",
operation: 'attributeChange', type: '',
requiresSpecialHandling: true,
feedback: React.createElement(
@@ -17,7 +18,7 @@ taskDescriptions = {
)
}, {
key: 'SelectCases', label: tr("~onboarding2.select.cases.task"),
- url: './resources/' + tr("~onboarding2.select.cases.movie.filename"),
+ url: './resources/' + resourceDir() + "SelectCases.mp4",
operation: 'selectCases',
constraints: [{ property: 'cases', value: true }],
prereq: 'MakeScatterplot',
@@ -37,7 +38,7 @@ taskDescriptions = {
)
}, {
key: 'HideUnselected', label: tr("~onboarding2.hide.unselected.task"),
- url: './resources/' + tr("~onboarding2.hide.unselected.movie.filename"),
+ url: './resources/' + resourceDir() + "HideUnselected.mp4",
operation: 'hideUnselected', type: '',
prereq: 'SelectCases',
feedback: React.createElement(
@@ -56,7 +57,7 @@ taskDescriptions = {
)
}, {
key: 'Deselect', label: tr("~onboarding2.deselect.task"),
- url: './resources/' + tr("~onboarding2.deselect.movie.filename"),
+ url: './resources/' + resourceDir() + "Deselect.mp4",
operation: 'selectCases',
constraints: [{ property: 'cases', value: false }],
prereq: 'HideUnselected',
@@ -71,7 +72,7 @@ taskDescriptions = {
)
}, {
key: 'Rescale', label: tr("~onboarding2.rescale.task"),
- url: './resources/' + tr("~onboarding2.rescale.movie.filename"),
+ url: './resources/' + resourceDir() + "Rescale.mp4",
operation: 'rescaleGraph', type: '',
prereq: 'HideUnselected',
feedback: React.createElement(
@@ -85,7 +86,7 @@ taskDescriptions = {
)
}, {
key: 'MakeLegend', label: tr("~onboarding2.add.legend.task"),
- url: './resources/' + tr("~onboarding2.add.legend.movie.filename"),
+ url: './resources/' + resourceDir() + "MakeLegend.mp4",
operation: 'legendAttributeChange', type: 'DG.GraphModel',
constraints: [{ property: 'attributeName', value: tr("~legend.attribute") }],
prereq: 'MakeScatterplot',
diff --git a/onboarding/source/onboarding_2.html b/onboarding/source/onboarding_2.html
index 31ed1518..acb520a3 100644
--- a/onboarding/source/onboarding_2.html
+++ b/onboarding/source/onboarding_2.html
@@ -10,7 +10,7 @@
- If you can see this, React is not working right.
+ Loading ...
diff --git a/onboarding/source/resources/es/Deselect.mp4 b/onboarding/source/resources/es/Deselect.mp4
index a68ad9d3..68c9c04f 100644
Binary files a/onboarding/source/resources/es/Deselect.mp4 and b/onboarding/source/resources/es/Deselect.mp4 differ
diff --git a/onboarding/source/resources/es/Drag2ndAttribute.mp4 b/onboarding/source/resources/es/Drag2ndAttribute.mp4
index 3a2a3950..11dfb802 100644
Binary files a/onboarding/source/resources/es/Drag2ndAttribute.mp4 and b/onboarding/source/resources/es/Drag2ndAttribute.mp4 differ
diff --git a/onboarding/source/resources/es/DragAttribute.mp4 b/onboarding/source/resources/es/DragAttribute.mp4
index 75a73855..99ace1c9 100644
Binary files a/onboarding/source/resources/es/DragAttribute.mp4 and b/onboarding/source/resources/es/DragAttribute.mp4 differ
diff --git a/onboarding/source/resources/es/HideUnselected.mp4 b/onboarding/source/resources/es/HideUnselected.mp4
index 93ae4bca..aaaf7e39 100644
Binary files a/onboarding/source/resources/es/HideUnselected.mp4 and b/onboarding/source/resources/es/HideUnselected.mp4 differ
diff --git a/onboarding/source/resources/es/MakeLegend.mp4 b/onboarding/source/resources/es/MakeLegend.mp4
index 5de8c31c..151987a0 100644
Binary files a/onboarding/source/resources/es/MakeLegend.mp4 and b/onboarding/source/resources/es/MakeLegend.mp4 differ
diff --git a/onboarding/source/resources/es/MakeScatterplot.mp4 b/onboarding/source/resources/es/MakeScatterplot.mp4
index 005f97a5..4d794fb9 100644
Binary files a/onboarding/source/resources/es/MakeScatterplot.mp4 and b/onboarding/source/resources/es/MakeScatterplot.mp4 differ
diff --git a/onboarding/source/resources/es/MakeTable.mp4 b/onboarding/source/resources/es/MakeTable.mp4
new file mode 100644
index 00000000..f060f803
Binary files /dev/null and b/onboarding/source/resources/es/MakeTable.mp4 differ
diff --git a/onboarding/source/resources/es/Rescale.mp4 b/onboarding/source/resources/es/Rescale.mp4
index e49ce9fc..380670a6 100644
Binary files a/onboarding/source/resources/es/Rescale.mp4 and b/onboarding/source/resources/es/Rescale.mp4 differ
diff --git a/onboarding/source/resources/es/SelectCases.mp4 b/onboarding/source/resources/es/SelectCases.mp4
index b3c5d6db..98e1ddb0 100644
Binary files a/onboarding/source/resources/es/SelectCases.mp4 and b/onboarding/source/resources/es/SelectCases.mp4 differ
diff --git a/onboarding/source/resources/es/mammals.csv b/onboarding/source/resources/es/mammals.csv
index 6d6cd9a2..f097da4e 100644
--- a/onboarding/source/resources/es/mammals.csv
+++ b/onboarding/source/resources/es/mammals.csv
@@ -1,28 +1,28 @@
-Mammal,Order,LifeSpan,Height,Mass,Sleep,Speed,Habitat,Diet
-African Elephant,Proboscidae,70,4,6400,3,40,land,plants
-Asian Elephant,Proboscidae,70,3,5000,4,40,land,plants
-Big Brown Bat,Chiroptera,19,0.1,0.02,20,40,land,meat
-Bottlenose Dolphin,Cetacea,25,3.5,635,5,37,water,meat
-Cheetah,Carnivora,14,1.5,50,12,110,land,meat
-Chimpanzee,Primate,40,1.5,68,10,,land,both
-Domestic Cat,Carnivora,16,0.8,4.5,12,50,land,meat
-Donkey,Perissodactyla,40,1.2,187,3,50,land,plants
-Giraffe,Artiodactyla,25,5,1100,2,50,land,plants
-Gray Wolf,Carnivora,16,1.6,80,13,64,land,meat
-Grey Seal,Pinnipedia,30,2.1,275,6,19,both,meat
-Ground Squirrel,Rodentia,9,0.3,0.1,15,19,land,both
-Horse,Perissodactyla,25,1.5,521,3,69,land,plants
-House Mouse,Rodentia,3,0.1,0.03,12,13,land,both
-Human,Primate,80,1.9,80,8,45,land,both
-Jaguar,Carnivora,20,1.8,115,11,60,land,meat
-Killer Whale,Cetacea,50,6.5,4000,,48,water,meat
-Lion,Carnivora,15,2.5,250,20,80,land,meat
-N. American Opossum,Didelphimorphia,5,0.5,5,19,,land,both
-Nine-Banded Armadillo,Xenarthra,10,0.6,7,17,1,land,meat
-Owl Monkey,Primate,12,0.4,1,17,,land,both
-Patas Monkey,Primate,20,0.9,13,,55,land,both
-Pig,Artiodactyla,10,1,192,8,18,land,both
-Pronghorn Antelope,Artiodactyla,10,0.9,70,,98,land,plants
-Rabbit,Lagomorpha,5,0.5,3,11,56,land,plants
-Red Fox,Carnivora,7,0.8,5,10,48,land,both
-Spotted Hyena,Carnivora,25,0.9,70,18,64,land,meat
\ No newline at end of file
+Mamífero,Orden,El Duración de Vida,Altura,masa,Sueño,Velocidad,Habitat,Dieta
+Elefante africano,Proboscidae,70,4,6400,3,40,tierra,plantas
+Elefante asiático,Proboscidae,70,3,5000,4,40,tierra,plantas
+Gran murciélago marrón,Chiroptera,19,0.1,0.02,20,40,tierra,carne
+Delfín nariz de botella,Cetacea,25,3.5,635,5,37,agua,carne
+Cheetah,Carnivora,14,1.5,50,12,110,tierra,carne
+Chimpancé,Primate,40,1.5,68,10,,tierra,ambas
+Gato doméstico,Carnivora,16,0.8,4.5,12,50,tierra,carne
+Burro,Perissodactyla,40,1.2,187,3,50,tierra,plantas
+Jirafa,Artiodactyla,25,5,1100,2,50,tierra,plantas
+Lobo gris,Carnivora,16,1.6,80,13,64,tierra,carne
+Elefante marino,Pinnipedia,30,2.1,275,6,19,ambos,carne
+Ardilla,Rodentia,9,0.3,0.1,15,19,tierra,ambas
+Caballo,Perissodactyla,25,1.5,521,3,69,tierra,plantas
+Ratón,Rodentia,3,0.1,0.03,12,13,tierra,ambas
+Humano,Primate,80,1.9,80,8,45,tierra,ambas
+Jaguar,Carnivora,20,1.8,115,11,60,tierra,carne
+Ballena asesina,Cetacea,50,6.5,4000,,48,agua,carne
+León,Carnivora,15,2.5,250,20,80,tierra,carne
+Zarigüeya,Didephimorphia,5,0.5,5,19,,tierra,ambas
+Armadillo,Xenarthra,10,0.6,7,17,1,tierra,carne
+Lechuza,Primate,12,0.4,1,17,,tierra,ambas
+Mono,Primate,20,0.9,13,,55,tierra,ambas
+Cerdo,Artiodactyla,10,1,192,8,18,tierra,ambas
+Antílope,Artiodactyla,10,0.9,70,,98,tierra,plantas
+Conejo,Lagomorpha,5,0.5,3,11,56,tierra,plantas
+Zorro rojo,Carnivora,7,0.8,5,10,48,tierra,ambas
+Hiena,Carnivora,25,0.9,70,18,64,tierra,carne
\ No newline at end of file
diff --git a/onboarding/source/resources/es/nhanes.csv b/onboarding/source/resources/es/nhanes.csv
new file mode 100644
index 00000000..a671be3a
--- /dev/null
+++ b/onboarding/source/resources/es/nhanes.csv
@@ -0,0 +1,101 @@
+Edad,Altura,Sexo,Nacido en,Educación,IMC,Peso
+34,151,Femenino,Mexico,Secundaria incompleta,24.34,55.5
+5,107.8,Femenino,USA,,16.01,18.6
+68,173,Femenino,USA,Secundaria completa,36.52,109.3
+0,,Femenino,USA,,,6.5
+2,87.9,Femenino,USA,,15.92,12.3
+24,165.1,Femenino,USA,Secundaria completa,46.04,125.5
+70,151.9,Femenino,Mexico,Secundaria incompleta,28.82,66.5
+30,172.4,Femenino,USA,Secundaria completa,26.51,78.8
+59,147.1,Femenino,USA,Secundaria completa,38.73,83.8
+2,89.8,Femenino,USA,,16.62,13.4
+76,157.5,Femenino,USA,Secundaria incompleta,33.1,82.1
+46,163,Femenino,USA,Terciario,55.1,146.4
+28,159.4,Femenino,USA,Secundaria completa,24.4,62
+14,153.3,Femenino,Mexico,Secundaria incompleta,27.23,64
+11,155.2,Femenino,USA,Secundaria incompleta,16.4,39.5
+84,160.9,Femenino,USA,Secundaria incompleta,26.46,68.5
+1,,Femenino,USA,,,10
+72,155.5,Femenino,USA,Terciario,24.73,59.8
+50,161.2,Femenino,USA,Terciario,28.09,73
+8,139,Femenino,USA,Secundaria incompleta,22.98,44.4
+17,166,Femenino,USA,Secundaria incompleta,28.67,79
+21,163.2,Femenino,USA,Terciario,28.53,76
+55,172,Femenino,USA,Secundaria completa,18.76,55.5
+31,154.7,Femenino,Mexico,Secundaria incompleta,29.33,70.2
+33,158.5,Femenino,Mexico,Secundaria incompleta,26.15,65.7
+3,98.7,Femenino,USA,,19.2,18.7
+68,157.7,Femenino,Otro lugar,Secundaria incompleta,32.45,80.7
+62,158.8,Femenino,Mexico,Secundaria incompleta,25.38,64
+55,167.8,Femenino,USA,Secundaria completa,33.7,94.9
+10,141.5,Femenino,USA,Secundaria incompleta,17.48,35
+10,154.4,Femenino,USA,Secundaria incompleta,18.54,44.2
+11,159.8,Femenino,USA,Secundaria incompleta,18.68,47.7
+1,,Femenino,USA,,,13.3
+16,158.1,Femenino,USA,Secundaria incompleta,25.52,63.8
+12,148.2,Femenino,USA,Secundaria incompleta,15.3,33.6
+76,168.6,Femenino,USA,Secundaria completa,23.99,68.2
+61,159.4,Femenino,USA,Terciario,44.12,112.1
+19,163.2,Femenino,USA,Terciario,36.12,96.2
+17,164.4,Femenino,USA,Secundaria incompleta,21.72,58.7
+5,114.7,Femenino,USA,,18.93,24.9
+71,158.5,Femenino,USA,Secundaria incompleta,32.28,81.1
+13,163.3,Femenino,USA,Secundaria incompleta,22.91,61.1
+60,164.1,Femenino,USA,Terciario,32.64,87.9
+1,,Femenino,USA,,,10.7
+4,105.1,Femenino,USA,,16.11,17.8
+11,153.1,Femenino,USA,Secundaria incompleta,23,53.9
+1,,Femenino,USA,,,9.4
+2,92.7,Femenino,USA,,19.43,16.7
+72,159.8,Femenino,Mexico,Secundaria incompleta,31.72,81
+30,162.6,Femenino,USA,Secundaria completa,51.14,135.2
+10,147.2,Femenino,USA,Secundaria incompleta,18.74,40.6
+10,137,Femenino,USA,Secundaria incompleta,17.16,32.2
+1,,Femenino,USA,,,10.4
+76,169,Femenino,USA,Terciario,33.89,96.8
+26,176.1,Femenino,USA,Terciario,24.48,75.9
+46,158.2,Femenino,Otro lugar,Secundaria incompleta,24.01,60.1
+5,115.4,Femenino,USA,,19.37,25.8
+12,153.9,Femenino,USA,Secundaria incompleta,19.38,45.9
+5,123.5,Femenino,USA,,14.56,22.2
+36,173.4,Femenino,USA,Secundaria completa,23.65,71.1
+1,,Masculino,USA,,,10.8
+35,178.9,Masculino,USA,Terciario,28.12,90
+64,156.3,Masculino,Mexico,Secundaria incompleta,36.72,89.7
+15,179.3,Masculino,USA,Secundaria incompleta,21.06,67.7
+85,174.5,Masculino,USA,Secundaria completa,19.44,59.2
+66,177.9,Masculino,USA,Secundaria completa,26.13,82.7
+37,191.7,Masculino,USA,Secundaria incompleta,22.64,83.2
+20,184.1,Masculino,Otro lugar,Secundaria incompleta,20.18,68.4
+31,197.6,Masculino,USA,Secundaria completa,22.97,89.7
+13,169.1,Masculino,USA,Secundaria incompleta,28.5,81.5
+12,162.6,Masculino,USA,Secundaria incompleta,20.5,54.2
+20,182,Masculino,USA,Secundaria completa,23.97,79.4
+0,,Masculino,USA,,,6.3
+55,180.1,Masculino,USA,Terciario,25.62,83.1
+5,109.6,Masculino,USA,,23.89,28.7
+0,,Masculino,USA,,,8.8
+77,182.9,Masculino,USA,Terciario,27.38,91.6
+51,185.4,Masculino,USA,Secundaria completa,25.11,86.3
+16,174.2,Masculino,USA,Secundaria incompleta,26.63,80.8
+66,183.3,Masculino,USA,Secundaria completa,26.28,88.3
+11,154.7,Masculino,USA,Secundaria incompleta,19.51,46.7
+23,180.4,Masculino,USA,Secundaria completa,21.79,70.9
+50,178.2,Masculino,USA,Secundaria completa,28.06,89.1
+28,181,Masculino,Mexico,Terciario,28.42,93.1
+19,170.7,Masculino,USA,Terciario,22.55,65.7
+57,178.5,Masculino,USA,Secundaria completa,41.74,133
+32,173.9,Masculino,USA,Terciario,23.11,69.9
+67,174.8,Masculino,USA,Terciario,34,103.9
+68,171.6,Masculino,Mexico,Secundaria incompleta,31.14,91.7
+11,159,Masculino,USA,Secundaria incompleta,22.27,56.3
+15,172.7,Masculino,USA,Secundaria incompleta,22.87,68.2
+21,177.4,Masculino,USA,Terciario,22.59,71.1
+58,178.9,Masculino,USA,Secundaria incompleta,41.59,133.1
+13,174.3,Masculino,USA,Secundaria incompleta,23.93,72.7
+13,171.5,Masculino,USA,Secundaria incompleta,17.17,50.5
+46,186.9,Masculino,USA,Terciario,23.7,82.8
+16,173.1,Masculino,USA,Secundaria incompleta,36.74,110.1
+78,175.5,Masculino,USA,Secundaria incompleta,35.97,110.8
+20,178.1,Masculino,USA,Terciario,23.17,73.5
+6,110.2,Masculino,USA,Secundaria incompleta,15.32,18.6
\ No newline at end of file
diff --git a/onboarding/source/resources/nhanes.csv b/onboarding/source/resources/nhanes.csv
new file mode 100644
index 00000000..ad8a5419
--- /dev/null
+++ b/onboarding/source/resources/nhanes.csv
@@ -0,0 +1,101 @@
+Sex,Age,Height,BornIn,Education,BMI,Weight
+Male,1,,USA,,,10.8
+Male,35,178.9,USA,HS incl GED,28.12,90
+Female,34,151,Mexico,Less than HS,24.34,55.5
+Male,64,156.3,Mexico,Less than HS,36.72,89.7
+Female,5,107.8,USA,,16.01,18.6
+Female,68,173,USA,More than HS,36.52,109.3
+Female,0,,USA,,,6.5
+Female,2,87.9,USA,,15.92,12.3
+Male,15,179.3,USA,Less than HS,21.06,67.7
+Female,24,165.1,USA,More than HS,46.04,125.5
+Female,70,151.9,Mexico,Less than HS,28.82,66.5
+Male,85,174.5,USA,More than HS,19.44,59.2
+Female,30,172.4,USA,More than HS,26.51,78.8
+Male,66,177.9,USA,More than HS,26.13,82.7
+Male,37,191.7,USA,Less than HS,22.64,83.2
+Male,20,184.1,Elsewhere,Less than HS,20.18,68.4
+Male,31,197.6,USA,More than HS,22.97,89.7
+Male,13,169.1,USA,Less than HS,28.5,81.5
+Male,12,162.6,USA,Less than HS,20.5,54.2
+Female,59,147.1,USA,More than HS,38.73,83.8
+Female,2,89.8,USA,,16.62,13.4
+Female,76,157.5,USA,Less than HS,33.1,82.1
+Male,20,182,USA,More than HS,23.97,79.4
+Male,0,,USA,,,6.3
+Male,55,180.1,USA,HS incl GED,25.62,83.1
+Male,5,109.6,USA,,23.89,28.7
+Female,46,163,USA,HS incl GED,55.1,146.4
+Female,28,159.4,USA,More than HS,24.4,62
+Female,14,153.3,Mexico,Less than HS,27.23,64
+Female,11,155.2,USA,Less than HS,16.4,39.5
+Female,84,160.9,USA,Less than HS,26.46,68.5
+Female,1,,USA,,,10
+Male,0,,USA,,,8.8
+Male,77,182.9,USA,HS incl GED,27.38,91.6
+Female,72,155.5,USA,HS incl GED,24.73,59.8
+Female,50,161.2,USA,HS incl GED,28.09,73
+Female,8,139,USA,Less than HS,22.98,44.4
+Female,17,166,USA,Less than HS,28.67,79
+Male,51,185.4,USA,More than HS,25.11,86.3
+Female,21,163.2,USA,HS incl GED,28.53,76
+Male,16,174.2,USA,Less than HS,26.63,80.8
+Female,55,172,USA,More than HS,18.76,55.5
+Male,66,183.3,USA,More than HS,26.28,88.3
+Female,31,154.7,Mexico,Less than HS,29.33,70.2
+Male,11,154.7,USA,Less than HS,19.51,46.7
+Male,23,180.4,USA,More than HS,21.79,70.9
+Female,33,158.5,Mexico,Less than HS,26.15,65.7
+Female,3,98.7,USA,,19.2,18.7
+Female,68,157.7,Elsewhere,Less than HS,32.45,80.7
+Female,62,158.8,Mexico,Less than HS,25.38,64
+Female,55,167.8,USA,More than HS,33.7,94.9
+Female,10,141.5,USA,Less than HS,17.48,35
+Male,50,178.2,USA,More than HS,28.06,89.1
+Female,10,154.4,USA,Less than HS,18.54,44.2
+Female,11,159.8,USA,Less than HS,18.68,47.7
+Female,1,,USA,,,13.3
+Male,28,181,Mexico,HS incl GED,28.42,93.1
+Female,16,158.1,USA,Less than HS,25.52,63.8
+Female,12,148.2,USA,Less than HS,15.3,33.6
+Female,76,168.6,USA,More than HS,23.99,68.2
+Male,19,170.7,USA,HS incl GED,22.55,65.7
+Female,61,159.4,USA,HS incl GED,44.12,112.1
+Male,57,178.5,USA,More than HS,41.74,133
+Male,32,173.9,USA,HS incl GED,23.11,69.9
+Male,67,174.8,USA,HS incl GED,34,103.9
+Female,19,163.2,USA,HS incl GED,36.12,96.2
+Male,68,171.6,Mexico,Less than HS,31.14,91.7
+Female,17,164.4,USA,Less than HS,21.72,58.7
+Female,5,114.7,USA,,18.93,24.9
+Female,71,158.5,USA,Less than HS,32.28,81.1
+Male,11,159,USA,Less than HS,22.27,56.3
+Female,13,163.3,USA,Less than HS,22.91,61.1
+Female,60,164.1,USA,HS incl GED,32.64,87.9
+Male,15,172.7,USA,Less than HS,22.87,68.2
+Female,1,,USA,,,10.7
+Male,21,177.4,USA,HS incl GED,22.59,71.1
+Female,4,105.1,USA,,16.11,17.8
+Female,11,153.1,USA,Less than HS,23,53.9
+Female,1,,USA,,,9.4
+Male,58,178.9,USA,Less than HS,41.59,133.1
+Female,2,92.7,USA,,19.43,16.7
+Female,72,159.8,Mexico,Less than HS,31.72,81
+Female,30,162.6,USA,More than HS,51.14,135.2
+Female,10,147.2,USA,Less than HS,18.74,40.6
+Male,13,174.3,USA,Less than HS,23.93,72.7
+Male,13,171.5,USA,Less than HS,17.17,50.5
+Female,10,137,USA,Less than HS,17.16,32.2
+Female,1,,USA,,,10.4
+Male,46,186.9,USA,HS incl GED,23.7,82.8
+Female,76,169,USA,HS incl GED,33.89,96.8
+Female,26,176.1,USA,HS incl GED,24.48,75.9
+Male,16,173.1,USA,Less than HS,36.74,110.1
+Male,78,175.5,USA,Less than HS,35.97,110.8
+Female,46,158.2,Elsewhere,Less than HS,24.01,60.1
+Female,5,115.4,USA,,19.37,25.8
+Male,20,178.1,USA,HS incl GED,23.17,73.5
+Female,12,153.9,USA,Less than HS,19.38,45.9
+Female,5,123.5,USA,,14.56,22.2
+Male,6,110.2,USA,Less than HS,15.32,18.6
+Female,36,173.4,USA,More than HS,23.65,71.1
\ No newline at end of file
diff --git a/onboarding/source/strings.json b/onboarding/source/strings.json
index e0e008a1..c2603de7 100644
--- a/onboarding/source/strings.json
+++ b/onboarding/source/strings.json
@@ -3,17 +3,11 @@
"~header.welcome": "Welcome to CODAP",
"~list.title": "Figure out how to accomplish each of these basic CODAP tasks:",
"~show.me.text": "Show me.",
- "~onboarding1.csv.filename": "mammals.csv",
- "~onboarding1.drag.movie.filename": "DragCSV.mp4",
"~onboarding1.drag.task": "Drag this data file into CODAP",
"~onboarding1.graph.task": "Make a graph",
- "~onboarding1.graph.movie.filename": "MakeGraph.mp4",
"~onboarding1.move.table.task": "Move a table or graph",
- "~onboarding1.move.table.movie.filename": "MoveGraph.mp4",
"~onboarding1.drag.attribute.task": "Drag an attribute to a graph's axis",
- "~onboarding1.drag.attribute.movie.filename": "DragAttribute.mp4",
"~onboarding1.drag.second.attribute.task": "Drag a 2nd attribute to a graph's axis",
- "~onboarding1.drag.second.attribute.movie.filename": "Drag2ndAttribute.mp4",
"~onboarding1.drag.success.message1": "You've got data! It appears in a case table.",
"~onboarding1.drag.success.message2": "Each row in the table represents a case and each column represents an attribute.",
"~onboarding1.drag.success.message3": "This data set contains data about mammals. Each case represents a different mammal. The attributes provide information about lifespan, height, and so on.",
@@ -28,7 +22,6 @@
"~onboarding1.drag.second.attribute.success.message2": "Your graph is bivariate meaning you have displayed two attributes on a single graph.",
"~onboarding1.drag.second.attribute.success.message3": "You can replace either attribute with a different attribute, or drag an attribute to the middle of the graph to create a legend for the points.",
"~onboarding1.make.table.task": "Make a table showing Mammals data",
- "~onboarding1.make.table.movie.filename": "MakeTable.mp4",
"~onboarding1.make.table.success.message1": "You made a case table showing the pre-loaded data.",
"~onboarding1.make.table.success.message2": "Each row in the table represents a case, and each column represents an attribute.",
"~onboarding1.make.table.success.message3": "This data set contains data about mammals. Each case represents a different mammal. The attributes provide information about lifespan, height, and so on.",
@@ -53,24 +46,18 @@
"~onboarding1.make.graph.success.alt.message1": "Very nice graph!",
"~onboarding1.make.graph.success.alt.message2": "There are no points in it, because you haven't dragged any data in yet.",
"~onboarding2.make.scatterplot.task": "Make a scatterplot of height vs age.",
- "~onboarding2.make.scatterplot.movie.filename": "MakeScatterplot.mp4",
"~onboarding2.make.scatterplot.success.message": "Great scatterplot! Hopefully you have height on the vertical axis and age on the horizontal axis.",
"~onboarding2.select.cases.task": "Drag a selection rectangle around a subset of the points.",
- "~onboarding2.select.cases.movie.filename": "SelectCases.mp4",
"~onboarding2.select.cases.success.message1": "OK. Cases are selected.",
"~onboarding2.select.cases.success.message2": "These are the ones we want to look at more closely.",
"~onboarding2.hide.unselected.task": "Hide the unselected cases.",
- "~onboarding2.hide.unselected.movie.filename": "HideUnselected.mp4",
"~onboarding2.hide.unselected.success.message1": "That very nicely got those other cases out of the way.",
"~onboarding2.hide.unselected.success.message2": "It's possible that the graph needs rescaling.",
"~onboarding2.deselect.task": "Deselect all cases, including the ones in the table.",
- "~onboarding2.deselect.movie.filename": "Deselect.mp4",
"~onboarding2.deselect.success.message": "Selection is important, but so is deselection!",
"~onboarding2.rescale.task": "Rescale the graph.",
- "~onboarding2.rescale.movie.filename": "Rescale.mp4",
"~onboarding2.rescale.success.message": "You pressed the graph's Rescale button so that all the points are visible and spread out as much as possible.",
"~onboarding2.add.legend.task": "Add Sex as a legend to the scatterplot.",
- "~onboarding2.add.legend.movie.filename": "MakeLegend.mp4",
"~onboarding2.add.legend.success.message1": "Legendary! Notice that the points are nicely colored according to the scheme laid out in the legend.",
"~onboarding2.add.legend.success.message2": "Pro tip: You can select all the points in one category by clicking on the legend key.",
"~onboarding2.all.success.message1": "Congratulations! You've done the following:",
@@ -86,17 +73,11 @@
"~header.welcome": "Les damos la bienvenida a CODAP",
"~list.title": "Explorar cómo realizar cada una de estas tareas básicas de CODAP:",
"~show.me.text": "Ver cómo",
- "~onboarding1.csv.filename": "es/mammals.csv",
- "~onboarding1.drag.movie.filename": "es/DragCSV.mp4",
"~onboarding1.drag.task": "Arrastar este archivo en CODAP",
"~onboarding1.graph.task": "Crear un gráfico",
- "~onboarding1.graph.movie.filename": "es/MakeGraph.mp4",
"~onboarding1.move.table.task": "Mover una tabla o un gráfico",
- "~onboarding1.move.table.movie.filename": "es/MoveGraph.mp4",
"~onboarding1.drag.attribute.task": "Arrastrar un atributo a uno de los ejes del gráfico",
- "~onboarding1.drag.attribute.movie.filename": "es/DragAttribute.mp4",
"~onboarding1.drag.second.attribute.task": "Arrastrar un segundo atributo a un eje del gráfico",
- "~onboarding1.drag.second.attribute.movie.filename": "es/Drag2ndAttribute.mp4",
"~onboarding1.drag.success.message1": "¡Aparecieron datos! Aparece en una tabla de casos.",
"~onboarding1.drag.success.message2": "Cada fila de la tabla representa un caso y cada columna representa un atributo.",
"~onboarding1.drag.success.message3": "Este conjunto de datos contiene datos sobre mamíferos. Cada caso representa un mamífero diferente. Los atributos brindan información sobre la esperanza de vida, la altura, etc.",
@@ -111,7 +92,6 @@
"~onboarding1.drag.second.attribute.success.message2": "El gráfico es bivariante, lo que significa que muestra dos atributos en un solo gráfico.",
"~onboarding1.drag.second.attribute.success.message3": "Se puede reemplazar cualquiera de los atributos por un atributo diferente o arrastrar un atributo al medio del gráfico para crear una leyenda para los puntos.",
"~onboarding1.make.table.task": "Crear una tabla que muestre los datos de los mamíferos",
- "~onboarding1.make.table.movie.filename": "MakeTable.mp4",
"~onboarding1.make.table.success.message1": "Hiciste una tabla de casos que muestra los datos precargados.",
"~onboarding1.make.table.success.message2": "Cada fila de la tabla representa un caso y cada columna representa un atributo.",
"~onboarding1.make.table.success.message3": "Este conjunto de datos contiene datos sobre mamíferos. Cada caso representa un mamífero diferente. Los atributos brindan información sobre la esperanza de vida, la altura, etc.",
@@ -136,24 +116,18 @@
"~onboarding1.make.graph.success.alt.message1": "¡Muy buen gráfico!",
"~onboarding1.make.graph.success.alt.message2": "No hay puntos porque todavía no arrastramos ningún dato.",
"~onboarding2.make.scatterplot.task": "Crear un gráfico de dispersión de altura vs edad.",
- "~onboarding2.make.scatterplot.movie.filename": "es/MakeScatterplot.mp4",
"~onboarding2.make.scatterplot.success.message": "¡Buen gráfico! Seguramente tienen altura en el ejer vertical y edad en el eje horizontal.",
"~onboarding2.select.cases.task": "Arrastrar un rectángulo de selección alrededor de un subconjunto de los puntos.",
- "~onboarding2.select.cases.movie.filename": "es/SelectCases.mp4",
"~onboarding2.select.cases.success.message1": "OK. Se seleccionaron los casos.",
"~onboarding2.select.cases.success.message2": "Estos son los únicos que queremos mirar más de cerca.",
"~onboarding2.hide.unselected.task": "Ocultar los casos no seleccionados.",
- "~onboarding2.hide.unselected.movie.filename": "es/HideUnselected.mp4",
"~onboarding2.hide.unselected.success.message1": "Eso eliminó muy bien esos otros casos del camino.",
"~onboarding2.hide.unselected.success.message2": "Es posible que se necesite cambiar la escala.",
"~onboarding2.deselect.task": "Deseleccionar todos los casos incluyendo los de la tabla.",
- "~onboarding2.deselect.movie.filename": "es/Deselect.mp4",
"~onboarding2.deselect.success.message": "Seleccionar es importante, ¡pero también lo es deseleccionar!",
"~onboarding2.rescale.task": "Cambiar la escala del gráfico.",
- "~onboarding2.rescale.movie.filename": "es/Rescale.mp4",
"~onboarding2.rescale.success.message": "Presionaste el botón de cambio de escala del gráfico, o sea que todos los puntos son visibles y se distribuyen lo máximo posible.",
"~onboarding2.add.legend.task": "Agregar Sexo como leyenda al gráfico de dispersión.",
- "~onboarding2.add.legend.movie.filename": "es/MakeLegend.mp4",
"~onboarding2.add.legend.success.message1": "¡Maravilloso! Observar que los puntos están coloreados de acuerdo con el esquema presentado en la leyenda.",
"~onboarding2.add.legend.success.message2": "Pro tip: You can select all the points in one category by clicking on the legend key.",
"~onboarding2.all.success.message1": "¡Felicitaciones! Lograste lo siguiente:",
diff --git a/onboarding/target/index.html b/onboarding/target/index.html
index ad6bef64..24c8aed2 100644
--- a/onboarding/target/index.html
+++ b/onboarding/target/index.html
@@ -10,7 +10,7 @@
- If you can see this, React is not working right.
+ Loading ...
diff --git a/onboarding/target/localeManager.js b/onboarding/target/localeManager.js
index e7520fef..8b2923da 100644
--- a/onboarding/target/localeManager.js
+++ b/onboarding/target/localeManager.js
@@ -112,4 +112,8 @@ function tr(sID, args) {
let s = resolve(sID);
let ix = 0;
return s.replace(/%@[0-9]?/g, replacer);
+}
+
+function resourceDir(){
+ return (locale !== "en-us" ? locale + "/" : "");
}
\ No newline at end of file
diff --git a/onboarding/target/onboarding.js b/onboarding/target/onboarding.js
index c2cdc8ab..fc4e5952 100644
--- a/onboarding/target/onboarding.js
+++ b/onboarding/target/onboarding.js
@@ -95,7 +95,7 @@ class DraggableLink extends React.Component {
handleDragStart(event) {
let dt = event.dataTransfer,
- tUrl = window.location.href.replace(/\/[^\/]*$/, "") + "/resources/"+tr("~onboarding1.csv.filename");
+ tUrl = window.location.href.replace(/\/[^\/]*$/, "") + "/resources/" + resourceDir() + "mammals.csv";
let ix;
for (let i = 0; i < dt.items.length; i++) {
if (dt.items[i].kind === 'file') {
@@ -403,12 +403,13 @@ function getStarted() {
console.log(msg);
});
- if (!hasMouse) {
+ if ((!hasMouse && onboarding1) || (!onboarding1)) {
+ csvToLoad = (onboarding1 ? "mammals.csv" : "nhanes.csv");
codapInterface.sendRequest({
action: 'create',
resource: 'dataContextFromURL',
values: {
- URL: window.location.href.replace(/\/[^\/]*$/, "") + "/resources/"+tr("~onboarding1.csv.filename")
+ URL: window.location.href.replace(/\/[^\/]*$/, "") + "/resources/" + resourceDir() + csvToLoad
}
}).then(function (iResult) {
console.log('Created data context from URL');
diff --git a/onboarding/target/onboarding_2.html b/onboarding/target/onboarding_2.html
index 31ed1518..acb520a3 100644
--- a/onboarding/target/onboarding_2.html
+++ b/onboarding/target/onboarding_2.html
@@ -10,7 +10,7 @@
- If you can see this, React is not working right.
+ Loading ...
diff --git a/onboarding/target/resources/es/Deselect.mp4 b/onboarding/target/resources/es/Deselect.mp4
index a68ad9d3..68c9c04f 100644
Binary files a/onboarding/target/resources/es/Deselect.mp4 and b/onboarding/target/resources/es/Deselect.mp4 differ
diff --git a/onboarding/target/resources/es/Drag2ndAttribute.mp4 b/onboarding/target/resources/es/Drag2ndAttribute.mp4
index 3a2a3950..11dfb802 100644
Binary files a/onboarding/target/resources/es/Drag2ndAttribute.mp4 and b/onboarding/target/resources/es/Drag2ndAttribute.mp4 differ
diff --git a/onboarding/target/resources/es/DragAttribute.mp4 b/onboarding/target/resources/es/DragAttribute.mp4
index 75a73855..99ace1c9 100644
Binary files a/onboarding/target/resources/es/DragAttribute.mp4 and b/onboarding/target/resources/es/DragAttribute.mp4 differ
diff --git a/onboarding/target/resources/es/HideUnselected.mp4 b/onboarding/target/resources/es/HideUnselected.mp4
index 93ae4bca..aaaf7e39 100644
Binary files a/onboarding/target/resources/es/HideUnselected.mp4 and b/onboarding/target/resources/es/HideUnselected.mp4 differ
diff --git a/onboarding/target/resources/es/MakeLegend.mp4 b/onboarding/target/resources/es/MakeLegend.mp4
index 5de8c31c..151987a0 100644
Binary files a/onboarding/target/resources/es/MakeLegend.mp4 and b/onboarding/target/resources/es/MakeLegend.mp4 differ
diff --git a/onboarding/target/resources/es/MakeScatterplot.mp4 b/onboarding/target/resources/es/MakeScatterplot.mp4
index 005f97a5..4d794fb9 100644
Binary files a/onboarding/target/resources/es/MakeScatterplot.mp4 and b/onboarding/target/resources/es/MakeScatterplot.mp4 differ
diff --git a/onboarding/target/resources/es/MakeTable.mp4 b/onboarding/target/resources/es/MakeTable.mp4
new file mode 100644
index 00000000..f060f803
Binary files /dev/null and b/onboarding/target/resources/es/MakeTable.mp4 differ
diff --git a/onboarding/target/resources/es/Rescale.mp4 b/onboarding/target/resources/es/Rescale.mp4
index e49ce9fc..380670a6 100644
Binary files a/onboarding/target/resources/es/Rescale.mp4 and b/onboarding/target/resources/es/Rescale.mp4 differ
diff --git a/onboarding/target/resources/es/SelectCases.mp4 b/onboarding/target/resources/es/SelectCases.mp4
index b3c5d6db..98e1ddb0 100644
Binary files a/onboarding/target/resources/es/SelectCases.mp4 and b/onboarding/target/resources/es/SelectCases.mp4 differ
diff --git a/onboarding/target/resources/es/mammals.csv b/onboarding/target/resources/es/mammals.csv
index 6d6cd9a2..f097da4e 100644
--- a/onboarding/target/resources/es/mammals.csv
+++ b/onboarding/target/resources/es/mammals.csv
@@ -1,28 +1,28 @@
-Mammal,Order,LifeSpan,Height,Mass,Sleep,Speed,Habitat,Diet
-African Elephant,Proboscidae,70,4,6400,3,40,land,plants
-Asian Elephant,Proboscidae,70,3,5000,4,40,land,plants
-Big Brown Bat,Chiroptera,19,0.1,0.02,20,40,land,meat
-Bottlenose Dolphin,Cetacea,25,3.5,635,5,37,water,meat
-Cheetah,Carnivora,14,1.5,50,12,110,land,meat
-Chimpanzee,Primate,40,1.5,68,10,,land,both
-Domestic Cat,Carnivora,16,0.8,4.5,12,50,land,meat
-Donkey,Perissodactyla,40,1.2,187,3,50,land,plants
-Giraffe,Artiodactyla,25,5,1100,2,50,land,plants
-Gray Wolf,Carnivora,16,1.6,80,13,64,land,meat
-Grey Seal,Pinnipedia,30,2.1,275,6,19,both,meat
-Ground Squirrel,Rodentia,9,0.3,0.1,15,19,land,both
-Horse,Perissodactyla,25,1.5,521,3,69,land,plants
-House Mouse,Rodentia,3,0.1,0.03,12,13,land,both
-Human,Primate,80,1.9,80,8,45,land,both
-Jaguar,Carnivora,20,1.8,115,11,60,land,meat
-Killer Whale,Cetacea,50,6.5,4000,,48,water,meat
-Lion,Carnivora,15,2.5,250,20,80,land,meat
-N. American Opossum,Didelphimorphia,5,0.5,5,19,,land,both
-Nine-Banded Armadillo,Xenarthra,10,0.6,7,17,1,land,meat
-Owl Monkey,Primate,12,0.4,1,17,,land,both
-Patas Monkey,Primate,20,0.9,13,,55,land,both
-Pig,Artiodactyla,10,1,192,8,18,land,both
-Pronghorn Antelope,Artiodactyla,10,0.9,70,,98,land,plants
-Rabbit,Lagomorpha,5,0.5,3,11,56,land,plants
-Red Fox,Carnivora,7,0.8,5,10,48,land,both
-Spotted Hyena,Carnivora,25,0.9,70,18,64,land,meat
\ No newline at end of file
+Mamífero,Orden,El Duración de Vida,Altura,masa,Sueño,Velocidad,Habitat,Dieta
+Elefante africano,Proboscidae,70,4,6400,3,40,tierra,plantas
+Elefante asiático,Proboscidae,70,3,5000,4,40,tierra,plantas
+Gran murciélago marrón,Chiroptera,19,0.1,0.02,20,40,tierra,carne
+Delfín nariz de botella,Cetacea,25,3.5,635,5,37,agua,carne
+Cheetah,Carnivora,14,1.5,50,12,110,tierra,carne
+Chimpancé,Primate,40,1.5,68,10,,tierra,ambas
+Gato doméstico,Carnivora,16,0.8,4.5,12,50,tierra,carne
+Burro,Perissodactyla,40,1.2,187,3,50,tierra,plantas
+Jirafa,Artiodactyla,25,5,1100,2,50,tierra,plantas
+Lobo gris,Carnivora,16,1.6,80,13,64,tierra,carne
+Elefante marino,Pinnipedia,30,2.1,275,6,19,ambos,carne
+Ardilla,Rodentia,9,0.3,0.1,15,19,tierra,ambas
+Caballo,Perissodactyla,25,1.5,521,3,69,tierra,plantas
+Ratón,Rodentia,3,0.1,0.03,12,13,tierra,ambas
+Humano,Primate,80,1.9,80,8,45,tierra,ambas
+Jaguar,Carnivora,20,1.8,115,11,60,tierra,carne
+Ballena asesina,Cetacea,50,6.5,4000,,48,agua,carne
+León,Carnivora,15,2.5,250,20,80,tierra,carne
+Zarigüeya,Didephimorphia,5,0.5,5,19,,tierra,ambas
+Armadillo,Xenarthra,10,0.6,7,17,1,tierra,carne
+Lechuza,Primate,12,0.4,1,17,,tierra,ambas
+Mono,Primate,20,0.9,13,,55,tierra,ambas
+Cerdo,Artiodactyla,10,1,192,8,18,tierra,ambas
+Antílope,Artiodactyla,10,0.9,70,,98,tierra,plantas
+Conejo,Lagomorpha,5,0.5,3,11,56,tierra,plantas
+Zorro rojo,Carnivora,7,0.8,5,10,48,tierra,ambas
+Hiena,Carnivora,25,0.9,70,18,64,tierra,carne
\ No newline at end of file
diff --git a/onboarding/target/resources/es/nhanes.csv b/onboarding/target/resources/es/nhanes.csv
new file mode 100644
index 00000000..a671be3a
--- /dev/null
+++ b/onboarding/target/resources/es/nhanes.csv
@@ -0,0 +1,101 @@
+Edad,Altura,Sexo,Nacido en,Educación,IMC,Peso
+34,151,Femenino,Mexico,Secundaria incompleta,24.34,55.5
+5,107.8,Femenino,USA,,16.01,18.6
+68,173,Femenino,USA,Secundaria completa,36.52,109.3
+0,,Femenino,USA,,,6.5
+2,87.9,Femenino,USA,,15.92,12.3
+24,165.1,Femenino,USA,Secundaria completa,46.04,125.5
+70,151.9,Femenino,Mexico,Secundaria incompleta,28.82,66.5
+30,172.4,Femenino,USA,Secundaria completa,26.51,78.8
+59,147.1,Femenino,USA,Secundaria completa,38.73,83.8
+2,89.8,Femenino,USA,,16.62,13.4
+76,157.5,Femenino,USA,Secundaria incompleta,33.1,82.1
+46,163,Femenino,USA,Terciario,55.1,146.4
+28,159.4,Femenino,USA,Secundaria completa,24.4,62
+14,153.3,Femenino,Mexico,Secundaria incompleta,27.23,64
+11,155.2,Femenino,USA,Secundaria incompleta,16.4,39.5
+84,160.9,Femenino,USA,Secundaria incompleta,26.46,68.5
+1,,Femenino,USA,,,10
+72,155.5,Femenino,USA,Terciario,24.73,59.8
+50,161.2,Femenino,USA,Terciario,28.09,73
+8,139,Femenino,USA,Secundaria incompleta,22.98,44.4
+17,166,Femenino,USA,Secundaria incompleta,28.67,79
+21,163.2,Femenino,USA,Terciario,28.53,76
+55,172,Femenino,USA,Secundaria completa,18.76,55.5
+31,154.7,Femenino,Mexico,Secundaria incompleta,29.33,70.2
+33,158.5,Femenino,Mexico,Secundaria incompleta,26.15,65.7
+3,98.7,Femenino,USA,,19.2,18.7
+68,157.7,Femenino,Otro lugar,Secundaria incompleta,32.45,80.7
+62,158.8,Femenino,Mexico,Secundaria incompleta,25.38,64
+55,167.8,Femenino,USA,Secundaria completa,33.7,94.9
+10,141.5,Femenino,USA,Secundaria incompleta,17.48,35
+10,154.4,Femenino,USA,Secundaria incompleta,18.54,44.2
+11,159.8,Femenino,USA,Secundaria incompleta,18.68,47.7
+1,,Femenino,USA,,,13.3
+16,158.1,Femenino,USA,Secundaria incompleta,25.52,63.8
+12,148.2,Femenino,USA,Secundaria incompleta,15.3,33.6
+76,168.6,Femenino,USA,Secundaria completa,23.99,68.2
+61,159.4,Femenino,USA,Terciario,44.12,112.1
+19,163.2,Femenino,USA,Terciario,36.12,96.2
+17,164.4,Femenino,USA,Secundaria incompleta,21.72,58.7
+5,114.7,Femenino,USA,,18.93,24.9
+71,158.5,Femenino,USA,Secundaria incompleta,32.28,81.1
+13,163.3,Femenino,USA,Secundaria incompleta,22.91,61.1
+60,164.1,Femenino,USA,Terciario,32.64,87.9
+1,,Femenino,USA,,,10.7
+4,105.1,Femenino,USA,,16.11,17.8
+11,153.1,Femenino,USA,Secundaria incompleta,23,53.9
+1,,Femenino,USA,,,9.4
+2,92.7,Femenino,USA,,19.43,16.7
+72,159.8,Femenino,Mexico,Secundaria incompleta,31.72,81
+30,162.6,Femenino,USA,Secundaria completa,51.14,135.2
+10,147.2,Femenino,USA,Secundaria incompleta,18.74,40.6
+10,137,Femenino,USA,Secundaria incompleta,17.16,32.2
+1,,Femenino,USA,,,10.4
+76,169,Femenino,USA,Terciario,33.89,96.8
+26,176.1,Femenino,USA,Terciario,24.48,75.9
+46,158.2,Femenino,Otro lugar,Secundaria incompleta,24.01,60.1
+5,115.4,Femenino,USA,,19.37,25.8
+12,153.9,Femenino,USA,Secundaria incompleta,19.38,45.9
+5,123.5,Femenino,USA,,14.56,22.2
+36,173.4,Femenino,USA,Secundaria completa,23.65,71.1
+1,,Masculino,USA,,,10.8
+35,178.9,Masculino,USA,Terciario,28.12,90
+64,156.3,Masculino,Mexico,Secundaria incompleta,36.72,89.7
+15,179.3,Masculino,USA,Secundaria incompleta,21.06,67.7
+85,174.5,Masculino,USA,Secundaria completa,19.44,59.2
+66,177.9,Masculino,USA,Secundaria completa,26.13,82.7
+37,191.7,Masculino,USA,Secundaria incompleta,22.64,83.2
+20,184.1,Masculino,Otro lugar,Secundaria incompleta,20.18,68.4
+31,197.6,Masculino,USA,Secundaria completa,22.97,89.7
+13,169.1,Masculino,USA,Secundaria incompleta,28.5,81.5
+12,162.6,Masculino,USA,Secundaria incompleta,20.5,54.2
+20,182,Masculino,USA,Secundaria completa,23.97,79.4
+0,,Masculino,USA,,,6.3
+55,180.1,Masculino,USA,Terciario,25.62,83.1
+5,109.6,Masculino,USA,,23.89,28.7
+0,,Masculino,USA,,,8.8
+77,182.9,Masculino,USA,Terciario,27.38,91.6
+51,185.4,Masculino,USA,Secundaria completa,25.11,86.3
+16,174.2,Masculino,USA,Secundaria incompleta,26.63,80.8
+66,183.3,Masculino,USA,Secundaria completa,26.28,88.3
+11,154.7,Masculino,USA,Secundaria incompleta,19.51,46.7
+23,180.4,Masculino,USA,Secundaria completa,21.79,70.9
+50,178.2,Masculino,USA,Secundaria completa,28.06,89.1
+28,181,Masculino,Mexico,Terciario,28.42,93.1
+19,170.7,Masculino,USA,Terciario,22.55,65.7
+57,178.5,Masculino,USA,Secundaria completa,41.74,133
+32,173.9,Masculino,USA,Terciario,23.11,69.9
+67,174.8,Masculino,USA,Terciario,34,103.9
+68,171.6,Masculino,Mexico,Secundaria incompleta,31.14,91.7
+11,159,Masculino,USA,Secundaria incompleta,22.27,56.3
+15,172.7,Masculino,USA,Secundaria incompleta,22.87,68.2
+21,177.4,Masculino,USA,Terciario,22.59,71.1
+58,178.9,Masculino,USA,Secundaria incompleta,41.59,133.1
+13,174.3,Masculino,USA,Secundaria incompleta,23.93,72.7
+13,171.5,Masculino,USA,Secundaria incompleta,17.17,50.5
+46,186.9,Masculino,USA,Terciario,23.7,82.8
+16,173.1,Masculino,USA,Secundaria incompleta,36.74,110.1
+78,175.5,Masculino,USA,Secundaria incompleta,35.97,110.8
+20,178.1,Masculino,USA,Terciario,23.17,73.5
+6,110.2,Masculino,USA,Secundaria incompleta,15.32,18.6
\ No newline at end of file
diff --git a/onboarding/target/resources/nhanes.csv b/onboarding/target/resources/nhanes.csv
new file mode 100644
index 00000000..ad8a5419
--- /dev/null
+++ b/onboarding/target/resources/nhanes.csv
@@ -0,0 +1,101 @@
+Sex,Age,Height,BornIn,Education,BMI,Weight
+Male,1,,USA,,,10.8
+Male,35,178.9,USA,HS incl GED,28.12,90
+Female,34,151,Mexico,Less than HS,24.34,55.5
+Male,64,156.3,Mexico,Less than HS,36.72,89.7
+Female,5,107.8,USA,,16.01,18.6
+Female,68,173,USA,More than HS,36.52,109.3
+Female,0,,USA,,,6.5
+Female,2,87.9,USA,,15.92,12.3
+Male,15,179.3,USA,Less than HS,21.06,67.7
+Female,24,165.1,USA,More than HS,46.04,125.5
+Female,70,151.9,Mexico,Less than HS,28.82,66.5
+Male,85,174.5,USA,More than HS,19.44,59.2
+Female,30,172.4,USA,More than HS,26.51,78.8
+Male,66,177.9,USA,More than HS,26.13,82.7
+Male,37,191.7,USA,Less than HS,22.64,83.2
+Male,20,184.1,Elsewhere,Less than HS,20.18,68.4
+Male,31,197.6,USA,More than HS,22.97,89.7
+Male,13,169.1,USA,Less than HS,28.5,81.5
+Male,12,162.6,USA,Less than HS,20.5,54.2
+Female,59,147.1,USA,More than HS,38.73,83.8
+Female,2,89.8,USA,,16.62,13.4
+Female,76,157.5,USA,Less than HS,33.1,82.1
+Male,20,182,USA,More than HS,23.97,79.4
+Male,0,,USA,,,6.3
+Male,55,180.1,USA,HS incl GED,25.62,83.1
+Male,5,109.6,USA,,23.89,28.7
+Female,46,163,USA,HS incl GED,55.1,146.4
+Female,28,159.4,USA,More than HS,24.4,62
+Female,14,153.3,Mexico,Less than HS,27.23,64
+Female,11,155.2,USA,Less than HS,16.4,39.5
+Female,84,160.9,USA,Less than HS,26.46,68.5
+Female,1,,USA,,,10
+Male,0,,USA,,,8.8
+Male,77,182.9,USA,HS incl GED,27.38,91.6
+Female,72,155.5,USA,HS incl GED,24.73,59.8
+Female,50,161.2,USA,HS incl GED,28.09,73
+Female,8,139,USA,Less than HS,22.98,44.4
+Female,17,166,USA,Less than HS,28.67,79
+Male,51,185.4,USA,More than HS,25.11,86.3
+Female,21,163.2,USA,HS incl GED,28.53,76
+Male,16,174.2,USA,Less than HS,26.63,80.8
+Female,55,172,USA,More than HS,18.76,55.5
+Male,66,183.3,USA,More than HS,26.28,88.3
+Female,31,154.7,Mexico,Less than HS,29.33,70.2
+Male,11,154.7,USA,Less than HS,19.51,46.7
+Male,23,180.4,USA,More than HS,21.79,70.9
+Female,33,158.5,Mexico,Less than HS,26.15,65.7
+Female,3,98.7,USA,,19.2,18.7
+Female,68,157.7,Elsewhere,Less than HS,32.45,80.7
+Female,62,158.8,Mexico,Less than HS,25.38,64
+Female,55,167.8,USA,More than HS,33.7,94.9
+Female,10,141.5,USA,Less than HS,17.48,35
+Male,50,178.2,USA,More than HS,28.06,89.1
+Female,10,154.4,USA,Less than HS,18.54,44.2
+Female,11,159.8,USA,Less than HS,18.68,47.7
+Female,1,,USA,,,13.3
+Male,28,181,Mexico,HS incl GED,28.42,93.1
+Female,16,158.1,USA,Less than HS,25.52,63.8
+Female,12,148.2,USA,Less than HS,15.3,33.6
+Female,76,168.6,USA,More than HS,23.99,68.2
+Male,19,170.7,USA,HS incl GED,22.55,65.7
+Female,61,159.4,USA,HS incl GED,44.12,112.1
+Male,57,178.5,USA,More than HS,41.74,133
+Male,32,173.9,USA,HS incl GED,23.11,69.9
+Male,67,174.8,USA,HS incl GED,34,103.9
+Female,19,163.2,USA,HS incl GED,36.12,96.2
+Male,68,171.6,Mexico,Less than HS,31.14,91.7
+Female,17,164.4,USA,Less than HS,21.72,58.7
+Female,5,114.7,USA,,18.93,24.9
+Female,71,158.5,USA,Less than HS,32.28,81.1
+Male,11,159,USA,Less than HS,22.27,56.3
+Female,13,163.3,USA,Less than HS,22.91,61.1
+Female,60,164.1,USA,HS incl GED,32.64,87.9
+Male,15,172.7,USA,Less than HS,22.87,68.2
+Female,1,,USA,,,10.7
+Male,21,177.4,USA,HS incl GED,22.59,71.1
+Female,4,105.1,USA,,16.11,17.8
+Female,11,153.1,USA,Less than HS,23,53.9
+Female,1,,USA,,,9.4
+Male,58,178.9,USA,Less than HS,41.59,133.1
+Female,2,92.7,USA,,19.43,16.7
+Female,72,159.8,Mexico,Less than HS,31.72,81
+Female,30,162.6,USA,More than HS,51.14,135.2
+Female,10,147.2,USA,Less than HS,18.74,40.6
+Male,13,174.3,USA,Less than HS,23.93,72.7
+Male,13,171.5,USA,Less than HS,17.17,50.5
+Female,10,137,USA,Less than HS,17.16,32.2
+Female,1,,USA,,,10.4
+Male,46,186.9,USA,HS incl GED,23.7,82.8
+Female,76,169,USA,HS incl GED,33.89,96.8
+Female,26,176.1,USA,HS incl GED,24.48,75.9
+Male,16,173.1,USA,Less than HS,36.74,110.1
+Male,78,175.5,USA,Less than HS,35.97,110.8
+Female,46,158.2,Elsewhere,Less than HS,24.01,60.1
+Female,5,115.4,USA,,19.37,25.8
+Male,20,178.1,USA,HS incl GED,23.17,73.5
+Female,12,153.9,USA,Less than HS,19.38,45.9
+Female,5,123.5,USA,,14.56,22.2
+Male,6,110.2,USA,Less than HS,15.32,18.6
+Female,36,173.4,USA,More than HS,23.65,71.1
\ No newline at end of file
diff --git a/onboarding/target/strings.json b/onboarding/target/strings.json
index e0e008a1..c2603de7 100644
--- a/onboarding/target/strings.json
+++ b/onboarding/target/strings.json
@@ -3,17 +3,11 @@
"~header.welcome": "Welcome to CODAP",
"~list.title": "Figure out how to accomplish each of these basic CODAP tasks:",
"~show.me.text": "Show me.",
- "~onboarding1.csv.filename": "mammals.csv",
- "~onboarding1.drag.movie.filename": "DragCSV.mp4",
"~onboarding1.drag.task": "Drag this data file into CODAP",
"~onboarding1.graph.task": "Make a graph",
- "~onboarding1.graph.movie.filename": "MakeGraph.mp4",
"~onboarding1.move.table.task": "Move a table or graph",
- "~onboarding1.move.table.movie.filename": "MoveGraph.mp4",
"~onboarding1.drag.attribute.task": "Drag an attribute to a graph's axis",
- "~onboarding1.drag.attribute.movie.filename": "DragAttribute.mp4",
"~onboarding1.drag.second.attribute.task": "Drag a 2nd attribute to a graph's axis",
- "~onboarding1.drag.second.attribute.movie.filename": "Drag2ndAttribute.mp4",
"~onboarding1.drag.success.message1": "You've got data! It appears in a case table.",
"~onboarding1.drag.success.message2": "Each row in the table represents a case and each column represents an attribute.",
"~onboarding1.drag.success.message3": "This data set contains data about mammals. Each case represents a different mammal. The attributes provide information about lifespan, height, and so on.",
@@ -28,7 +22,6 @@
"~onboarding1.drag.second.attribute.success.message2": "Your graph is bivariate meaning you have displayed two attributes on a single graph.",
"~onboarding1.drag.second.attribute.success.message3": "You can replace either attribute with a different attribute, or drag an attribute to the middle of the graph to create a legend for the points.",
"~onboarding1.make.table.task": "Make a table showing Mammals data",
- "~onboarding1.make.table.movie.filename": "MakeTable.mp4",
"~onboarding1.make.table.success.message1": "You made a case table showing the pre-loaded data.",
"~onboarding1.make.table.success.message2": "Each row in the table represents a case, and each column represents an attribute.",
"~onboarding1.make.table.success.message3": "This data set contains data about mammals. Each case represents a different mammal. The attributes provide information about lifespan, height, and so on.",
@@ -53,24 +46,18 @@
"~onboarding1.make.graph.success.alt.message1": "Very nice graph!",
"~onboarding1.make.graph.success.alt.message2": "There are no points in it, because you haven't dragged any data in yet.",
"~onboarding2.make.scatterplot.task": "Make a scatterplot of height vs age.",
- "~onboarding2.make.scatterplot.movie.filename": "MakeScatterplot.mp4",
"~onboarding2.make.scatterplot.success.message": "Great scatterplot! Hopefully you have height on the vertical axis and age on the horizontal axis.",
"~onboarding2.select.cases.task": "Drag a selection rectangle around a subset of the points.",
- "~onboarding2.select.cases.movie.filename": "SelectCases.mp4",
"~onboarding2.select.cases.success.message1": "OK. Cases are selected.",
"~onboarding2.select.cases.success.message2": "These are the ones we want to look at more closely.",
"~onboarding2.hide.unselected.task": "Hide the unselected cases.",
- "~onboarding2.hide.unselected.movie.filename": "HideUnselected.mp4",
"~onboarding2.hide.unselected.success.message1": "That very nicely got those other cases out of the way.",
"~onboarding2.hide.unselected.success.message2": "It's possible that the graph needs rescaling.",
"~onboarding2.deselect.task": "Deselect all cases, including the ones in the table.",
- "~onboarding2.deselect.movie.filename": "Deselect.mp4",
"~onboarding2.deselect.success.message": "Selection is important, but so is deselection!",
"~onboarding2.rescale.task": "Rescale the graph.",
- "~onboarding2.rescale.movie.filename": "Rescale.mp4",
"~onboarding2.rescale.success.message": "You pressed the graph's Rescale button so that all the points are visible and spread out as much as possible.",
"~onboarding2.add.legend.task": "Add Sex as a legend to the scatterplot.",
- "~onboarding2.add.legend.movie.filename": "MakeLegend.mp4",
"~onboarding2.add.legend.success.message1": "Legendary! Notice that the points are nicely colored according to the scheme laid out in the legend.",
"~onboarding2.add.legend.success.message2": "Pro tip: You can select all the points in one category by clicking on the legend key.",
"~onboarding2.all.success.message1": "Congratulations! You've done the following:",
@@ -86,17 +73,11 @@
"~header.welcome": "Les damos la bienvenida a CODAP",
"~list.title": "Explorar cómo realizar cada una de estas tareas básicas de CODAP:",
"~show.me.text": "Ver cómo",
- "~onboarding1.csv.filename": "es/mammals.csv",
- "~onboarding1.drag.movie.filename": "es/DragCSV.mp4",
"~onboarding1.drag.task": "Arrastar este archivo en CODAP",
"~onboarding1.graph.task": "Crear un gráfico",
- "~onboarding1.graph.movie.filename": "es/MakeGraph.mp4",
"~onboarding1.move.table.task": "Mover una tabla o un gráfico",
- "~onboarding1.move.table.movie.filename": "es/MoveGraph.mp4",
"~onboarding1.drag.attribute.task": "Arrastrar un atributo a uno de los ejes del gráfico",
- "~onboarding1.drag.attribute.movie.filename": "es/DragAttribute.mp4",
"~onboarding1.drag.second.attribute.task": "Arrastrar un segundo atributo a un eje del gráfico",
- "~onboarding1.drag.second.attribute.movie.filename": "es/Drag2ndAttribute.mp4",
"~onboarding1.drag.success.message1": "¡Aparecieron datos! Aparece en una tabla de casos.",
"~onboarding1.drag.success.message2": "Cada fila de la tabla representa un caso y cada columna representa un atributo.",
"~onboarding1.drag.success.message3": "Este conjunto de datos contiene datos sobre mamíferos. Cada caso representa un mamífero diferente. Los atributos brindan información sobre la esperanza de vida, la altura, etc.",
@@ -111,7 +92,6 @@
"~onboarding1.drag.second.attribute.success.message2": "El gráfico es bivariante, lo que significa que muestra dos atributos en un solo gráfico.",
"~onboarding1.drag.second.attribute.success.message3": "Se puede reemplazar cualquiera de los atributos por un atributo diferente o arrastrar un atributo al medio del gráfico para crear una leyenda para los puntos.",
"~onboarding1.make.table.task": "Crear una tabla que muestre los datos de los mamíferos",
- "~onboarding1.make.table.movie.filename": "MakeTable.mp4",
"~onboarding1.make.table.success.message1": "Hiciste una tabla de casos que muestra los datos precargados.",
"~onboarding1.make.table.success.message2": "Cada fila de la tabla representa un caso y cada columna representa un atributo.",
"~onboarding1.make.table.success.message3": "Este conjunto de datos contiene datos sobre mamíferos. Cada caso representa un mamífero diferente. Los atributos brindan información sobre la esperanza de vida, la altura, etc.",
@@ -136,24 +116,18 @@
"~onboarding1.make.graph.success.alt.message1": "¡Muy buen gráfico!",
"~onboarding1.make.graph.success.alt.message2": "No hay puntos porque todavía no arrastramos ningún dato.",
"~onboarding2.make.scatterplot.task": "Crear un gráfico de dispersión de altura vs edad.",
- "~onboarding2.make.scatterplot.movie.filename": "es/MakeScatterplot.mp4",
"~onboarding2.make.scatterplot.success.message": "¡Buen gráfico! Seguramente tienen altura en el ejer vertical y edad en el eje horizontal.",
"~onboarding2.select.cases.task": "Arrastrar un rectángulo de selección alrededor de un subconjunto de los puntos.",
- "~onboarding2.select.cases.movie.filename": "es/SelectCases.mp4",
"~onboarding2.select.cases.success.message1": "OK. Se seleccionaron los casos.",
"~onboarding2.select.cases.success.message2": "Estos son los únicos que queremos mirar más de cerca.",
"~onboarding2.hide.unselected.task": "Ocultar los casos no seleccionados.",
- "~onboarding2.hide.unselected.movie.filename": "es/HideUnselected.mp4",
"~onboarding2.hide.unselected.success.message1": "Eso eliminó muy bien esos otros casos del camino.",
"~onboarding2.hide.unselected.success.message2": "Es posible que se necesite cambiar la escala.",
"~onboarding2.deselect.task": "Deseleccionar todos los casos incluyendo los de la tabla.",
- "~onboarding2.deselect.movie.filename": "es/Deselect.mp4",
"~onboarding2.deselect.success.message": "Seleccionar es importante, ¡pero también lo es deseleccionar!",
"~onboarding2.rescale.task": "Cambiar la escala del gráfico.",
- "~onboarding2.rescale.movie.filename": "es/Rescale.mp4",
"~onboarding2.rescale.success.message": "Presionaste el botón de cambio de escala del gráfico, o sea que todos los puntos son visibles y se distribuyen lo máximo posible.",
"~onboarding2.add.legend.task": "Agregar Sexo como leyenda al gráfico de dispersión.",
- "~onboarding2.add.legend.movie.filename": "es/MakeLegend.mp4",
"~onboarding2.add.legend.success.message1": "¡Maravilloso! Observar que los puntos están coloreados de acuerdo con el esquema presentado en la leyenda.",
"~onboarding2.add.legend.success.message2": "Pro tip: You can select all the points in one category by clicking on the legend key.",
"~onboarding2.all.success.message1": "¡Felicitaciones! Lograste lo siguiente:",
diff --git a/onboarding/target/task_descriptions.js b/onboarding/target/task_descriptions.js
index a2314b40..a1d606f8 100644
--- a/onboarding/target/task_descriptions.js
+++ b/onboarding/target/task_descriptions.js
@@ -1,8 +1,9 @@
hasMouse = !('ontouchstart' in window);
+onboarding1 = true;
taskDescriptions = {
descriptions: [hasMouse ? {
- key: 'Drag', label: tr("~onboarding1.drag.task"), url: './resources/'+tr("~onboarding1.drag.movie.filename"),
+ key: 'Drag', label: tr("~onboarding1.drag.task"), url: './resources/' + resourceDir() + "DragCSV.mp4",
operation: 'dataContextCountChanged',
feedback: React.createElement(
'div',
@@ -24,7 +25,7 @@ taskDescriptions = {
)
)
} : {
- key: 'MakeTable', label: tr("~onboarding1.make.table.task"), url: './resources/' + tr("~onboarding1.make.table.movie.filename"),
+ key: 'MakeTable', label: tr("~onboarding1.make.table.task"), url: './resources/' + resourceDir() + "MakeTable.mp4",
feedback: React.createElement(
'div',
null,
@@ -45,7 +46,7 @@ taskDescriptions = {
)
)
}, {
- key: 'MakeGraph', label: tr("~onboarding1.graph.task"), url: './resources/' + tr("~onboarding1.graph.movie.filename"),
+ key: 'MakeGraph', label: tr("~onboarding1.graph.task"), url: './resources/' + resourceDir() + "MakeGraph.mp4",
feedback: React.createElement(
'div',
null,
@@ -75,7 +76,7 @@ taskDescriptions = {
)
)
}, {
- key: 'MoveComponent', label: tr("~onboarding1.move.table.task"), url: './resources/' + tr("~onboarding1.move.table.movie.filename"),
+ key: 'MoveComponent', label: tr("~onboarding1.move.table.task"), url: './resources/' + resourceDir() + "MoveGraph.mp4",
operation: 'move', type: ['DG.GraphView', 'DG.TableView'],
feedback: React.createElement(
'div',
@@ -103,7 +104,7 @@ taskDescriptions = {
*/
{
- key: 'AssignAttribute', label: tr("~onboarding1.drag.attribute.task"), url: './resources/' + tr("~onboarding1.drag.attribute.movie.filename"),
+ key: 'AssignAttribute', label: tr("~onboarding1.drag.attribute.task"), url: './resources/' + resourceDir() + "DragAttribute.mp4",
feedback: React.createElement(
'div',
null,
@@ -124,7 +125,7 @@ taskDescriptions = {
)
)
}, {
- key: 'SecondAttribute', label: tr("~onboarding1.drag.second.attribute.task"), url: './resources/' + tr("~onboarding1.drag.second.attribute.movie.filename"),
+ key: 'SecondAttribute', label: tr("~onboarding1.drag.second.attribute.task"), url: './resources/' + resourceDir() + "Drag2ndAttribute.mp4",
feedback: React.createElement(
'div',
null,
diff --git a/onboarding/target/task_descriptions_2.js b/onboarding/target/task_descriptions_2.js
index 4d467201..4b1eaacb 100644
--- a/onboarding/target/task_descriptions_2.js
+++ b/onboarding/target/task_descriptions_2.js
@@ -1,9 +1,10 @@
-hasMouse = true; // This is a kludge to prevent loading of Mammals on touch devices
+hasMouse = !('ontouchstart' in window);
+onboarding1 = false;
taskDescriptions = {
descriptions: [{
key: 'MakeScatterplot', label: tr("~onboarding2.make.scatterplot.task"),
- url: './resources/' + tr("~onboarding2.make.scatterplot.movie.filename"),
+ url: './resources/' + resourceDir() + "MakeScatterplot.mp4",
operation: 'attributeChange', type: '',
requiresSpecialHandling: true,
feedback: React.createElement(
@@ -17,7 +18,7 @@ taskDescriptions = {
)
}, {
key: 'SelectCases', label: tr("~onboarding2.select.cases.task"),
- url: './resources/' + tr("~onboarding2.select.cases.movie.filename"),
+ url: './resources/' + resourceDir() + "SelectCases.mp4",
operation: 'selectCases',
constraints: [{ property: 'cases', value: true }],
prereq: 'MakeScatterplot',
@@ -37,7 +38,7 @@ taskDescriptions = {
)
}, {
key: 'HideUnselected', label: tr("~onboarding2.hide.unselected.task"),
- url: './resources/' + tr("~onboarding2.hide.unselected.movie.filename"),
+ url: './resources/' + resourceDir() + "HideUnselected.mp4",
operation: 'hideUnselected', type: '',
prereq: 'SelectCases',
feedback: React.createElement(
@@ -56,7 +57,7 @@ taskDescriptions = {
)
}, {
key: 'Deselect', label: tr("~onboarding2.deselect.task"),
- url: './resources/' + tr("~onboarding2.deselect.movie.filename"),
+ url: './resources/' + resourceDir() + "Deselect.mp4",
operation: 'selectCases',
constraints: [{ property: 'cases', value: false }],
prereq: 'HideUnselected',
@@ -71,7 +72,7 @@ taskDescriptions = {
)
}, {
key: 'Rescale', label: tr("~onboarding2.rescale.task"),
- url: './resources/' + tr("~onboarding2.rescale.movie.filename"),
+ url: './resources/' + resourceDir() + "Rescale.mp4",
operation: 'rescaleGraph', type: '',
prereq: 'HideUnselected',
feedback: React.createElement(
@@ -85,7 +86,7 @@ taskDescriptions = {
)
}, {
key: 'MakeLegend', label: tr("~onboarding2.add.legend.task"),
- url: './resources/' + tr("~onboarding2.add.legend.movie.filename"),
+ url: './resources/' + resourceDir() + "MakeLegend.mp4",
operation: 'legendAttributeChange', type: 'DG.GraphModel',
constraints: [{ property: 'attributeName', value: tr("~legend.attribute") }],
prereq: 'MakeScatterplot',
diff --git a/published-plugins.json b/published-plugins.json
index bd20f958..4a9c5d25 100644
--- a/published-plugins.json
+++ b/published-plugins.json
@@ -31,6 +31,21 @@
"Tools"
]
},
+ {
+ "title": "Testimate",
+ "description": "Test hypotheses using classical inferential methods.",
+ "title-string": "DG.plugin.Testimate.name",
+ "description-string": "DG.plugin.Testimate.description",
+ "width": 400,
+ "height": 600,
+ "path": "/eepsmedia/plugins/testimate/index.html",
+ "icon": "/eepsmedia/plugins/testimate/images/chisq.svg",
+ "visible": "true",
+ "isStandard": "true",
+ "categories": [
+ "Tools"
+ ]
+ },
{
"title": "Draw Tool",
"description": "Annotate an image.",
@@ -72,7 +87,7 @@
"width": 380,
"height": 490,
"path": "/noaa-codap-plugin/",
- "icon": "/NOAA-weather/assets/images/icon-noaa-weather.svg",
+ "icon": "/noaa-codap-plugin/assets/images/icon-noaa-weather.svg",
"visible": "true",
"isStandard": "true",
"categories": [
@@ -142,4 +157,4 @@
"Tools"
]
}
-]
\ No newline at end of file
+]
diff --git a/src/data_interactive_map.json b/src/data_interactive_map.json
index 6304c0ae..cff2bdf8 100644
--- a/src/data_interactive_map.json
+++ b/src/data_interactive_map.json
@@ -76,7 +76,22 @@
"visible": "true",
"isStandard": "true",
"categories": [
- "Tools"
+ "Tools", "Partners"
+ ]
+ },
+ {
+ "title": "Testimate",
+ "description": "Test hypotheses using classical inferential methods.",
+ "title-string": "DG.plugin.Testimate.name",
+ "description-string": "DG.plugin.Testimate.description",
+ "width": 400,
+ "height": 600,
+ "path": "/eepsmedia/plugins/testimate/index.html",
+ "icon": "/eepsmedia/plugins/testimate/images/xxx.svg",
+ "visible": "true",
+ "isStandard": "true",
+ "categories": [
+ "Tools", "Partners"
]
},
{
@@ -802,7 +817,7 @@
"width": 380,
"height": 490,
"path": "/noaa-codap-plugin/",
- "icon": "/NOAA-weather/assets/images/icon-noaa-weather.svg",
+ "icon": "/noaa-codap-plugin/assets/images/icon-noaa-weather.svg",
"visible": "true",
"isStandard": "true",
"categories": [