//Yan add 1 functions function commentsubmit() { // process the responses var comment2 = ($$$("comment2").value == 'You may leave this area blank.') ? "" : $$$("comment2").value; // AJAX request $("#comment2").val("Thank you!").attr("disabled", true); $("#comment_submit").css("visibility", "hidden"); $.ajax({ type: 'POST', url: 'includes/comments1.php', data: { comment2: comment2, participant_id: globals.participant_id } }); } /*==========================================FixedSet DEFINITION STARTS HERE ================================================*/ /*============================ function FixedSet becomes a test object which implements testInterface.txt ================= */ function FixedSet(test_index) { // test_index is the position of the FixedSet test in globals.tests /* configuration for the rest of the test, these are constants which can be changed at will */ var config = { // the symbols that will be shown or tested //symbols : "23456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(''), // .split turns the string into an array // removing I and O // symbols : "23456789ABCDEFGHJKLMNPQRSTUVWXYZ".split(''), // just digits: symbols : "0123456789".split(''), // keyboard input codes for one and zero (top-row numbers and numpad) true_codes : [49, 97], false_codes : [48, 96], // maximum and minimum number of stimulus symbols in each stim_set min_stims : 1, max_stims : 6, /* Currently total probes per trial is posProbesPerTrial + negProbesPerTrial. For debugging purposes, it's often useful to make this 1. */ posProbesPerTrial : 3, negProbesPerTrial : 8, // durations for different parts of the test stimDuration : 3000, itiDuration : 500, waitToFirstDuration : 1000, waitToNextDuration : 0, feedbackDuration : 300, nextTrialWarningDuration : 1500, // number of sets before telling the user to take a break setsBeforeBreak : 3, /* for processing outliers, what multiple of the iqr away from the median should be accepted as valid data */ iqrMultiple : 2, }; /*==========================================FixedSet INTERFACE STARTS HERE ============================================= =================== Everything in the testInterface.txt is implemented under FixedSet INTERFACE ========================*/ var fixedSet = this; // for reference in functions because "this" keyword has different scope than variables this.test_name = "fixedSet"; // call this whatever you want... this.test_id = null; // id of the test in mysql database this.index = test_index; // position in globals.tests /* a variable to hold expment results and trials etc., */ /**** Commenting out the variant that uses both mouse and keyboard var expment = { // phases of the test, first two are mouse based, last two are keyboard based phases : new Array ({ type : "practice", instructions : "

Instructions for the Memory Test

This first part is only for practice. Your results will be discarded.

You will see a slide of anywhere between 1 - 5 symbols flash on your screen. Memorize the set of symbols (you need not remember their order).

set

Then, you will see a series of images like the one shown below.


probe

Decide whether or not the symbol was part of the previous set and click on either the [✓] or the [x] to indicate Yes or No.

Remember, your goal is to answer correctly as quickly as possible. You should sit in a quiet place, free of distractions.

In the first part of this test, you will have to respond with your mouse. After each probe, you will have to recenter it by clicking on the arrow button:

mouse_center

Click the arrow on the bottom right when you're ready to begin the practice.

", numTrials : 1, allowedInputs : ["mouse"], }, { type : "trial", instructions : "



Great Job! :)


Now for the real deal. It'll be the same as the practice you just took.

When you're ready, press the arrow to begin the test.

By the way, we know that the buttons are very far apart. We do this to track some information about your reaction time, and you'll be able to see all of these results in the end. Thanks for your patience and cooperation!

", numTrials : 5, // this can be exchanged as desired. Should probably be greater than config.max_stims - config.min_stims; allowedInputs : ["mouse"], }, { type : "practice", instructions: "



Awesome! Well done, relax a little.


In the next part of this test, you'll be given the same task. But this time, you will have to respond with your keyboard instead of your mouse.

To do this, press 1 to indicate [✓] and 0 to indicate [X].

To begin with the practice, press the arrow button or your space bar.

", numTrials: 1, allowedInputs : ["keyboard"], }, { type: "trial", instructions : "



Good!

Now for the part that counts. Remember:

try to respond as fast as possible.

You will be scored on your response time and accuracy. Good luck!

", numTrials: 5, allowedInputs : ["keyboard"], } ), trials : [], // an array to hold all of the trials, will be populated by setup results : [], // array for the results of the trials tmp_results : {}, // temporary responses for single probes }; **********/ var expment = { // phases of the test, first two are mouse based, last two are keyboard based phases : new Array ( { type : "practice", instructions: "

