import { JsPsych , JsPsychPlugin , ParameterType , TrialType } from "jspsych" ; const info = < const > { name : "visual-search-circle" , parameters : { /** The target image to be displayed. This must specified when using the target, foil and set_size parameters to define the stimuli set, rather than the stimuli parameter. */ target : { type : ParameterType . IMAGE , pretty_name : "Target" , default : null , } , /** The image to use as the foil/distractor. This must specified when using the target, foil and set_size parameters to define the stimuli set, rather than the stimuli parameter. */ foil : { type : ParameterType . IMAGE , pretty_name : "Foil" , default : null , } , /** How many items should be displayed, including the target when target_present is true? This must specified when using the target, foil and set_size parameters to define the stimuli set, rather than the stimuli parameter. */ set_size : { type : ParameterType . INT , pretty_name : "Set size" , default : null , } , /** Array containing one or more image files to be displayed. This only needs to be specified when NOT using the target, foil, and set_size parameters to define the stimuli set. */ stimuli : { type : ParameterType . IMAGE , pretty_name : "Stimuli" , default : null , array : true , } , /**
         * Is the target present?
         * When using the target, foil and set_size parameters, false means that the foil image will be repeated up to the set_size,
         * and if true, then the target will be presented along with the foil image repeated up to set_size - 1.
         * When using the stimuli parameter, this parameter is only used to determine the response accuracy.
         */ target_present : { type : ParameterType . BOOL , pretty_name : "Target present" , default : undefined , } , /** Path to image file that is a fixation target. */ fixation_image : { type : ParameterType . IMAGE , pretty_name : "Fixation image" , default : undefined , } , /** Two element array indicating the height and width of the search array element images. */ target_size : { type : ParameterType . INT , pretty_name : "Target size" , array : true , default : [ 50 , 50 ] , } , /** Two element array indicating the height and width of the fixation image. */ fixation_size : { type : ParameterType . INT , pretty_name : "Fixation size" , array : true , default : [ 16 , 16 ] , } , /** The diameter of the search array circle in pixels. */ circle_diameter : { type : ParameterType . INT , pretty_name : "Circle diameter" , default : 250 , } , /** The key to press if the target is present in the search array. */ target_present_key : { type : ParameterType . KEY , pretty_name : "Target present key" , default : "j" , } , /** The key to press if the target is not present in the search array. */ target_absent_key : { type : ParameterType . KEY , pretty_name : "Target absent key" , default : "f" , } , /** The maximum duration to wait for a response. */ trial_duration : { type : ParameterType . INT , pretty_name : "Trial duration" , default : null , } , /** How long to show the fixation image for before the search array (in milliseconds). */ fixation_duration : { type : ParameterType . INT , pretty_name : "Fixation duration" , default : 1000 , } , } , } ; type Info = typeof info ; author Josh de Leeuw
 *  see https class VisualSearchCirclePlugin implements JsPsychPlugin < Info > { static info = info ; constructor ( private jsPsych ) { } trial ( display_element , trial ) { var paper_size = trial . circle_diameter + trial . target_size [ 0 ] ; var fix_loc = this . generateFixationLoc ( trial ) ; var to_present = this . generatePresentationSet ( trial ) ; var display_locs = this . generateDisplayLocs ( to_present . length , trial ) ; display_element . innerHTML += '<div id="jspsych-visual-search-circle-container" style="position: relative; width:' + paper_size + "px; height:" + paper_size + 'px"></div>' ; var paper = display_element . querySelector ( "#jspsych-visual-search-circle-container" ) ; const show_fixation = ( ) => { paper . innerHTML += "<img src='" + trial . fixation_image + "' style='position: absolute; top:" + fix_loc [ 0 ] + "px; left:" + fix_loc [ 1 ] + "px; width:" + trial . fixation_size [ 0 ] + "px; height:" + trial . fixation_size [ 1 ] + "px;'></img>" ; this . jsPsych . pluginAPI . setTimeout ( ( ) => { show_search_array ( ) ; } , trial . fixation_duration ) ; } ; const end_trial = ( rt , correct , key_press ) => { var trial_data = { correct : correct , rt : rt , response : key_press , locations : display_locs , target_present : trial . target_present , set_size : trial . set_size , } ; this . jsPsych . finishTrial ( trial_data ) ; } ; show_fixation ( ) ; const show_search_array = ( ) => { for ( var i = 0 ; i < display_locs . length ; i ++ ) { paper . innerHTML += "<img src='" + to_present [ i ] + "' style='position: absolute; top:" + display_locs [ i ] [ 0 ] + "px; left:" + display_locs [ i ] [ 1 ] + "px; width:" + trial . target_size [ 0 ] + "px; height:" + trial . target_size [ 1 ] + "px;'></img>" ; } var trial_over = false ; const after_response = ( info ) => { trial_over = true ; var correct = false ; if ( ( this . jsPsych . pluginAPI . compareKeys ( info . key , trial . target_present_key ) && trial . target_present ) || ( this . jsPsych . pluginAPI . compareKeys ( info . key , trial . target_absent_key ) && ! trial . target_present ) ) { correct = true ; } clear_display ( ) ; end_trial ( info . rt , correct , info . key ) ; } ; var valid_keys = [ trial . target_present_key , trial . target_absent_key ] ; const key_listener = this . jsPsych . pluginAPI . getKeyboardResponse ( { callback_function : after_response , valid_responses : valid_keys , rt_method : "performance" , persist : false , allow_held_key : false , } ) ; if ( trial . trial_duration !== null ) { this . jsPsych . pluginAPI . setTimeout ( ( ) => { if ( ! trial_over ) { this . jsPsych . pluginAPI . cancelKeyboardResponse ( key_listener ) ; trial_over = true ; var rt = null ; var correct = false ; var key_press = null ; clear_display ( ) ; end_trial ( rt , correct , key_press ) ; } } , trial . trial_duration ) ; } function clear_display ( ) { display_element . innerHTML = "" ; } } ; } private generateFixationLoc ( trial ) { var paper_size = trial . circle_diameter + trial . target_size [ 0 ] ; return [ Math . floor ( paper_size / 2 - trial . fixation_size [ 0 ] / 2 ) , Math . floor ( paper_size / 2 - trial . fixation_size [ 1 ] / 2 ) , ] ; } private generateDisplayLocs ( n_locs , trial ) { var diam = trial . circle_diameter ; var radi = diam / 2 ; var paper_size = diam + trial . target_size [ 0 ] ; var stimh = trial . target_size [ 0 ] ; var stimw = trial . target_size [ 1 ] ; var hstimh = stimh / 2 ; var hstimw = stimw / 2 ; var display_locs = [ ] ; var random_offset = Math . floor ( Math . random ( ) * 360 ) ; for ( var i = 0 ; i < n_locs ; i ++ ) { display_locs . push ( [ Math . floor ( paper_size / 2 + this . cosd ( random_offset + i * ( 360 / n_locs ) ) * radi - hstimw ) , Math . floor ( paper_size / 2 - this . sind ( random_offset + i * ( 360 / n_locs ) ) * radi - hstimh ) , ] ) ; } return display_locs ; } private generatePresentationSet ( trial ) { var to_present = [ ] ; if ( trial . target !== null && trial . foil !== null && trial . set_size !== null ) { if ( trial . target_present ) { for ( var i = 0 ; i < trial . set_size - 1 ; i ++ ) { to_present . push ( trial . foil ) ; } to_present . push ( trial . target ) ; } else { for ( var i = 0 ; i < trial . set_size ; i ++ ) { to_present . push ( trial . foil ) ; } } } else if ( trial . stimuli !== null ) { to_present = trial . stimuli ; } else { console . error ( "Error in visual-search-circle plugin: you must specify an array of images via the stimuli parameter OR specify the target, foil and set_size parameters." ) ; } return to_present ; } private cosd ( num ) { return Math . cos ( ( num / 180 ) * Math . PI ) ; } private sind ( num ) { return Math . sin ( ( num / 180 ) * Math . PI ) ; } simulate ( trial , simulation_mode , simulation_options , load_callback ) { if ( simulation_mode == "data-only" ) { load_callback ( ) ; this . simulate_data_only ( trial , simulation_options ) ; } if ( simulation_mode == "visual" ) { this . simulate_visual ( trial , simulation_options , load_callback ) ; } } private create_simulation_data ( trial , simulation_options ) { const key = this . jsPsych . pluginAPI . getValidKey ( [ trial . target_present_key , trial . target_absent_key , ] ) ; const set = this . generatePresentationSet ( trial ) ; const default_data = { correct : trial . target_present ? key == trial . target_present_key : key == trial . target_absent_key , response : key , rt : this . jsPsych . randomization . sampleExGaussian ( 500 , 50 , 1 / 150 , true ) , set_size : set . length , target_present : trial . target_present , locations : this . generateDisplayLocs ( set . length , trial ) , } ; const data = this . jsPsych . pluginAPI . mergeSimulationData ( default_data , simulation_options ) ; this . jsPsych . pluginAPI . ensureSimulationDataConsistency ( trial , data ) ; return data ; } private simulate_data_only ( trial , simulation_options ) { const data = this . create_simulation_data ( trial , simulation_options ) ; this . jsPsych . finishTrial ( data ) ; } private simulate_visual ( trial , simulation_options , load_callback ) { const data = this . create_simulation_data ( trial , simulation_options ) ; const display_element = this . jsPsych . getDisplayElement ( ) ; this . trial ( display_element , trial ) ; load_callback ( ) ; if ( data . rt !== null ) { this . jsPsych . pluginAPI . pressKey ( data . response , trial . fixation_duration + data . rt ) ; } } } export default VisualSearchCirclePlugin ;