scripts/experiments/numerosity/numerosity.js
import {generateRandomDistribution} from "/scripts/experiment-properties/distribution/random_distribution_generator.js";
import {balance_subconditions} from "/scripts/experiment-properties/balancing/balancing_controller.js";
import {get_data} from "/scripts/experiment-properties/data/data_controller.js";
export default class Numerosity {
/**
* Initializes a Numerosity experiment object.
*
* @param params {object} Parameters passed in from routing
*/
constructor(params) {
var address = location.protocol + "//" + location.hostname + ":" + location.port;
let trial_structure = params["trial_structure"];
let condition_name = params["condition"];
let graph_type = params["graph_type"];
let balancing_type = params["balancing"];
this.condition_name = condition_name;
this.condition_group = this.condition_name.split('_')[0];
// **NOTE: EXPERIMENTS variable comes from /public/config/experiments-config.js
if (!EXPERIMENTS["numerosity"]["trial_structure"].includes(trial_structure)) {
throw Error(trial_structure + " is not supported.");}
else {
this.trial_structure = trial_structure;
}
if (!EXPERIMENTS["numerosity"]["graph_type"].includes(graph_type)){
throw Error(graph_type + " is not supported.")}
else {
this.graph_type = graph_type;
};
if (!EXPERIMENTS["numerosity"]["balancing_type"].includes(balancing_type)) {
throw Error(balancing_type + " is not supported.") }
else {
this.balancing_type = balancing_type;
}
this.condition_name = condition_name;
this.subject_id = params["subject_id"];
this.subject_initials = params["subject_initials"];
// ========================================
// EXPERIMENT VARIABLES
this.raw_sub_conds; // subconditions in estimation_data.js
this.target_color = "#dbc667";
// ========================================
// TEST EXPERIMENT VARIABLES
this.sub_condition_order;
this.experiment_conditions_constants = [];
this.current_sub_condition_index;
this.trial_responses = [];
/// ========================================
// CURRENT TRIAL DATA
// Plotting-related vars
this.target_coordinates = "";
this.distractor_coordinates = "";
// JsPsych trial_data for the current trial
this.trial_data = "";
// ========================================
// PREPARE EXPERIMENT
// Extract raw constants
this.raw_sub_conds = get_data(this);
// Prepare experiment
this.prepare_experiment();
}
/**
* Orders the input data according to balancing type and
* initializes the Estimation object's variables.
*
*/
prepare_experiment() {
let dataset = this.raw_sub_conds;
this.sub_condition_order = balance_subconditions(this.balancing_type, this.constructor.name.toLowerCase(), dataset.length);
let ordered_dataset = [];
// Order the data set according to the randomly ordered array
for (let i = 0; i < this.sub_condition_order.length; i++) {
ordered_dataset[i] = dataset[this.sub_condition_order[i]];
}
// Set experiment trials
this.experiment_conditions_constants = ordered_dataset;
this.current_sub_condition_index = 0;
}
/**
* Generates a Numerosity object for use in the JsPsych timeline.
* Numerosity currently does not support practice trials
*
* Each trial begins with a fixation trial block,
* followed by the stimulus,
* ended by a feedback trial block where the subject must estimate the
* number of target stimuli via the slider
*
* @param block_type {string} "test" or "practice"
* @return trial {object}
*/
generate_trial(block_type) {
if ((block_type !== "test") && (block_type !== "practice")) {
throw Error(block_type + " is not supported.")
}
// Initialize a variable for this so it is usable inside on_start
var numerosity_exp = this;
var address = location.protocol + "//" + location.hostname + ":" + location.port + "/numerosity_trial";
let group = {};
var fixation = {
type: 'html-keyboard-response',
stimulus: '<div style="font-size:60px;">+</div>',
choices: jsPsych.NO_KEYS,
trial_duration: 1000,
data: {type: 'fixation'}
};
var trial = {
type:'external-html-keyboard-response',
url: address,
choices: jsPsych.NO_KEYS,
trial_duration: 2000,
execute_script: true,
on_start: function(trial){ // NOTE: on_start takes in trial var
var index = numerosity_exp.current_sub_condition_index;
var constants = numerosity_exp.experiment_conditions_constants[index];
trial.data = constants;
numerosity_exp.set_target_color(constants);
var base_coordinates = generateRandomDistribution(constants.row, constants.col, constants.target_num_points, null);
numerosity_exp.coordinates = [base_coordinates];
if (numerosity_exp.condition_group === "distractor") {
var distractor_coordinates = generateRandomDistribution(constants.row, constants.col, constants.dist_num_points, base_coordinates);
numerosity_exp.distractor_coordinates = [distractor_coordinates];
}
numerosity_exp.trial_data = trial.data;
}
};
var slider_response = {
type: 'html-slider-response',
labels: [8,62],
min: 8,
max: 62,
start: 35,
stimulus:
"<p>How many of this square did you see?",
prompt: '<p>Select the number by sliding the bar</p>',
on_start: function(slider_response) {
slider_response.stimulus = "<p>How many of this square did you see?" +
"<div align = 'center' style='height: 200px; display: block;'>"+
`<img src='http://localhost:8080/img/instructions/numerosity/${numerosity_exp.target_color}.png'></img>`+
"</div>" +
"<div align = 'center' style='height: 25px; display: block;'>"+
"</div><p> </p>";
var index = numerosity_exp.current_sub_condition_index;
var constants = numerosity_exp.experiment_conditions_constants[index];
slider_response.data = constants;
numerosity_exp.handle_data_saving(slider_response, block_type, constants, index);
}
};
//trial.data.slider_response = slider_response.data.response;
group.timeline = [fixation, trial, slider_response];
return group;
}
/*
Fetches and sets target color from subcondition constants
for use in the response/feedback trial block.
*/
set_target_color(target) {
console.log(target);
var target_hex;
if (target.point_color) {
console.log("targetpointcolor");
target_hex = target.point_color.substring(1); //removing the # symbol from the target.point_color
}
if (target.target_color) {
console.log("targettargetcolor");
target_hex = target.target_color.substring(1);
}
if (target.mix_by_attribute) {
if (target.mix_by_attribute.point_color) {
target_hex = target.mix_by_attribute.point_color[0].substring(1);
}
}
this.target_color = "num_" + target_hex;
console.log(this.target_color);
}
/**
* Handles saving the relevant data on a given trial.
*
*/
handle_data_saving(trial, block_type, constants, index) {
// Add all constants from excel
trial.data = constants;
// Adding constants that required computation (not from excel)
trial.data.type = "numerosity";
trial.data.sub_condition = index;
// Block specific saves
if (block_type == "test"){
trial.data.run_type = "test";
}
else{
trial.data.run_type = "practice";
}
}
/*
* Saves experiment data as csv
* */
export_trial_data() {
var trial_data = jsPsych.data.get().filter({type: 'numerosity', run_type: 'test'})
.ignore('type')
.ignore('run_type')
.ignore('left_correlation')
.ignore('right_correlation')
// These are variables forced on by jsPsych
.ignore('stimulus')
.ignore('key_press')
.ignore('choices')
.ignore('trial_type')
.ignore('trial_index')
.ignore('time_elapsed')
.ignore('internal_node_id');
var string = "S" + this.subject_id + "_" + this.condition_name + "_numerosity_trial_results.csv";
trial_data.localSave('csv', string);
}
/**
* When called, will save aggregated trial data into a CSV.
*/
export_summary_data() {
var csv = 'SUBJECT_ID,SUBJECT_INITIALS,CONDITION_NAME,NUM_TARGET_POINTS,ROW,COL,TRIALS\n';
var data = [];
// Organize each row of the csv
for (let i = 0; i<this.experiment_conditions_constants.length; i++){
var row = [this.subject_id, this.subject_initials, this.condition_name];
var constants = this.experiment_conditions_constants[i];
var condition_data = jsPsych.data.get();
row.push(constants.target_num_points);
row.push(constants.row);
row.push(constants.col);
row.push(condition_data.count());
data.push(row);
}
// Append each row
data.forEach(function(row){
csv += row.join(',');
csv += "\n";
});
var hiddenElement = document.createElement('a');
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
hiddenElement.target = '_blank';
hiddenElement.download = "S" + this.subject_id + "_" + this.condition_name + "_numerosity_summary_results.csv";
hiddenElement.click();
}
}