Instructions for the Memory Speed Test

1. First, you will see a screen (like the one in the image below) showing anywhere between 1 and 5 symbols. Memorize these symbols (you need not remember their order).

An example of a screen showing a set of symbols to memorize.

2. Then, you will see a series of screens, each showing one symbol at a time and asking you whether that symbol was present in the set you have just memorized.

Decide whether or not the symbol was part of the original set and press 1 for yes and 0 for no.

Remember, your goal is to answer correctly as quickly as possible. You should sit in a quiet place, free of distractions.

Click the arrow below or press space bar when you're ready to start the practice task (this first practice task will not count toward your final results).

", numTrials: 1, allowedInputs : ["keyboard"], }, { type: "trial", instructions : "



Good!

Now for the part that counts. Remember:

your goal is to respond as quickly and as accurately as possible.

You will be scored on your response time and accuracy. Good luck!

", numTrials: 12, allowedInputs : ["keyboard"], } ), trials : [], // an array to hold all of the trials, will be populated by setup results : [], // array for the results of the trials tmp_results : {}, // temporary responses for single probes }; this.expment = expment; // make expment accessible outside of FixedSet and implements test.expment in testInterface.txt /* other variables needed to proceed with the test wrapped in vars to avoid taking up FixedSet namespace */ var vars = { ch : null, // will hold nextTrial chain phase : 0, // current phase of test 0-indexed current_probe_id : 0, // for use in submitting results to server individually current_trial_id : 0, // for use in submitting results to server individually (see this.sendDataIndividually) mouse_input : true, // is the mouse allowed? keyboard_input : true, // is the keyboard allowed? (this will be switched out depdning on expment.phases // total_num_* is used to control calculations etc., total_num_symbols : config.symbols.length, total_num_phases : expment.phases.length, trial_num : 0, // current trial that the user is on out of the TOTAL, starts at 0 }; /* calculate the total number of probes that the user will take */ vars.total_num_trials = 0; for(n in expment.phases) { vars.total_num_trials += expment.phases[n].numTrials; } this.vars = vars; // make vars accessible outside of FixedSet and implements test.vars in testInterface.txt /* implements test.setup in testInterface.txt */ this.setup = function() { var phases = expment.phases; // alias to shorten code for(n in phases) { expment.trials[n] = [{type : "inst", html : phases[n].instructions}]; var sizes = []; for(var i = 0; i < phases[n].numTrials; i++) { // each phase has a certain number of trials. Iterate through all of those trials /* determine the set size of the current trial */ do { // if there are more trials than the number of possible sizes, reset sizes if (sizes.length > config.max_stims - config.min_stims) { sizes = []; } var total_in_stim = getRandomInt(config.min_stims, config.max_stims); } while (arrayContains(sizes, total_in_stim)) // don't repeat set sizes unless all set sizes have been produced once sizes.push(total_in_stim); /* create the set and all the probes for it */ var stim_set = getRandomSet(total_in_stim); var probes = []; var probesNeeded = { pos : config.posProbesPerTrial, neg : config.negProbesPerTrial } var lastProbe = ""; // this will store the last probe that was chosen from getNextProbe // getNextProbe is a helper function that gets the next probe while ensuring it's different than the lastProbe function getNextProbe(isPos, last) { if((total_in_stim < 2) && isPos) return getProbe(isPos, stim_set); // if set_size is 1 and the response is positive, then don't worry about repeats else { // otherwise, make sure there are not repeats do {var probe = getProbe(isPos, stim_set);} while(probe == last) return probe; } } /* generate all the probes */ for(var j = 0; j < config.posProbesPerTrial + config.negProbesPerTrial; j++) { /* isPos is true when we need more positive probes AND (either we don't need negative probes OR 50% of the time) */ var isPos = (probesNeeded.pos > 0) && ((probesNeeded.neg < 1) || (getRandomInt(0, 1) == 1)); // played with some boolean logic here :) var probe = getNextProbe(isPos, lastProbe); probes.push({symbol: probe, response_required: isPos}); // update the probes that we still need if(isPos) probesNeeded.pos--; else probesNeeded.neg--; lastProbe = probe; // update the last probe } // put this probe onto the end of the experiment expment.trials[n].push({ of_test : fixedSet.test_name, type : phases[n].type, trial_num : i, phase : n, allowed_input : phases[n].allowedInputs, stim_set : stim_set, stim_time : config.stimDuration, wait_time : config.waitToFirstDuration, total_in_stim : total_in_stim, probes : probes, }); } // END for(var i = 0; i < .... } // END for(n in phases) }; // END this.setup() /* implements test.preload_arr in testInterface.txt */ this.preload_arr = [], // no images need to be preloaded for this test /* implements test.preload_helper in testInterface.txt */ this.preload_helper = function () { // load in the html for the different trials for(phase in expment.trials) { for(trial in expment.trials[phase]) { var current_trial = expment.trials[phase][trial]; if (current_trial.type != "inst") { populateStim(current_trial); populateProbes(current_trial); } } } /* this gets the world_stats which will be used later to compare our stats to */ $.ajax({ url: "includes/world_stats.php", type: "GET", data: {}, success : function(r){ try { globals.world_stats = $.parseJSON(r); if(!globals.world_stats) { // if there was a failure, throw an error throw "Notice, world results are seeded, Ajax failure"; } } catch (e) { //console.error(e); // catch any error and just populate world_stats with some seed results globals.world_stats = { "total":520, "correct":514, "accuracy":98.846153846154, "avg_rt":909.4513618677, "pos_total":255, "neg_total":259, "p_rt":223741, "n_rt":243717, "avg_p_rt":877.41568627451, "avg_n_rt":940.99227799228, "avg_mt":41.252918287938, "set_size_rt":[89334,89980,93323,97160,97661], "set_size_total":[104,102,103,104,101], "set_size_avg":[858.98076923077,882.1568627451,906.04854368932,934.23076923077,966.94059405941], "date":"1374689121" } } }, error : function() { // if php failed, get a cached version of the data from the server $.ajax({ url: "includes/world_stats.txt", type: "GET", data: {}, success : function(r){ try { globals.world_stats = $.parseJSON(r); if(!globals.world_stats) { // if there was a failure, throw an error throw "Notice, world results are seeded, Ajax failure"; } } catch (e) { showFailPage(); } }, error : function() { showFailPage(); } }); }, }); // END $.ajax({... }, // END this.preload_helper ... /* implements test.nextTrialChain in testInterface.txt */ this.nextTrialChain = function (trial) { motorLogger.stopMouseLog(); motorLogger.startMouseLog({ trial_type : trial.type, trial_num : trial.trial_num }); expment.tmp_results = trial; // will be pushed onto results upon completion of the trial return new Array( // has to return this array which is used in the chain function by zen.js function () { vars.trial_num++; hideNextButton(); showSlide("empty"); $("#stims").children().hide(); $("#" + trial.of_test + trial.type + trial.trial_num + trial.phase).show(); $("#prompt").show(); // display the part of the test that we're currently on var partHTML = "

