diff --git a/docs/tutorials/rt-task.md b/docs/tutorials/rt-task.md index 05c4dafb..828b8397 100644 --- a/docs/tutorials/rt-task.md +++ b/docs/tutorials/rt-task.md @@ -5,6 +5,7 @@ This tutorial will work through the creation of a simple response time task. The * Using a plugin to create a standard trial. * Combining plugins together to create new kinds of trials. * Using timeline variables to maximize code reuse. +* Preloading media * Randomizing presentation order. * Manipulating, filtering, and aggregating data. * Using dynamic content to change the experiment parameters based on the subject's responses. @@ -18,9 +19,9 @@ Start by downloading jsPsych and setting up a folder to contain your experiment My experiment - - - + + + @@ -36,9 +37,9 @@ All jsPsych experiments are defined by a timeline. The timeline is an array that var timeline = []; ``` -Let's greet the subject with a simple welcome message using the [jspsych-html-keyboard-response](../plugins/jspsych-html-keyboard-response.md) plugin. +Let's greet the subject with a simple welcome message using the [jspsych-html-keyboard-response](/plugins/jspsych-html-keyboard-response.md) plugin. -First, we create a trial that uses the jspsych-html-keyboard-response plugin and contains a simple string to show the subject. +First, we create a trial that uses the `jspsych-html-keyboard-response` plugin and contains a simple string to show the subject. ```javascript var welcome = { @@ -61,62 +62,67 @@ jsPsych.init({ }); ``` -### The complete code so far +??? example "The complete code so far" + ``` html + + + + My experiment + + + + + + - - - - - - -``` + /* start the experiment */ + jsPsych.init({ + timeline: timeline + }); + + + ``` ## Part 3: Show instructions -We can use the same basic structure from part 2 to create a new trial that shows instructions to the subject. The only difference in this trial is that we will use HTML formatting to control how the instructions display. +We can use the same basic structure from part 2 to create a new trial that shows instructions to the subject. The only difference in this trial is that we will use HTML formatting to control how the instructions display and we will add a two second gap after the trial using the `post_trial_gap` parameter. The trial definition looks like this: ```javascript var instructions = { type: "html-keyboard-response", - stimulus: "

In this experiment, a circle will appear in the center " + - "of the screen.

If the circle is blue, " + - "press the letter F on the keyboard as fast as you can.

" + - "

If the circle is orange, press the letter J " + - "as fast as you can.

" + - "
"+ - "
" + - "

Press the F key

" + - "
" + - "

Press the J key

" + - "
"+ - "

Press any key to begin.

" + stimulus: ` +

In this experiment, a circle will appear in the center + of the screen.

If the circle is blue, + press the letter F on the keyboard as fast as you can.

+

If the circle is orange, press the letter J + as fast as you can.

+
+
+

Press the F key

+
+

Press the J key

+
+

Press any key to begin.

