# sketchpad

Current version: 1.0.3. [See version history](https://github.com/jspsych/jsPsych/blob/main/packages/plugin-sketchpad/CHANGELOG.md).

This plugin creates an interactive canvas that the participant can draw on using their mouse or touchscreen.
It can be used for sketching tasks, like asking the participant to draw a particular object.
It can also be used for some image segmentation or annotation tasks by setting the `background_image` parameter to render an image on the canvas.

The plugin stores a [base 64 data URL representation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) of the final image. 
This can be converted to an image file using [online tools](https://www.google.com/search?q=base64+image+decoder) or short programs in [R](https://stackoverflow.com/q/58604195/3726673), [python](https://stackoverflow.com/q/2323128/3726673), or another language of your choice. 
It also records all of the individual strokes that the participant made during the trial.

!!! warning
    This plugin generates **a lot** of data. Each trial can easily add 500kb+ of data to a final JSON output. 
    You can reduce the amount of data generated by turning off storage of the individual stroke data (`save_strokes: false`) or storage of the final image (`save_final_image: false`) if your use case doesn't require that information.
    If you are going to be collecting a lot of data with this plugin you may want to save your data to your server after each trial and not wait until the end of the experiment to perform a single bulk upload. 
    You can do this by putting data saving code inside the [`on_data_update` event handler](../overview/events.md#on_data_update).

## Parameters

In addition to the [parameters available in all plugins](../overview/plugins.md#parameters-available-in-all-plugins), this plugin accepts the following parameters. 


| Parameter          | Type            | Default Value | Description                              |
| ------------------ | --------------- | ------------- | ---------------------------------------- |
| canvas_shape | `"rectangle"` or `"circle"` | `"rectangle"` | The shape of the canvas element. |
| canvas_width | int | 500 | Width of the canvas in pixels when `canvas_shape` is `"rectangle"` |
| canvas_height | int | 500 | Height of the canvas in pixels when `canvas_shape` is `"rectangle"` |
| canvas_diameter | int | 500 | Diameter of the canvas in pixels when `canvas_shape` is `"circle"` |
| canvas_border_width | int | 0 | Width of the canvas border |
| canvas_border_color | string | `"#000"` | Color of the canvas border |
| background_image | image path | `null` | Path to an image to render as the background of the canvas |
| background_color | string | `"#fff"` | Color of the canvas background. Note that a `background_image` will render on top of the color.
| stroke_width | int | 2 | Width of the stroke on the canvas |
| stroke_color | string | `"#000"` | Color of the stroke on the canvas |
| stroke_color_palette | array of strings | `[]` | Array of colors to render as a palette of choices for stroke color. Clicking on the corresponding color button will change the stroke color. |
| prompt | string | null | HTML content to render on the screen.
| prompt_location | `"abovecanvas"` or `"belowcanvas"` or `"belowbutton"` | `"abovecanvas"` | The location to render the prompt content. |
| save_final_image | bool | true | Whether to save the final image in the data as a base64 encoded data URL. |
| save_strokes | bool | true | Whether to save the individual stroke data that generated the final image. |
| key_to_draw | key string | null | If this key is held down then it is like the mouse button being held down. The "ink" will flow when the button is held and stop when it is lifted. Pass in the string representation of the key, e.g., `'a'` for the A key or `' '` for the spacebar. |
| show_finished_button | bool | true | Whether to show the button that ends the trial. |
| finished_button_label | string | `"Finished"` | The label for the button that ends the trial. |
| show_clear_button | bool | true | Whether to show the button that clears the entire drawing. |
| clear_button_label | string | `"Clear"` | The label for the button that clears the entire drawing. |
| show_undo_button | bool | true | Whether to show the button that enables an undo action. |
| undo_button_label | string | `"Undo"` | The label for the button that enables an undo action. |
| show_redo_button | bool | true | Whether to show the button that enables a redo action. Note that `show_undo_button` must be `true` for the redo button to show up. |
| redo_button_label | string | `"Redo"` | The label for the button that enables a redo action. |
| choices | array of keys | `"NO_KEYS"` | This array contains the key(s) that the participant is allowed to press in order to end the trial. Keys should be specified as characters (e.g., `'a'`, `'q'`, `' '`, `'Enter'`, `'ArrowDown'`) - see [this page](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) and [this page (event.key column)](https://www.freecodecamp.org/news/javascript-keycode-list-keypress-event-key-codes/) for more examples. Any key presses that are not listed in the array will be ignored. The default value of `"NO_KEYS"` means that no keys will be accepted as valid responses. Specifying `"ALL_KEYS"` will mean that all responses are allowed. |
| trial_duration | int | null | Length of time before the trial ends. If `null` the trial will continue indefinitely (until another way of ending the trial occurs). |
| show_countdown_trial_duration | bool | false | Whether to show a timer that counts down until the end of the trial when `trial_duration` is not `null`. |
| countdown_timer_html | string | `'<span id="sketchpad-timer"></span> remaining'` | The HTML to use for rendering the countdown timer. The element with `id="sketchpad-timer"` will have its content replaced by a countdown timer in the format `MM:SS`.

## Data Generated

In addition to the [default data collected by all plugins](../overview/plugins.md#data-collected-by-all-plugins), this plugin collects the following data for each trial.

| Name           | Type        | Value                                    |
| -------------- | ----------- | ---------------------------------------- |
| rt | int | The length of time from the start of the trial to the end of the trial.
| response | string | If the trial was ended by clicking the finished button, then `"button"`. If the trial was ended by pressing a key, then the key that was pressed. If the trial timed out, then `null`. |
| png | base64 data URL string | If `save_image` is true, then this will contain the base64 encoded data URL for the image, in png format. |
| strokes | array of stroke objects | If `save_strokes` is true, then this will contain an array of stroke objects. Objects have an `action` property that is either `"start"`, `"move"`, or `"end"`. If `action` is `"start"` or `"move"` it will have an `x` and `y` property that report the coordinates of the action relative to the upper-left corner of the canvas. If `action` is `"start"` then the object will also have a `t` and `color` property, specifying the time of the action relative to the onset of the trial (ms) and the color of the stroke. If `action` is `"end"` then it will only have a `t` property. |

## Simulation Mode

This plugin does not yet support [simulation mode](../overview/simulation.md).

## Install

Using the CDN-hosted JavaScript file:

```js
<script src="https://unpkg.com/@jspsych/plugin-sketchpad@1.0.3"></script>
```

Using the JavaScript file downloaded from a GitHub release dist archive:

```js
<script src="jspsych/plugin-sketchpad.js"></script>
```

Using NPM:

```
npm install @jspsych/plugin-sketchpad
```
```js
import sketchpad from '@jspsych/plugin-sketchpad';
```

## Examples

???+ example "Basic sketchpad with a prompt"
    === "Code"

        ```javascript
        var trial = {
          type: jsPsychSketchpad,
          prompt: '<p>Draw an apple!</p>',
          prompt_location: 'abovecanvas',
          canvas_width: 300,
          canvas_height: 300,
          canvas_border_width: 2
        }
        ```
    
    === "Demo"
        <div style="text-align:center;">
          <iframe src="../../demos/jspsych-sketchpad-demo1.html" width="90%;" height="500px;" frameBorder="0"></iframe>
        </div>

    <a target="_blank" rel="noopener noreferrer" href="../../demos/jspsych-sketchpad-demo1.html">Open demo in new tab</a>

???+ example "Image segmentation with different colors"
    === "Code"

        ```javascript
        var trial = {
          type: jsPsychSketchpad,
          prompt: '<p style="width:380px">Circle the mouth using red. Circle the eyes using blue.</p>',
          prompt_location: 'abovecanvas',
          stroke_color_palette: ['red', 'blue'],
          stroke_color: 'red',
          background_image: 'img/sad_face_4.jpg',
          canvas_width: 380,
          canvas_height: 252
        }
        ```

    === "Demo"
        <div style="text-align:center;">
          <iframe src="../../demos/jspsych-sketchpad-demo2.html" width="90%;" height="500px;" frameBorder="0"></iframe>
        </div>

    <a target="_blank" rel="noopener noreferrer" href="../../demos/jspsych-sketchpad-demo2.html">Open demo in new tab</a>

???+ example "Draw an image in a time limit, then display the image and ask for a label."
    === "Code"

        ```javascript
        var draw = {
          type: jsPsychSketchpad,
          prompt: '<p>Draw the first animal that comes to mind. You have 30 seconds!</p>',
          prompt_location: 'belowcanvas',
          trial_duration: 30000,
          show_countdown_trial_duration: true,
        }

        var label = {
          type: jsPsychSurveyText,
          preamble: () => {
            var imageData = jsPsych.data.get().last(1).values()[0].png;
            return `<img src="${imageData}"></img>`;
          },
          questions: [
            {prompt: 'What animal did you draw?'}
          ]
        }
        ```

    === "Demo"
        <div style="text-align:center;">
          <iframe src="../../demos/jspsych-sketchpad-demo3.html" width="90%;" height="500px;" frameBorder="0"></iframe>
        </div>

    <a target="_blank" rel="noopener noreferrer" href="../../demos/jspsych-sketchpad-demo3.html">Open demo in new tab</a>