"; if(trial.type == "practice") { partHTML += "Practice"; } else if(globals.tests.length > 1) { partHTML += "Memory Speed Test: Part " + test_index + " out of " + globals.tests.length; } else { partHTML += "Memory Speed Test"; } partHTML += "

"; $$$("heading").innerHTML = partHTML; if (vars.total_num_trials > 1) { $$$("progress").innerHTML = "" + "Progress: " + (vars.trial_num) + " out of " + vars.total_num_trials; } /* determine which inputs are allowed */ vars.mouse_input = (expment.phases[vars.phase].allowedInputs.indexOf("mouse") > -1); vars.keyboard_input = (expment.phases[vars.phase].allowedInputs.indexOf("keyboard") > -1); }, config.itiDuration, function () { showSlide("nextTrialWarning"); }, config.nextTrialWarningDuration, function () { showSlide("stims"); }, config.stimDuration, function() { showSlide("empty"); }, config.waitToFirstDuration, function (){ displayProbe(trial, 0); } ); }, // END this.nextTrialChain ... /* implements test.submit in testInterface.txt */ this.submit = function() { sendData(); // sends all the expment results to the server /* fieldDict is a dictionary that is used to display the user's statistics on the results page, For each field there is : [Description, Unit, Comparison], the Comparison should be -1 if smaller is desirable, 1 if larger is desirable, e.g. response time is better when smaller so it has "-1" as Comparison*/ var fieldDict = { avg_rt : ["Average Response Time (smaller is better)", "milliseconds", "-1"], // avg_p_rt : ["Positive Response Time", "milliseconds", "-1"], // avg_n_rt : ["Negative Response Time", "milliseconds", "-1"], // total : ["Total Probes", "probes", "0"], // correct : ["Correct", "probes", "0"], accuracy : ["Accuracy (bigger is better)", "%", "1"], // avg_mt : ["Reaction Time", "milliseconds", "-1"], }; var stats = get_stats(); // both stats and world_stats should have each field in fieldDict var world_stats = get_worldStats(); var html = "

