/* * plugin for jsPsych based in Qisheng Li 11/2019. /// https://github.com/QishengLi/virtual_chinrest Modified by Gustavo Juantorena 08/2020 // https://github.com/GEJ1 Contributions from Peter J. Kohler: https://github.com/pjkohler */ jsPsych.plugins['virtual-chinrest'] = (function() { var plugin = {}; plugin.info = { name: "virtual-chinrest", parameters: { resize_units: { type: jsPsych.plugins.parameterType.STRING, default: "none", description: 'What units to resize to? ["none"/"cm"/"inch"/"deg"]. If "none", no resize will be done.' }, pixels_per_unit: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Pixels per unit', default: 100, description: 'After the scaling factor is applied, this many pixels will equal one unit of measurement.' }, mouse_adjustment: { type: jsPsych.plugins.parameterType.BOOL, pretty_name: 'Adjust Using Mouse?', default: true }, adjustment_prompt: { type: jsPsych.plugins.parameterType.STRING, default: ' Let’s find out how big your monitor is! '+ '

Please use any credit card that you have available.
' + 'It can also be a grocery store membership card,
'+ 'your drivers license or anything else of the same format.
'+ 'Place your card flat onto the screen, and adjust the slider below to match its size.

'+ '

If you do not have access to a real card
'+ 'you can use a ruler to measure the image width to 3.37 inches or 85.6 mm.
' }, adjustment_button_prompt: { type: jsPsych.plugins.parameterType.STRING, default: 'Click here when the card has the right size!' }, item_path: { type: jsPsych.plugins.parameterType.STRING, default: "img/card.png" }, item_height_mm: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Item height', default: 53.98, description: 'The height of the item to be measured.' }, item_width_mm: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Item width', default: 85.60, description: 'The width of the item to be measured.' }, item_init_size: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Initial Size', default: 250, description: 'The initial size of the card, in pixels, along the largest dimension.' }, blindspot_reps: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Blindspot measurement repetitions', default: 5, description: 'How many times to measure the blindspot location? If 0, blindspot will not detected and viewing distance not computed.' }, blindspot_prompt: { type: jsPsych.plugins.parameterType.STRING, default: 'Now, let’s quickly test how far away you are sitting.'+ '

You might know that vision tests at a doctor’s practice often involve chinrests.
'+ 'The doctor basically asks you to sit away from a screen in a specific distance.
'+ 'We do this here with a “virtual chinrest”.


'+ 'Instructions'+ '
'+ '
  1. Put your finger on space bar on the keyboard.
  2. '+ '
  3. Close your right eye. (Tips: it might be easier to cover your right eye by hand!)
  4. '+ '
  5. Using your left eye, focus on the black square.
  6. '+ '
  7. Click the button below to start the animation of the red ball. The red ball '+ 'will disappear as it moves from right to left. Press the “Space” key as soon as the ball disappears from your eye sight.
  8. '+ '

Keep your right eye closed and hit the “Space” key fast!


' }, blindspot_start_prompt: { type: jsPsych.plugins.parameterType.STRING, default: 'Start' }, blindspot_done_prompt: { type: jsPsych.plugins.parameterType.STRING, default: 'Done' }, blindspot_measurements_prompt: { type: jsPsych.plugins.parameterType.STRING, default: 'Remaining measurements: ' }, viewing_distance_report: { type: jsPsych.plugins.parameterType.STRING, default: 'Estimated viewing distance (cm):', description: 'If "none" is given, viewing distance will not be reported to the participant' } } } // Get screen size var w = window.innerWidth; var h = window.innerHeight; const screen_size_px = [] screen_size_px.push(w) screen_size_px.push('x') screen_size_px.push(h) let trial_data = {} // declare trial data as empty so we have access to it across functions let config_data = { "ball_pos": [], "slider_clck": false } plugin.trial = function(display_element, trial) { /*try {*/ if ( !( trial.blindspot_reps > 0 ) && ( (trial.resize_units == "deg" ) || (trial.resize_units == "degrees" ) ) ) { throw Error("Blindspot repetitions set to 0, so resizing to degrees of visual angle is not possible!") } else { const start_time = performance.now(); trial_data = { "item_width_mm": trial.item_width_mm, "item_height_mm": trial.item_height_mm //card dimension: 85.60 × 53.98 mm (3.370 × 2.125 in) }; let button_str = '
' + '
'+ '
'+ '
'+ '
'+ '' } else { pagesize_content += '
'+ '

'+ button_str + trial.adjustment_button_prompt + '

'+ ''+ '
'+ '' } const blindspot_content = '' display_element.innerHTML = '
'+ pagesize_content + blindspot_content + '
' // Event listeners for mouse-based resize if ( trial.mouse_adjustment ) { let dragging = false; let origin_x, origin_y; let cx, cy; const mouseupevent = function(e){ dragging = false; } display_element.addEventListener('mouseup', mouseupevent); const mousedownevent = function(e){ e.preventDefault(); dragging = true; origin_x = e.pageX; origin_y = e.pageY; cx = parseInt(scale_div.style.width); cy = parseInt(scale_div.style.height); } display_element.querySelector('#jspsych-resize-handle').addEventListener('mousedown', mousedownevent); const scale_div = display_element.querySelector('#item'); function resizeevent(e){ if(dragging){ let dx = (e.pageX - origin_x); let dy = (e.pageY - origin_y); if(Math.abs(dx) >= Math.abs(dy)){ scale_div.style.width = Math.round(Math.max(20, cx+dx*2)) + "px"; scale_div.style.height = Math.round(Math.max(20, cx+dx*2) / aspect_ratio ) + "px"; } else { scale_div.style.height = Math.round(Math.max(20, cy+dy*2)) + "px"; scale_div.style.width = Math.round(aspect_ratio * Math.max(20, cy+dy*2)) + "px"; } } } display_element.addEventListener('mousemove', resizeevent) } //Event listeners for buttons if ( trial.blindspot_reps > 0 ) { display_element.querySelector('#blind_spot').addEventListener('click', function(){ configureBlindSpot() }) display_element.querySelector('#start_ball').addEventListener('click', function(){ animateBall() }) } else { // run the two relevant functions to get item_width_mm and px2mm distanceSetup.px2mm(get_item_width()) } display_element.querySelector('#proceed').addEventListener('click', function(){ // finish trial trial_data.rt = performance.now() - start_time; display_element.innerHTML = ''; trial_data.item_width_deg = 2*(Math.atan((trial_data["item_width_mm"]/2)/trial_data["view_dist_mm"])) * 180/Math.PI trial_data.px2deg = trial_data["item_width_px"] / trial_data.item_width_deg // size of item in pixels divided by size of item in degrees of visual angle let px2unit_scr = 0 switch (trial.resize_units) { case "cm": case "centimeters": px2unit_scr = trial_data["px2mm"]*10 // pixels per centimeter break; case "inch": case "inches": px2unit_scr = trial_data["px2mm"]*25.4 // pixels per inch break; case "deg": case "degrees": px2unit_scr = trial_data["px2deg"] // pixels per degree of visual angle break; } if (px2unit_scr > 0) { // scale the window scale_factor = px2unit_scr / trial.pixels_per_unit; document.getElementById("jspsych-content").style.transform = "scale(" + scale_factor + ")"; // pixels have been scaled, so pixels per degree, pixels per mm and pixels per item_width needs to be updated trial_data.px2deg = trial_data.px2deg / scale_factor trial_data.px2mm = trial_data.px2mm / scale_factor trial_data.item_width_px = trial_data.item_width_px / scale_factor trial_data.scale_factor = scale_factor } if ( trial.blindspot_reps > 0 ) { trial_data.win_width_deg = window.innerWidth/trial_data.px2deg trial_data.win_height_deg = window.innerHeight/trial_data.px2deg } else { // delete degree related properties delete trial_data.px2deg delete trial_data.item_width_deg } jsPsych.finishTrial(trial_data); jsPsych.pluginAPI.cancelAllKeyboardResponses(); }) } /*} catch (e) { console.error(e) }*/ }; (function ( distanceSetup, $ ) { // jQuery short-hand for $(document).ready(function() { ... }); distanceSetup.round = function(value, decimals) { return Number(Math.round(value+'e'+decimals)+'e-'+decimals); }; distanceSetup.px2mm = function(item_width_px) { const px2mm = item_width_px/trial_data["item_width_mm"]; trial_data["px2mm"] = distanceSetup.round(px2mm, 2); return px2mm; }; }( window.distanceSetup = window.distanceSetup || {}, jQuery)) function get_item_width() { const item_width_px = $('#item').width(); trial_data["item_width_px"] = distanceSetup.round(item_width_px,2); return item_width_px } function configureBlindSpot() { drawBall(); $('#page-size').remove(); $('#blind-spot').css({'visibility':'visible'}); // $(document).on('keydown', recordPosition); $(document).on('keydown', recordPosition); } $( function() { $( "#slider" ).slider({value:"50"}); } ); $(document).ready(function() { $( "#slider" ).on("slide", function (event, ui) { const item_width = ui.value + "%"; $("#item").css({"width":item_width}); }); $('#slider').on('slidechange', function(event, ui){ config_data["slider_clck"] = true; }); }); //============================= //Ball Animation function drawBall(pos=180){ // pos: define where the fixation square should be. var mySVG = SVG("svgDiv"); const item_width_px = get_item_width() const rectX = distanceSetup.px2mm(item_width_px)*pos; const ballX = rectX*0.6 // define where the ball is var ball = mySVG.circle(30).move(ballX, 50).fill("#f00"); window.ball = ball; var square = mySVG.rect(30, 30).move(Math.min(rectX - 50, 950), 50); //square position config_data["square_pos"] = distanceSetup.round(square.cx(),2); config_data['rectX'] = rectX config_data['ballX'] = ballX }; function animateBall(){ ball.animate(7000).during( function(pos){ moveX = - pos*config_data['ballX']; window.moveX = moveX; moveY = 0; ball.attr({transform:"translate("+moveX+","+moveY+")"}); } ).loop(true, false). after(function(){ animateBall(); }); //disable the button after clicked once. $("#start_ball").attr("disabled", true); $('#start_ball').css("display", "none"); }; function recordPosition(event, angle=13.5) { // angle: define horizontal blind spot entry point position in degrees. if (event.keyCode == '32') { //Press "Space" config_data["ball_pos"].push(distanceSetup.round((ball.cx() + moveX),2)); var sum = config_data["ball_pos"].reduce((a, b) => a + b, 0); var ballPosLen = config_data["ball_pos"].length; config_data["avg_ball_pos"] = distanceSetup.round(sum/ballPosLen, 2); var ball_sqr_distance = (config_data["square_pos"]-config_data["avg_ball_pos"])/trial_data["px2mm"]; var viewDistance = ball_sqr_distance/Math.radians(angle) trial_data["view_dist_mm"] = distanceSetup.round(viewDistance, 2); //counter and stop var counter = Number($('#click').text()); counter = counter - 1; $('#click').text(Math.max(counter, 0)); if (counter <= 0) { ball.stop(); // Disable space key $('html').bind('keydown', function(e) { if (e.keyCode == 32) {return false;} }); // Display data $('#info').css("visibility", "visible"); $('#info-h').append(trial_data["view_dist_mm"]/10) $('#proceed').css("display", "inline"); return //trial_data.viewing_distance_cm; } ball.stop(); animateBall(); } }; //helper function for radians // Converts from degrees to radians. Math.radians = function(degrees) { return degrees * Math.PI / 180; }; return plugin; })();