+ `, + post_trial_gap: 2000 }; ``` +!!! tip + In JavaScript there are three different ways to define a `string`. You can use single quotes `'`, double quotes `"`, or backticks `` ` ``. Using backticks has two advantages over the other approaches, especially when you are creating long strings with HTML. You can extend the `string` across multiple lines and you can use [template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) to easily incorporate variables. + Notice that the HTML includes `` tags to display the images that the subject will be responding to. You'll need to download these image files. Right-click on each image below and select *Save image as...*. Put the images in a folder called `img` in the experiment folder you created in part 1. ![blue circle](../img/blue.png) @@ -128,68 +134,69 @@ Don't forget to add the trial to the timeline: timeline.push(instructions); ``` -### The complete code so far +??? example "The complete code so far" + ```html + + + + My experiment + + + + + + - - - - - - -``` + /* start the experiment */ + jsPsych.init({ + timeline: timeline + }); + + + ``` ## Part 4: Displaying stimuli and getting responses Creating trials to show the stimuli is conceptually the same as creating a trial to show instructions, except that now we are displaying an image instead of text or html. This means we need to use a different plugin: jspsych-image-keyboard-response. We need to start by loading this plugin by adding a ` - - - + + + + ``` @@ -215,75 +222,189 @@ As usual, we need to add the trials to the timeline. timeline.push(blue_trial, orange_trial); ``` -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + - + /* start the experiment */ + jsPsych.init({ + timeline: timeline + }); + + + ``` + +## Part 5: Preloading media + +Whenever we use media elements (images, audio, or video) in an experiment it is a good idea to preload them prior to needing them for a trial. By preloading media we ask the participant's browser to download the media ahead of needing it, so that when we do need to display or play it there is no lag from needing to download it. + +We are going to use the [jspsych-preload plugin](/plugins/jspsych-preload.md) to preload the two images. The [media preloading section](/overview/media-preloading.md) goes into a lot of detail about various options for preloading and different ways that you can use this plugin. Here we are simply going to give the plugin a list of the files that we want to be preloaded. + +First we need to add the preload plugin to our `` section. + +```html hl_lines="6" + + My experiment + + + + + + + ``` -## Part 5: Timeline variables +We'll put this trial at the very start of the experiment, so add this code before the `welcome` trial. + +```js +var preload = { + type: 'preload', + images: ['img/blue.png', 'img/orange.png'] +} +``` + +As always, add the trial to the timeline. + +```js +timeline.push(preload); +``` + +??? example "The complete code so far" + + ```html + + + + My experiment + + + + + + + + + + ``` + +## Part 6: Timeline variables In the full experiment, we will want more than two trials. One way we could do this is to create many more objects that define trials and push them all onto the timeline, but there is a more efficient way: using timeline variables. @@ -328,96 +449,106 @@ var test_procedure = { } ``` -We have to add the `test_procedure` to the main `timeline` array, but the fixation and test trial do not need to be added to `timeline` because they already exist on the `test_procedure` timeline. +We have to add the `test_procedure` to the main `timeline` array, but the `fixation` and `test` trial do not need to be added to `timeline` because they already exist on the `test_procedure` timeline. ```javascript timeline.push(test_procedure); ``` -What happens when the experiment reaches the test procedure? jsPsych will run the `test_procedure` timeline one time for each entry in the `test_stimuli` array (twice, in this case). The first time through, jsPsych will substitute the timeline variables from the first array entry (blue image), and the second time through the second array entry will be used (orange image). Notice that the fixation trial occurs before both the orange and the blue circles, because the entire timeline of the `test_procedure` is repeated for each entry in the `timeline_variables` array. +What happens when the experiment reaches the test procedure? jsPsych will run the `test_procedure` timeline one time for each entry in the `test_stimuli` array (two times total, in this case). The first time through, jsPsych will substitute the timeline variables from the first array entry (blue image), and the second time through the second array entry will be used (orange image). Notice that the fixation trial occurs before both the orange and the blue circles, because the entire timeline of the `test_procedure` is repeated for each entry in the `timeline_variables` array. -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + + - -``` + timeline.push(test_procedure); + + /* start the experiment */ + jsPsych.init({ + timeline: timeline + }); + + + ``` -## Part 6: Parameters for timelines with timeline variables +## Part 7: Parameters for timelines with timeline variables -Right now our experiment is a measly two trials long. Even worse is that the order of the stimuli is the same every time! When we use timeline variables, we get access to some very easy-to-use methods to randomize the order and repeat the trials. To randomize the order, simply set `randomize_order: true` on the object with the `timeline_variables`: +Right now our experiment is a measly two trials long. Even worse is that the order of the stimuli is the same every time! When we use timeline variables, we get access to some methods to randomize the order and repeat the trials. To randomize the order, simply set `randomize_order: true` on the object with the `timeline_variables`: ```javascript var test_procedure = { @@ -437,91 +568,101 @@ var test_procedure = { repetitions: 5 } ``` -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + + - -``` + timeline.push(test_procedure); -## Part 7: Using functions to generate parameters + /* start the experiment */ + jsPsych.init({ + timeline: timeline + }); + + + ``` + +## Part 8: Using functions to generate parameters One aspect of the experiment that could be improved is the duration of the fixation cross. As the experiment stands right now, the timing of the circles appearing is very predictable. We can change that by using a different value for the `trial_duration` parameter in the `fixation` trial for each trial. But how can we do that and keep the simple code structure we have now where we only have to define the fixation trial once? One option would be to add another timeline variable, like `"fixation_duration"` and use that to control the timing. But another option is to specify the `trial_duration` parameter as a function. If a parameter is a function, jsPsych will execute the function every time the trial runs. That means that if the function returns different results probabilistically, we can get a different parameter value every time the trial runs. -To do that here, we'll use one of the built-in randomization methods in [jsPsych's randomization module](../core_library/jspsych-randomization.md). `jsPsych.randomization.sampleWithoutReplacement()` takes an array of items to sample from and generates a new array of length *N* by sampling without replacement. +To do that here, we'll use one of the built-in randomization methods in [jsPsych's randomization module](/core_library/jspsych-randomization.md). `jsPsych.randomization.sampleWithoutReplacement()` takes an array of items to sample from and generates a new array of length *N* by sampling without replacement. ```javascript var fixation = { @@ -536,93 +677,103 @@ var fixation = { In the code above, we replaced the `trial_duration: 1000` parameter in `fixation` with a function. Inside the function, we take a sample from the array `[250, 500, 750, 1000, 1250, 1500, 1750, 2000]` of size 1 (second parameter to `jsPsych.randomization.sampleWithoutReplacement`). The return value from calling `jsPsych.randomization.sampleWithoutReplacement` is an array of length 1, so we add the `[0]` selection at the end to get the value out of the array. -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + + - -``` + timeline.push(test_procedure); -## Part 8: Displaying the data + /* start the experiment */ + jsPsych.init({ + timeline: timeline + }); + + + ``` -We have created a complete, if simple, experiment at this point, so let's take a look at the data being generated. jsPsych has a built-in [function called `jsPsych.data.displayData()`](../core_library/jspsych-data.md#jspsychdatadisplaydata) that is useful for debugging your experiment. It will remove all of the information on the screen and replace it with the raw data collected so far. This isn't terribly useful when you are actually running an experiment, but it's nice for checking the data during development. +## Part 10: Displaying the data -We need the `displayData` function to execute when the experiment ends. One way to do this is to use the [`on_finish` callback function](../overview/callbacks.md#on_finish-experiment). This function will automatically execute once all the trials in the experiment are finished. We can specify a function to call in the `init` method. +We have created a complete, if simple, experiment at this point, so let's take a look at the data being generated. jsPsych has a built-in [function called `jsPsych.data.displayData()`](/core_library/jspsych-data.md#jspsychdatadisplaydata) that is useful for debugging your experiment. It will remove all of the information on the screen and replace it with the raw data collected so far. This isn't terribly useful when you are actually running an experiment, but it's nice for checking the data during development. + +We need the `displayData` function to execute when the experiment ends. One way to do this is to use the [`on_finish` callback function](/overview/callbacks.md#on_finish-experiment). This function will automatically execute once all the trials in the experiment are finished. We can specify a function to call in the `init` method. ```javascript jsPsych.init({ @@ -633,119 +784,144 @@ jsPsych.init({ }); ``` -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + + - -``` + timeline.push(test_procedure); -## Part 9: Tagging trials with additional data + /* start the experiment */ + jsPsych.init({ + timeline: timeline, + on_finish: function() { + jsPsych.data.displayData(); + } + }); + + + ``` + +## Part 11: Tagging trials with additional data All trials in jsPsych can be tagged with additional arbitrary data. This data will get stored alongside the data that the plugin normally generates, which allows experimenters to record properties of a trial along with the data from the trial. -When might you use this feature? In this experiment, it would be nice to tag each trial with a circle as a test trial, so that the resulting data can be easily filtered to look at only the test trials. We also could tag the test trials with a property that indicates what the correct response should be (F for the blue circles, J for the orange). - -In our current code, we are using the timeline variables feature of jsPsych to choose which circle gets presented on a trial. Since we want to tag the trials differently based on which circle is presented, we need to add the tagging data to the `test_stimuli` array, and then use the `jsPsych.timelineVariable()` function to get the values and assign them to the `data` property of the trial. - -We start by modifying `test_stimuli`: - -```javascript -var test_stimuli = [ - { stimulus: "img/blue.png", data: {test_part: 'test', correct_response: 'f'}}, - { stimulus: "img/orange.png", data: {test_part: 'test', correct_response: 'j'}} -]; -``` -Now we assign these values to the `data` parameter of the `test` trial. +When might you use this feature? In this experiment, it would be nice to tag each trial with a circle as a `response` trial, so that the resulting data can be easily filtered to look at only the critical trials. We can do that like this. ```javascript var test = { type: "image-keyboard-response", stimulus: jsPsych.timelineVariable('stimulus'), choices: ['f', 'j'], - data: jsPsych.timelineVariable('data') + data: { + task: 'response' + } } ``` -Another kind of tagging that would be useful is to mark each fixation trial as such, to make removing the data from fixation trials easier. This is a simpler task, as we don't need to use the timeline variables feature. We can just add a `data` property to the `fixation` trial: +We also could tag the test trials with a property that indicates what the correct response should be (F for the blue circles, J for the orange). In our current code, we are using the timeline variables feature of jsPsych to choose which circle gets presented on a trial. Since we want to tag the trials differently based on which circle is presented, we need to add the tagging data to the `test_stimuli` array, and then use the `jsPsych.timelineVariable()` function to get the value and assign it to a property in the `data` of the trial. + +We start by modifying `test_stimuli`: + +```javascript +var test_stimuli = [ + { stimulus: "img/blue.png", correct_response: 'f'}, + { stimulus: "img/orange.png", correct_response: 'j'} +]; +``` + +Now we can use `timelineVariable()` in the `data` parameter of the `test` trial. + +```javascript +var test = { + type: "image-keyboard-response", + stimulus: jsPsych.timelineVariable('stimulus'), + choices: ['f', 'j'], + data: { + task: 'response', + correct_response: jsPsych.timelineVariable('correct_response') + } +} +``` + +Another kind of tagging that would be useful is to mark each fixation trial as such, to make removing the data from fixation trials easier. ```js var fixation = { @@ -755,102 +931,119 @@ var fixation = { trial_duration: function(){ return jsPsych.randomization.sampleWithoutReplacement([250, 500, 750, 1000, 1250, 1500, 1750, 2000], 1)[0]; }, - data: {test_part: 'fixation'} + data: { + task: 'fixation' + } } ``` -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + + - -``` + timeline.push(test_procedure); -## Part 10: Manipulating data during the experiment + /* start the experiment */ + jsPsych.init({ + timeline: timeline, + on_finish: function() { + jsPsych.data.displayData(); + } + }); + + + ``` -Now that the data from the test trials has a tag that describes the correct response, it would be easy to analyze the data after the fact (in R, for example) and calculate whether the participant responded correctly. +## Part 12: Manipulating data during the experiment -But, we can also do this in jsPsych as the experiment runs to save time later and enable a limited set of data aggregation and analysis directly in the experiment code. +Now that the data from the test trials has a tag that describes the correct response, it would be easy to analyze the data after the fact and calculate whether the participant responded correctly. + +But, we can also do this in jsPsych as the experiment runs to save time later and enable a limited set of data analysis directly in the experiment code. To do this, we'll use the `on_finish` event of the test trial. We can assign a function to `on_finish`, and that function will receive an object containing the data generated by the trial. This object can be manipulated inside the function, and any changes made to the object will be stored in jsPsych's internal representation of the data. @@ -861,125 +1054,145 @@ var test = { type: "image-keyboard-response", stimulus: jsPsych.timelineVariable('stimulus'), choices: ['f', 'j'], - data: jsPsych.timelineVariable('data'), + data: { + task: 'response', + correct_response: jsPsych.timelineVariable('correct_response') + }, on_finish: function(data){ - data.correct = data.key_press == data.correct_response; + data.correct = data.response == data.correct_response; } } ``` -The `data.key_press` value is a string representation of the key the subject pressed. We can compare this with the `data.correct_response` value, and assign this computer value to a new property `data.correct`. +The `data.response` value is a string representation of the key the subject pressed. We can compare this with the `data.correct_response` value, and assign this computed value to a new property `data.correct`. -### The complete code so far +??? example "The complete code so far" -```html - - - - My experiment - - - - - - - + + + + + + + - -``` + timeline.push(test_procedure); + + /* start the experiment */ + jsPsych.init({ + timeline: timeline, + on_finish: function() { + jsPsych.data.displayData(); + } + }); + + + ``` -## Part 11: Data aggregation +## Part 13: Data aggregation -A new feature in jsPsych version 6.0 is a suite of data aggregation functions. You can now easily calculate things like mean response times for a selected set of trials. In this part, we'll use these functions to add a final trial to the experiment that tells the subject their accuracy and their mean response time for correct responses. +jsPsych provides a limited set of analysis functions to allow you to calculate things like mean response times for a selected set of trials. In this part, we'll use these functions to add a final trial to the experiment that tells the subject their accuracy and their mean response time for correct responses. -We'll use the text plugin. Because the actual text that we want to display changes based on the subject's performance in the experiment, we need to use a function for the `text` parameter and return the desired text. +We'll use the `html-keyboard-response` plugin. Because the text that we want to display changes based on the subject's performance in the experiment, we need to use a function for the `stimulus` parameter and return the desired text. + +Here's what the code looks like, and a description follows below. ```js var debrief_block = { type: "html-keyboard-response", stimulus: function() { - var trials = jsPsych.data.get().filter({test_part: 'test'}); + var trials = jsPsych.data.get().filter({task: 'response'}); var correct_trials = trials.filter({correct: true}); var accuracy = Math.round(correct_trials.count() / trials.count() * 100); var rt = Math.round(correct_trials.select('rt').mean()); - return "