Your results

"; // generate the table for everything in the fieldDict for(var key in fieldDict) { if(fieldDict.hasOwnProperty(key)) { html += ""; // display the Description html += tdsWithWinners(stats[key], world_stats[key], key); // display the two statistics with the winner highlighted html += ""; // display the unit } } /* given number a and number b and key in fieldDict, return the correctly html formatted ";} function tdOfLoser(text) {return "";} /* for_range is passed in as the for_range parameter in the svg functions, look below at function SVG() for more information */ var for_range = [stats.set_size_avg, world_stats.set_size_avg]; // this is the SVG graph html += "
DescriptionYouWorldUnit
" + fieldDict[key][0] + ": " + fieldDict[key][1] + "
rows */ function tdsWithWinners(a, b, key) { var compared = compareStats(a, b, key); if(compared > 0) return tdOfWinner(a) + tdOfLoser(b); else if(compared < 0) return tdOfLoser(a) + tdOfWinner(b); else return tdOfLoser(a) + tdOfLoser(b); } function compareStats(a, b, key) { return (a - b) * Number(fieldDict[key][2]); // (a - b) determines which is bigger and then multiplies by Comparison in fieldDict } function tdOfWinner(text) {return "" + Math.floor(Number(text)) + "" + Math.floor(Number(text)) + "

" + 'But, wait! There\'s more!' + "

" + "
" + "" + "" + "" + "" + "" + "" + generateCircles(stats.set_size_avg, for_range, "#1111BB") + generateCircles(world_stats.set_size_avg, for_range, "black") + "" + generateAxes(stats.set_size_avg, for_range) + "Set Size (Number of items memorized)Response time (milliseconds)
"; // this is the description following the svg table html+= "

Above are your average response times per set size in blue and the world's average response times per set size in black. If you hover over any dot on the graph, you will see the exact average response time for that set size.

Don't worry if your results look unusual. Many factors can impact individual results: the quality of your keyboard, lighting, fatigue, your monitor, etc.

But do take a look at the average (World) results. This experiment is designed to test a hypothesis formulated by Robert Sternberg, an American psychologist. In the 1960’s, Sternberg argued that in order to access any part of our short-term memory, we do a complete scan of every part of our short-term memory. If this is true, then the lines you see above should be sloping upwards: as we try to keep more information in memory at any given time, it should take us longer to decide if a new symbol is part of the set we have memorized or not. Individual results may be quite noisy, but if all goes well, the average results over all participants should show a clear trend. In prior experiments, it had been shown that we need approximately 35 extra milliseconds to respond for each additional item we try to memorize.

"; //comments html += "

" + "Are you satisfied with the feedback you received and with the way we show your results? We are always open for suggestions, so please let us know what you think!" + "

" + "" + ""; fixedSet.results_html = html; // put the html into results_html } /* sends all of the current results to the server */ var sendData = function() { var tmp_results = {}; var number_of_trials = vars.total_num_trials; /* add the info to tmp_results and send to the server */ tmp_results.test = { test_name : fixedSet.test_name, number_of_trials : number_of_trials, comments : "", stim_set : config.symbols.slice(0).toString() } tmp_results.trials = []; for(n in expment.results) { tmp_results.trials[n] = { participant_id : globals.participant_id, trial_type : expment.results[n].type, trial_num : expment.results[n].trial_num, stims : expment.results[n].stim_set.slice(0).toString(), stim_size : expment.results[n].stim_set.length, stim_time : config.stimDuration, num_probes : config.posProbesPerTrial + config.negProbesPerTrial, time_before_first_probe : config.waitToFirstDuration, time_between_probes : config.waitToNextDuration, } tmp_results.trials[n].probes = []; for(s in expment.results[n].probes) { var probe = expment.results[n].probes[s]; tmp_results.trials[n].probes[s] = { probe_num : probe.probe_num, response_time : probe.rt, move_time : probe.mt, response : (probe.input_response ? 1 : 0), correct : (probe.input_response == probe.response_required ? 1 : 0), response_type : probe.input_type, probe : probe.symbol, } } } sendAjax({database : "all", data : tmp_results}); // send all the information to the server }; this.sendData = sendData; // make accessible outside of FixedSet and implements test.sendData in testInterface.txt // will hold the html for the results, called later, implements test.results_html in testInterface.txt this.results_html = null, /* ================================================= FixedSet INTERFACE ENDS HERE ============================================== */ /* ============================ The rest of the file includes helper functions for each part of the interface ================== */ /* ================================================= PRELOAD HELPERS STARTS HERE =============================================== */ // determines whether a positive or negative response should be required function isPosResponse() { return (getRandomInt(0,1) == 1); // right now it's 50% of the time } // gets a random set from config.symbols of length "num" function getRandomSet(num) { var return_set = []; do { var index = getRandomInt(0, (vars.total_num_symbols - 1)); // get a random index from the symbols if (!arrayContains(return_set, config.symbols[index])) { return_set.push(config.symbols[index]); // if the return_set doesn't already have this symbol, then add it in! } } while(return_set.length < num) // keep repeating until the total number required has been gotten return return_set; } // gets a probe to test the individual. if response_required, then will return something in the set // otherwise will return something outside of the set function getProbe(pos_response_required, set) { if (pos_response_required) { var randomIndex = getRandomInt(0, (set.length - 1)); return set[randomIndex]; } else { do { var randomIndex = getRandomInt(0, (vars.total_num_symbols - 1)); var symbol = config.symbols[randomIndex]; } while (arrayContains(set, symbol)); return symbol; } } // the following two populate functions fill the divs with the necessary // stimulus and probes function populateStim(current_trial) { var stim_html = "
"; for(i in current_trial.stim_set){stim_html += current_trial.stim_set[i] + " ";} stim_html += "
"; $("#stims").prepend(stim_html); } function populateProbes(current_trial) { for(n in current_trial.probes) { var probe_html = "
"; probe_html += current_trial.probes[n].symbol; probe_html += "
"; $("#probes").prepend(probe_html); } } /* ================================================= PRELOAD HELPERS ENDS HERE ================================================== */ /* ================================================= NEXT TRIAL CHAINS HELPERS STARTS HERE ====================================== */ // displays the next probe function displayProbe(trial, probeNumber) { motorLogger.setData({ probe_num : probeNumber }); $("#probes").children().hide(); $("#feedback").hide(); $("#response").show(); $("#" + trial.of_test + trial.type + trial.trial_num + trial.phase + "response" + probeNumber).show(); // display probe showSlide("probes"); // initialData recorded var initialData = { rt_start : new Date(), trial : trial, probeNumber : probeNumber } receiveResponses(initialData); // done with intial display, now wait for a response from the user } // receives responses (mouse and config.true_codes and config.false_codes function receiveResponses(initialData) { if(vars.mouse_input) { $("#yes_response").on("click", function() {record_response(true, "mouse")}); // binds the yes response $("#no_response").on("click", function () {record_response(false, "mouse")}); // binds the no response } if(vars.keyboard_input) { $(document).on("keydown", keyboard_handler); // binds for yes or no response from keyboard } $(document).on("mousemove", move_handler); // binds for reaction time var input = {}; // will record all information from the responses // record the first mouse movement function move_handler(e) { input.mt = (new Date()) - initialData.rt_start; $(document).off("mousemove", move_handler); } // must name the keyboard handler, so it can be unbound later function keyboard_handler(e) { // the response is positive or negative if the code is in the config.true_codes or false_codes var is_pos = arrayContains(config.true_codes, e.keyCode); var is_neg = arrayContains(config.false_codes, e.keyCode); if (is_pos || is_neg) { // functionally ignores other inputs input.mt = (new Date()) - initialData.rt_start; // reaction time, overrides any information from the mouse record_response(is_pos, "keyboard"); } } // function used to record responses, called by handlers later function record_response(isPosResponse, responseType) { input.rt = (new Date()) - initialData.rt_start; input.response = isPosResponse; input.type = responseType; // turn off all the listeners $(document).off("keydown", keyboard_handler); $("#yes_response").off("click"); $("#no_response").off("click"); processSingleProbe(input, initialData.trial, initialData.probeNumber); // process the data } } // processes single responses function processSingleProbe(input, trial, probeNumber) { // make sure nextTrialChain has stopped if(vars.ch) { clearChain(vars.ch); } $("#response").hide(); // hide the response // give feedback to the user var feedback_html = (trial.probes[probeNumber].response_required == input.response ? "

Correct!

" : "

Incorrect!

") $("#feedback").html(feedback_html); if (trial.type == "practice") $("#feedback").show(); // record the results expment.tmp_results.probes[probeNumber].rt = input.rt; expment.tmp_results.probes[probeNumber].mt = input.mt; expment.tmp_results.probes[probeNumber].input_response = input.response; expment.tmp_results.probes[probeNumber].input_type = input.type; expment.tmp_results.probes[probeNumber].probe_num = probeNumber; // go to the next trial goToNext(trial, probeNumber, input.type == "mouse"); } // goes to the next probe or trial function goToNext(trial, probeNumber, needsRecenter) { // show feedback, if the mouse needs to be recentered, show the recentering div setTimeout(function() { $("#feedback").hide(); $("#feedback").empty(); $("#" + trial.of_test + trial.type + trial.trial_num + trial.phase + "response" + probeNumber).hide(); if (needsRecenter) { withMouseCenter(next); } else { setTimeout(next, config.itiDuration); } }, config.feedbackDuration); function withMouseCenter(fun) { $("#mouse_center").show(); $("#mouse_center").children("img").on('click', function() { $("#mouse_center").hide(); $("#mouse_center").children("img").off("click"); fun(); }); } // function which will move to the next probe or trial function next() { // if there are more probes in the trial, show them if(probeNumber < (config.posProbesPerTrial + config.negProbesPerTrial) - 1) { showSlide("empty"); setTimeout(function() {displayProbe(trial, probeNumber + 1)}, config.waitToNextDuration); } else { // otherwise go to the nextTrial if(vars.ch) { clearChain(vars.ch); } var tmp = trial; tmp.probes = expment.tmp_results.probes.slice(0); expment.results.push(tmp); expment.tmp_results = {}; if(trial.type != "practice" && ((trial.trial_num % config.setsBeforeBreak) == (config.setsBeforeBreak - 1))) { motorLogger.sendMotorLog("includes/motor_logger.php"); $("#mouse_center").children("div").html("

You've completed a few sets! Take a breather :). When you're ready to move on to the next trial, press the arrow beneath this message.

"); function keyboard_handler(e) { if(e.keyCode == 32) {// space bar is 32 key code $("#mouse_center").children("img").click(); } } $(document).on("keypress", keyboard_handler); withMouseCenter(function() { $(document).off("keypress", keyboard_handler); $("#mouse_center").children("div").empty(); nextTrial(fixedSet); }); } else { nextTrial(fixedSet); } } } // END function next } // END function goToNext /* ================================================= NEXT TRIAL CHAINS HELPERS ENDS HERE ====================================== */ /* ================================================= AJAX HELPERS START HERE ================================================== */ vars.tried_ajax = 0; vars.current_ajax_requests = 0; // a function used to send ajax calls to the server for the test function sendAjax(info) { vars.current_ajax_requests++; $.ajax({ type: "POST", url : "includes/data.php", data: info }).done(function(r) { // the response should be an id for each of the // parts of the test if(!/^[0-9]+$/.test(r)) { //alert(r); return; } switch (info.database) { case "tests" : test_id = r; break; case "trials" : vars.current_trial_id = r; break; case "probes" : vars.current_probe_id = r; break; case "all" : // do nothing; break; } vars.current_ajax_requests--; }); } /* the functionw withAjaxSync and sendDataIndividually aren't used right now... // used to make sure that the other ajax responses have completed before firing func() function withAjaxSync(func) { if(vars.current_ajax_requests > 0) { vars.tried_ajax += 1; console.log("Waiting for ajax..."); if(vars.tried_ajax < 250) { setTimeout(function() {withAjaxSync(func)}, 250); } else { showFailPage(); } } else { vars.tried_ajax = 0; func(); } } // sends all the probes but individually and not as a package (can be useful if test is unloaded before finishing) function sendDataIndividually() { var number_of_trials = 0; for(n in expment.phases) { number_of_trials += expment.phases[n].numTrials; } sendAjax({database : "tests", data : { test_name : fixedSet.test_name, number_of_trials : number_of_trials, comments : "", stim_set : config.symbols.slice(0).toString() }}); var send_next = function(n) { if(n == expment.results.length) { return; } else { withAjaxSync(function() { sendAjax({database : "trials", data: { test_id : fixedSet.test_id, participant_id : globals.participant_id, trial_type : expment.results[n].type, trial_num : expment.results[n].trial_num, stims : expment.results[n].stim_set.slice(0).toString(), stim_size : expment.results[n].stim_set.length, stim_time : config.stimDuration, num_probes : config.posProbesPerTrial + config.negProbesPerTrial, time_before_first_probe : config.waitToFirstDuration, time_between_probes : config.waitToNextDuration, }}); withAjaxSync(function() { for(s in expment.results[n].probes) { var probe = expment.results[n].probes[s]; sendAjax({database : "probes", data : { trial_id : vars.current_trial_id, probe_num : probe.probe_num, response_time : probe.rt, move_time : probe.mt, response : (probe.input_response ? 1 : 0), correct : (probe.input_response == probe.response_required ? 1 : 0), response_type : probe.input_type, probe : probe.symbol, }}); } send_next(n + 1); }); }); } } send_next(0); } */ /* ======================================================= AJAX HELPERS END HERE ======================================= */ /* ======================================================= SUBMIT HELPERS START HERE =================================== */ // used for the results page function get_stats() { /* initial declarations of items needed in fieldDict */ var probes = 0; var correct = 0; var pos_correct = 0; var neg_correct = 0; var rt = 0; // total response time var p_rt = 0; // total positive response time var n_rt = 0; // total negative response time var mt = 0; // total move time (reaction time) var set_size_total = []; // total probes of each set size var set_size_time = []; // total time of each set size var set_size_avg = []; // average time of each set size var set_size_mt = []; // total reaction time of each set size var set_size_mavg = []; // average reaction time of each set size var rts = []; // reaction times by set_size, to calculate outliers /* this initializes the set_size arrays to 0 for each set size that existed in the trial */ for(var i = config.min_stims - 1; i < config.max_stims; i++) { set_size_total[i] = 0; set_size_time[i] = 0; set_size_avg[i] = 0; set_size_mt[i] = 0; set_size_mavg[i] = 0; rts[i] = []; } /* processing the outliers (currently taking out any response times that are (config.lqrMultiple * LQR) away from the median per set size) will return an array of upper and lower bounds e.g. [{upper: 432, lower:222}, {upper: 456, lower:243}], the index of each range in the return should be the set size minus 1 */ function processOutliers() { var ranges = []; for(n in expment.results) { if(!(expment.results[n].type == "practice") && expment.results[n].allowed_input.indexOf("keyboard") > -1) { // only take keyboard input and non-practice trials var trial = expment.results[n]; // alias for coding for(s in trial.probes) { var probe = trial.probes[s]; // alias for coding if(probe.input_response == probe.response_required) { // only record correct responses rts[trial.total_in_stim - 1].push(probe.rt); } } } } for(n in rts) { ranges[n] = getRange(rts[n]); // getRange calculates the (config.lqrMultiple * LQR) range from the median } return ranges; } /* given an array of response times, getRange returns an upper bound and a lower bound (inclusive) for the range of plausible response times */ function getRange(rt) { if(rt.length < 4) { // if there are less than 4 legitimate times, none should be outliers return { lower : Math.min.apply(Math, rt), upper : Math.max.apply(Math, rt), } } rt = rt.sort(function(a,b) {return a - b}); // sort assending var med = median(rt); var upperQ = median(rt.filter(function(a) {return med < a})); // upperQ is the upper Quartile var lowerQ = median(rt.filter(function(a) {return a < med})); // lowerQ is the lower Quartile var iqr = upperQ - lowerQ; // inner quartile range return { lower : med - config.iqrMultiple * iqr, // lower bound upper : med + config.iqrMultiple * iqr // upper bound } } /* median returns the median of an array of values, if the array is unsorted, pass in the 2nd argument as false */ function median(values, unsorted) { // http://caseyjustus.com/finding-the-median-of-an-array-with-javascript if(unsorted) values.sort( function(a,b) {return a - b;} ); var half = Math.floor(values.length/2); if(values.length % 2) return values[half]; else return (values[half-1] + values[half]) / 2.0; } var ranges = processOutliers(); // ranges now has the upper and lower bounds for each set size /* count up all the results! */ for(n in expment.results) { if(!(expment.results[n].type == "practice") && expment.results[n].allowed_input.indexOf("keyboard") > -1 ) { // ignore the result if it was a practice trial or a mouse trial var trial = expment.results[n]; // alias to make coding easier for(s in trial.probes) { var probe = trial.probes[s]; // alias to make coding easier probes++; if(probe.input_response == probe.response_required) { // if correct_response correct++; rt += probe.rt; mt += probe.mt; (probe.input_response ? p_rt += probe.rt : n_rt += probe.rt); (probe.input_response ? pos_correct++ : neg_correct++); if(ranges[trial.total_in_stim - 1].lower <= probe.rt && probe.rt <= ranges[trial.total_in_stim - 1].upper) { // only record set information without outliers set_size_total[trial.total_in_stim - 1] += 1; set_size_time[trial.total_in_stim - 1] += probe.rt; set_size_mt[trial.total_in_stim - 1] += probe.mt; } } } } } /* after grabbing totals, calculate */ avg_rt = Math.floor(rt / correct); avg_p_rt = Math.floor(p_rt / pos_correct); avg_n_rt = Math.floor(n_rt / neg_correct); avg_mt = Math.floor(mt / correct); accuracy = Math.floor((correct * 100) / probes); for(n in set_size_total) { set_size_avg[n] = Math.floor(set_size_time[n] / set_size_total[n]); set_size_mavg[n] = Math.floor(set_size_mt[n] / set_size_total[n]); } return { avg_rt : avg_rt, avg_p_rt : avg_p_rt, avg_n_rt : avg_n_rt, avg_mt : avg_mt, correct : correct, total : probes, accuracy : accuracy, set_size_avg : set_size_avg, set_size_mavg : set_size_mavg } } function get_worldStats() { return globals.world_stats; } var svg = new SVG(); this.svg = svg; // make globally accessible function SVG() { var svg = this; /* height and width of svg element on the page */ this.height = 361; this.width = 681; /* offsets of the graphable areas in the svg element (kind of like padding) */ this.top = 30; this.bottom = 301; this.left = 50; this.right = 661; /* edited by this.setCustomRange, used to tell where the range of the Y and X elements are given a set of points */ this.Ymax = 0; this.Ymin = 0; this.Xmax = 0; this.Xmin = 0; this.Yrange = 0; this.Xrange = 0; this.setCustomRange = function(times, for_range) { function array_max(arr) {return Math.max.apply(Math, arr);} function array_min(arr) {return Math.min.apply(Math, arr);} svg.Ymax = Math.max.apply(Math, times); svg.Ymin = Math.min.apply(Math, times); svg.Xmax = (times.length - 1); for(n in for_range) { svg.Ymax = Math.max(svg.Ymax, array_max(for_range[n])); svg.Ymin = Math.min(svg.Ymin, array_min(for_range[n])); svg.Xmax = Math.max(svg.Xmax, for_range[n].length - 1); } svg.Xmin = 0; svg.Yrange = svg.Ymax - svg.Ymin; svg.Xrange = svg.Xmax - svg.Xmin; }; this.calculateYPosition = function(point) { return (svg.bottom - ((point - svg.Ymin) / svg.Yrange) * (svg.bottom - svg.top)) }; this.calculateXPosition = function(pointNumber) { return (svg.left + ((pointNumber - svg.Xmin) / svg.Xrange) * (svg.right - svg.left)); }; this.mouseOverPoint = function(color, index, point) { var circle_selector = "#circle" + color + index; $(circle_selector).attr("r", 8); $("#svgText").empty(); $("#svgText").append("Set size: " + (Number(index) + 1) + ", Response Time: " + point + "ms"); $(circle_selector).on("mouseout", function() { $(circle_selector).attr("r", 5); $("#svgText").empty(); }); }; } function generateItemAsString(times, for_range, fun) { svg.setCustomRange(times, for_range); var string = ""; for(n in times) { string += fun(n, times[n]); } return string; } function generatePoints(times, for_range) { return generateItemAsString(times, for_range, function(num, point) { return svg.calculateXPosition(num) + "," + svg.calculateYPosition(point) + " "; }); } function generateCircles(times, for_range, color) { function getIdCol(color) { if(color.slice(0,1) == "#") { return color.slice(1); } else { return color; } } var id_col = getIdCol(color); return generateItemAsString(times, for_range, function(num, point) { return ""; }); } function generateAxes(times, for_range) { return generateItemAsString(times, for_range, function(num, point) { var ys = (svg.Yrange * (num / (times.length - 1))) + svg.Ymin; return "" + (Number(num) + 1) + "" + Math.floor(ys) + ""; }); } } /* ======================================================= SUBMIT HELPERS END HERE =================================== */ /*========================================================= FixedSet DEFINITION ENDS HERE ============================ */