You responded correctly on "+accuracy+"% of the trials.

"+ - "

Your average response time was "+rt+"ms.

"+ - "

Press any key to complete the experiment. Thank you!

"; + return `

You responded correctly on ${accuracy}% of the trials.

+

Your average response time was ${rt}ms.

+

Press any key to complete the experiment. Thank you!

`; } }; @@ -987,7 +1200,7 @@ var debrief_block = { timeline.push(debrief_block); ``` -To create the variable `trials`, we use `jsPsych.data.get()` which returns a jsPsych data collection containing all of the data from the experiment. We can then use `.filter` to select only the trials where `test_part` is `'test'` (a benefit of tagging the trials in part 9). `trials` contains all of the data from the trials where a circle was shown. +To create the variable `trials`, we use `jsPsych.data.get()` which returns a jsPsych data collection containing all of the data from the experiment. We can then use `.filter` to select only the trials where `task` is `'response'` (a benefit of tagging the trials in part 11). `trials` contains all of the data from the trials where a circle was shown. To get only the correct trials, we can use `.filter()` again to select only the trials from the `trials` data collection where the property `correct` is `true`. @@ -997,17 +1210,18 @@ Finally, to calculate the mean response time on correct trials, we use the `.sel ## The final code -This code is available in the examples folder in the jsPsych download. It is called `demo-simple-rt-task.html`. +This code is available in the `/examples` folder in the jsPsych release download. It is called `demo-simple-rt-task.html`. ```html My experiment - - - - + + + + + - - - - - - + + + + + - /* define welcome message trial */ - var welcome_block = { - type: "html-keyboard-response", - stimulus: "Welcome to the experiment. Press any key to begin." - }; - timeline.push(welcome_block); + + - var debrief_block = { - type: "html-keyboard-response", - stimulus: function() { - - var trials = jsPsych.data.get().filter({test_part: 'test'}); - var correct_trials = trials.filter({correct: true}); - var accuracy = Math.round(correct_trials.count() / trials.count() * 100); - var rt = Math.round(correct_trials.select('rt').mean()); - - return "

You responded correctly on "+accuracy+"% of the trials.

"+ - "

Your average response time was "+rt+"ms.

"+ - "

Press any key to complete the experiment. Thank you!

"; - - } - }; - timeline.push(debrief_block); - - /* start the experiment */ - jsPsych.init({ - timeline: timeline, - on_finish: function() { - jsPsych.data.displayData(); - } - }); - - + \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 4ca5f57c..d31999ed 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,6 +23,8 @@ markdown_extensions: guess_lang: false - toc: permalink: true + - pymdownx.superfences + - pymdownx.details repo_url: 'https://github.com/jspsych/jsPsych' repo_name: 'jspsych/jspsych' docs_dir: docs