python
This commit is contained in:
Andreas Wilms
2025-09-08 16:25:55 +02:00
commit 78481ca337
617 changed files with 345831 additions and 0 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
PCSurvey/potree/pointclouds

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
PCSurvey/potree/pointclouds
*.tar

BIN
Auswertung/.DS_Store vendored Normal file

Binary file not shown.

276
Auswertung/controller.py Normal file
View File

@@ -0,0 +1,276 @@
import data
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
import json
path_main: str = os.getcwd()
path_main = path_main.replace("\\", "/")
path_export = path_main + "/results_incentive_sizes"
path_export_count = path_main + "/results_incentive_amount"
path_export_no_emphasis = path_main + "/results_no_emphasis"
# Import paths for the different studies
folder_surveydata_basic: str = "surveydata_archive/surveydata_basic"
path_surveydata_basic: str = path_main + "/" + folder_surveydata_basic
folder_surveydata_basic_element_large: str = "surveydata_archive/surveydata_basic_element_large"
path_surveydata_large: str = path_main + "/" + folder_surveydata_basic_element_large
folder_surveydata_basic_element_small: str = "surveydata_archive/surveydata_basic_element_small"
path_surveydata_small: str = path_main + "/" + folder_surveydata_basic_element_small
folder_surveydata_basic_2Elements: str = "surveydata_archive/surveydata_basic_2_elements"
path_surveydata_basic_2Elements: str = path_main + "/" + folder_surveydata_basic_2Elements
folder_surveydata_basic_3Elements: str = "surveydata_archive/surveydata_basic_3_elements"
path_surveydata_basic_3Elements: str = path_main + "/" + folder_surveydata_basic_3Elements
folder_surveydata_basic_no_emphasis: str = "surveydata_archive/surveydata_basic_no_emphasis"
path_surveydata_basic_no_emphasis: str = path_main + "/" + folder_surveydata_basic_no_emphasis
# Parameters for the analysis
max_blurs: int = 4
max_wrong_elements: int = 3
positions_bremen = [[13, 13.5, 17], [31, 50, 2], [-65, -7, 9], [-40, -68, 3], [-35, 64, 4], [-5, 76, 1], [-18, 30, 0],[-15, -27, 0]]
positions_randersacker = [[-6, -7, 0.6],[-4.5, -18, 4],[-13, -33, -0.5],[-6, -14.5, -0.5], [8, -0.4, 1], [1.7, -8, 7.3]]
# Functions for cleaning the data - only leaves first entry of a worker across all studies
def clean_all_multiple_workers_in_order():
workers= []
objs = data.get_eval_blocks(path_surveydata_basic)
for obj in objs:
workers.append(obj["worker"])
def delete_double_workers(path_surveydata, workers):
double_workers = []
objs = data.get_eval_blocks(path_surveydata)
for obj in objs:
if obj["worker"] in workers:
double_workers.append(obj["worker"])
else:
workers.append(obj["worker"])
#iterate folder and delete all files with double workers
for file in os.listdir(path_surveydata):
if file.endswith(".json"):
with open(path_surveydata + "/" + file, 'r', encoding='utf-8') as f:
d = json.load(f)
if d[len(d)-1].get('worker') in double_workers:
os.remove(path_surveydata + "/" + file)
# order in which studies were conducted so that the first entry is kept
delete_double_workers(path_surveydata_large, workers)
delete_double_workers(path_surveydata_small, workers)
delete_double_workers(path_surveydata_basic_2Elements, workers)
delete_double_workers(path_surveydata_basic_3Elements, workers)
delete_double_workers(path_surveydata_basic_no_emphasis, workers)
# Function which bundles the creation of the CSV files and diagrams for the chapter "Incentive Sizes"
def build_csv_heatmaps_diagrams_chapter_incentive_sizes(path_small, path_medium, path_large, positions_bremen, positions_randersacker, max_blurs, max_wrong_elements, export_path):
# Load survey data from the three different studies (small, medium, large)
study_data_small = data.get_eval_blocks(path_small)
study_data_medium = data.get_eval_blocks(path_medium)
study_data_large = data.get_eval_blocks(path_large)
# Filter successful blocks for each study
study_data_small_succesfull = data.get_successful_blocks(study_data_small, max_blurs, max_wrong_elements)
study_data_medium_succesfull = data.get_successful_blocks(study_data_medium, max_blurs, max_wrong_elements)
study_data_large_succesfull = data.get_successful_blocks(study_data_large, max_blurs, max_wrong_elements)
# Build CSV files for general user statistics and user statistics for each study
data.build_csv_general_user_statistics(study_data_small, max_blurs, max_wrong_elements, "general_user_stats_small",export_path)
data.build_csv_general_user_statistics(study_data_medium, max_blurs, max_wrong_elements, "general_user_stats_medium",export_path)
data.build_csv_general_user_statistics(study_data_large, max_blurs, max_wrong_elements, "general_user_stats_large",export_path)
data.build_csv_user_statistics(study_data_small, max_blurs, max_wrong_elements, "user_stats_small",export_path)
data.build_csv_user_statistics(study_data_medium, max_blurs, max_wrong_elements, "user_stats_medium",export_path)
data.build_csv_user_statistics(study_data_large, max_blurs, max_wrong_elements, "user_stats_large",export_path)
# Filter data for Bremen and Randersacker and its x-y coordinates for each study use successful blocks
bremen_blocks_small = data.get_bremen_blocks(study_data_small_succesfull)
rander_blocks_small = data.get_randersacker_blocks(study_data_small_succesfull)
xb_s, yb_s = data.filter_x_y_from_blocks(bremen_blocks_small)
xr_s, yr_s = data.filter_x_y_from_blocks(rander_blocks_small)
bremen_blocks_medium = data.get_bremen_blocks(study_data_medium_succesfull)
rander_blocks_medium = data.get_randersacker_blocks(study_data_medium_succesfull)
xb_m, yb_m = data.filter_x_y_from_blocks(bremen_blocks_medium)
xr_m, yr_m = data.filter_x_y_from_blocks(rander_blocks_medium)
bremen_blocks_large = data.get_bremen_blocks(study_data_large_succesfull)
rander_blocks_large = data.get_randersacker_blocks(study_data_large_succesfull)
xb_l, yb_l = data.filter_x_y_from_blocks(bremen_blocks_large)
xr_l, yr_l = data.filter_x_y_from_blocks(rander_blocks_large)
# Build triple heatmap for Bremen for each size (small, medium, large)
data.plot_triple_heatmap_bremen(xb_s, yb_s, xb_m, yb_m, xb_l, yb_l, export_path, "triple_heatmap_bremen")
# Build triple heatmap for Randersacker for each size (small, medium, large)
data.plot_triple_heatmap_randersacker(xr_s, yr_s, xr_m, yr_m, xr_l, yr_l, export_path, "triple_heatmap_randersacker")
# Build multiple heatmaps by position for Bremen and Randersacker for each size (small, medium, large)
data.plot_multiple_heatmaps_bremen(bremen_blocks_small, positions_bremen, export_path, "heatmap_bremen_multiple_small")
data.plot_multiple_heatmaps_bremen(bremen_blocks_medium, positions_bremen, export_path, "heatmap_bremen_multiple_medium")
data.plot_multiple_heatmaps_bremen(bremen_blocks_large, positions_bremen, export_path, "heatmap_bremen_multiple_large")
data.plot_multiple_heatmaps_rander(rander_blocks_small, positions_randersacker, export_path, "heatmap_randersacker_multiple_small")
data.plot_multiple_heatmaps_rander(rander_blocks_medium, positions_randersacker, export_path, "heatmap_randersacker_multiple_medium")
data.plot_multiple_heatmaps_rander(rander_blocks_large, positions_randersacker, export_path, "heatmap_randersacker_multiple_large")
# Build CSV files for elements statistics for Bremen and Randersacker for each size (small, medium, large)
# Build diagram based on the elements statistics for Bremen and Randersacker for each size (small, medium, large)
small_by_tabrule = data.get_successful_blocks(study_data_small, max_blurs, max_wrong_elements, "true")
medium_by_tabrule = data.get_successful_blocks(study_data_medium, max_blurs, max_wrong_elements, "true")
large_by_tabrule = data.get_successful_blocks(study_data_large, max_blurs, max_wrong_elements, "true")
bremen_blocks_all_small = data.get_bremen_blocks(small_by_tabrule)
bremen_blocks_all_medium = data.get_bremen_blocks(medium_by_tabrule)
bremen_blocks_all_large = data.get_bremen_blocks(large_by_tabrule)
rander_blocks_all_small = data.get_randersacker_blocks(small_by_tabrule)
rander_blocks_all_medium = data.get_randersacker_blocks(medium_by_tabrule)
rander_blocks_all_large = data.get_randersacker_blocks(large_by_tabrule)
elements_stats_b_small = data.create_csv_elements_bremen(bremen_blocks_all_small, positions_bremen,export_path, "bremen_elements_small")
elements_stats_b_medium = data.create_csv_elements_bremen(bremen_blocks_all_medium, positions_bremen,export_path, "bremen_elements_medium")
elements_stats_b_large = data.create_csv_elements_bremen(bremen_blocks_all_large, positions_bremen,export_path, "bremen_elements_large")
element_stats_r_small = data.create_csv_elements_randersacker(rander_blocks_all_small, positions_randersacker,export_path, "randersacker_elements_small")
element_stats_r_medium = data.create_csv_elements_randersacker(rander_blocks_all_medium, positions_randersacker,export_path, "randersacker_elements_medium")
element_stats_r_large = data.create_csv_elements_randersacker(rander_blocks_all_large, positions_randersacker,export_path, "randersacker_elements_large")
data.plot_sucessrate_by_element_pos_size(elements_stats_b_small, elements_stats_b_medium, elements_stats_b_large, export_path,"bremen")
data.plot_sucessrate_by_element_pos_size(element_stats_r_small, element_stats_r_medium, element_stats_r_large, export_path,"randersacker")
# Build diagram based on the QoE ACR rating for Bremen and Randersacker for each size (small, medium, large)
data.plot_qoe_acr_rating_bar(bremen_blocks_small, bremen_blocks_medium, bremen_blocks_large, "qoe_acr_rating_bremen", export_path)
data.plot_qoe_acr_rating_bar(rander_blocks_small, rander_blocks_medium, rander_blocks_large, "qoe_acr_rating_randersacker", export_path)
# Build csv movement stats
data.build_csv_movement_stats(study_data_small_succesfull, study_data_medium_succesfull,study_data_large_succesfull,"movement_stats",path_export)
# Build success rate by stimuli
data.build_csv_stimuli_success(study_data_small_succesfull, "stimuli_success_small", path_export)
data.build_csv_stimuli_success(study_data_medium_succesfull, "stimuli_success_medium", path_export)
data.build_csv_stimuli_success(study_data_large_succesfull, "stimuli_success_large", path_export)
# Plot average movement
data.plot_bar_avg_movement(study_data_small_succesfull, study_data_medium_succesfull, study_data_large_succesfull, "avg-movement" ,export_path)
# Function which bundles the creation of the CSV files and diagrams for the chapter "Incentive Quantities"
def build_csv_heatmaps_diagrams_chapter_incentive_amount(path_one, path_two, path_three, positions_bremen, positions_randersacker, max_blurs, max_wrong_elements, export_path):
# Load survey data from the three different studies (one, two, three)
study_data_one = data.get_eval_blocks(path_one)
study_data_two = data.get_eval_blocks_2_elements(path_two)
study_data_three = data.get_eval_blocks_3_elements(path_three)
# Filter successful blocks for each study
study_data_one_succesfull = data.get_successful_blocks(study_data_one, max_blurs, max_wrong_elements)
study_data_two_succesfull = data.get_successful_blocks(study_data_two, max_blurs, max_wrong_elements)
study_data_three_succesfull = data.get_successful_blocks(study_data_three, max_blurs, max_wrong_elements)
# Build CSV files for general user statistics and user statistics for each study
data.build_csv_general_user_statistics(study_data_one, max_blurs, max_wrong_elements, "general_user_stats_one",export_path)
data.build_csv_general_user_statistics(study_data_two, max_blurs, max_wrong_elements, "general_user_stats_two",export_path)
data.build_csv_general_user_statistics(study_data_three, max_blurs, max_wrong_elements, "general_user_stats_three",export_path)
data.build_csv_user_statistics(study_data_one, max_blurs, max_wrong_elements, "user_stats_one",export_path)
data.build_csv_user_statistics(study_data_two, max_blurs, max_wrong_elements, "user_stats_two",export_path)
data.build_csv_user_statistics(study_data_three, max_blurs, max_wrong_elements, "user_stats_three",export_path)
# Filter data for Bremen and Randersacker and its x-y coordinates for each study use successful blocks
bremen_blocks_one = data.get_bremen_blocks(study_data_one_succesfull)
rander_blocks_one = data.get_randersacker_blocks(study_data_one_succesfull)
xb_1, yb_1 = data.filter_x_y_from_blocks(bremen_blocks_one)
xr_1, yr_1 = data.filter_x_y_from_blocks(rander_blocks_one)
bremen_blocks_two = data.get_bremen_blocks(study_data_two_succesfull)
rander_blocks_two = data.get_randersacker_blocks(study_data_two_succesfull)
xb_2, yb_2 = data.filter_x_y_from_blocks(bremen_blocks_two)
xr_2, yr_2 = data.filter_x_y_from_blocks(rander_blocks_two)
bremen_blocks_three = data.get_bremen_blocks(study_data_three_succesfull)
rander_blocks_three = data.get_randersacker_blocks(study_data_three_succesfull)
xb_3, yb_3 = data.filter_x_y_from_blocks(bremen_blocks_three)
xr_3, yr_3 = data.filter_x_y_from_blocks(rander_blocks_three)
# Build triple heatmap for Bremen for each quantity
data.plot_triple_heatmap_bremen(xb_1, yb_1, xb_2, yb_2, xb_3, yb_3, export_path, "triple_heatmap_bremen_amount", "count")
# Build triple heatmap for Randersacker for each quantity
data.plot_triple_heatmap_randersacker(xr_1, yr_1, xr_2, yr_2, xr_3, yr_3, export_path, "triple_heatmap_randersacker_amount", "count")
# Build diagram based on the QoE ACR rating for Bremen and Randersacker for each size (small, medium, large)
data.plot_qoe_acr_rating_bar(bremen_blocks_one, bremen_blocks_two, bremen_blocks_three, "qoe_acr_rating_bremen_amount", export_path, "count")
data.plot_qoe_acr_rating_bar(rander_blocks_one, rander_blocks_two, rander_blocks_three, "qoe_acr_rating_randersacker_amount", export_path, "count")
# Build diagram for the average movement for each quantity and stimulus
data.plot_bar_avg_movement(study_data_one_succesfull, study_data_two_succesfull, study_data_three_succesfull, "avg-movement" ,export_path, "count")
# Build csv movement stats
data.build_csv_movement_stats(study_data_one_succesfull, study_data_two_succesfull,study_data_three_succesfull,"movement_stats",path_export_count, "count")
# success rate by position permutation and appearances
data_two_filtered_tab = data.get_successful_blocks(study_data_two, max_blurs, max_wrong_elements, "true")
data_three_filtered_tab = data.get_successful_blocks(study_data_three, max_blurs, max_wrong_elements, "true")
data.build_csv_positions_permutations(data_two_filtered_tab, positions_bremen, positions_randersacker ,"positions_permutations_two", export_path)
data.build_csv_positions_permutations(data_three_filtered_tab, positions_bremen, positions_randersacker,"positions_permutations_three", export_path, 3)
# Function which bundles the creation of the CSV files and diagrams for the chapter "Emphasis and wording influence"
def build_diagrams_no_emphasis_chapter(path_surveydata_no_emphasis, path_basic_survey_data, export_path, max_blurs, max_wrong_elements):
# Load survey data from the emphasis and no emphasis study
study_data_no_emphasis = data.get_eval_blocks(path_surveydata_no_emphasis)
study_data = data.get_eval_blocks(path_basic_survey_data)
# Filter successful blocks for each study
study_data_ne_succesfull = data.get_successful_blocks(study_data_no_emphasis, max_blurs, max_wrong_elements)
study_data_succesfull = data.get_successful_blocks(study_data, max_blurs, max_wrong_elements)
bremen_blocks = data.get_bremen_blocks(study_data_succesfull)
bremen_blocks_ne = data.get_bremen_blocks(study_data_ne_succesfull)
rander_blocks = data.get_randersacker_blocks(study_data_succesfull)
rander_blocks_ne = data.get_randersacker_blocks(study_data_ne_succesfull)
xb, yb = data.filter_x_y_from_blocks(bremen_blocks)
xb_ne, yb_ne = data.filter_x_y_from_blocks(bremen_blocks_ne)
xr, yr = data.filter_x_y_from_blocks(rander_blocks)
xr_ne, yr_ne = data.filter_x_y_from_blocks(rander_blocks_ne)
# Build CSV files for general user statistics and user statistics for each study
data.build_csv_general_user_statistics(study_data_no_emphasis, max_blurs, max_wrong_elements, "general_user_stats_no_emphasis",export_path)
data.build_csv_user_statistics(study_data_no_emphasis, max_blurs, max_wrong_elements, "user_stats_no_emphasis",export_path)
# Build diagram based on the QoE ACR rating for Bremen and Randersacker for each condition
data.plot_qoe_acr_rating_two_inputs(bremen_blocks, bremen_blocks_ne, "qoe_acr_rating_no_emphasis_bremen", export_path, "emph")
data.plot_qoe_acr_rating_two_inputs(rander_blocks, rander_blocks_ne, "qoe_acr_rating_no_emphasis_randersacker", export_path, "emph")
# Build csv movement stats
data.build_csv_movement_stats(study_data_succesfull, study_data_ne_succesfull, study_data_ne_succesfull,"movement_stats",path_export_no_emphasis)
# Build success rate by stimuli for both conditions
data.plot_qoe_acr_rating_bar_emphasis(bremen_blocks, bremen_blocks_ne, "qoe_acr_rating_no_emphasis_bremen", export_path)
data.plot_qoe_acr_rating_bar_emphasis(rander_blocks, rander_blocks_ne, "qoe_acr_rating_no_emphasis_randersacker", export_path)
# Plot average movement for both conditions and stimuli
data.plot_bar_avg_emphais(study_data_succesfull, study_data_ne_succesfull, "bremen-avg-movement" ,export_path)
# Heatmaps for both conditions and stimuli
data.plot_double_heatmap_bremen(xb_ne, yb_ne,xb, yb, export_path, "emphasis_heatmap_bremen")
data.plot_double_heatmap_randersacker(xr_ne, yr_ne, xr, yr, export_path, "emphasis_heatmap_randersacker")
# First clean data to only have one entry per worker
clean_all_multiple_workers_in_order()
# Call the functions to build the CSV files and diagrams for the different chapters
build_csv_heatmaps_diagrams_chapter_incentive_sizes(path_surveydata_small, path_surveydata_basic, path_surveydata_large, positions_bremen, positions_randersacker, max_blurs, max_wrong_elements, path_export)
build_csv_heatmaps_diagrams_chapter_incentive_amount(path_surveydata_basic, path_surveydata_basic_2Elements, path_surveydata_basic_3Elements, positions_bremen, positions_randersacker, max_blurs, max_wrong_elements, path_export_count)
build_diagrams_no_emphasis_chapter(path_surveydata_basic_no_emphasis, path_surveydata_basic,path_export_no_emphasis, max_blurs, max_wrong_elements)

2118
Auswertung/data.py Normal file

File diff suppressed because it is too large Load Diff

48
DOC.md Normal file
View File

@@ -0,0 +1,48 @@
# Interactive Point Cloud Study
This project is the implementation of a point cloud study on QoE described in my Bachelors thesis.
### Brief Summary of Study Flow:
- User and task instructions
- Trial run
- Study begins
- End / Feedback / Code issuance
## Documentation
The folder **PCSurvey** contains all the code for the study. The folder **PCStatic** contains images and HTML files for the instruction and information phases of the study.
The files *survey.html* and *survey.js* serve as the entry point and logic for the study. The *survey.js* file constructs the study flow using jsPsych.
### The "potree" folder
It contains a folder **pointclouds**, which includes all the point clouds used for the study.
The file *paired_comparison_template.html* creates the interface for this evaluation condition. This interface was developed early on but ultimately was not used. It requires minor adaptations but is overall functional.
The files *sequentiell_template.html* and *sequentiell_template2.html* serve as the interfaces for the sequential evaluation method. We have two versions: one is the first, and the other is the second. It is necessary for them to be separate, even though they are very similar, due to the external HTML loading process in jsPsych.
The files *single_page_template_\*.html* are designated for each element and test condition (1, 2, and 3 elements in the cloud). Each stimulus varies in size and requires special configuration. These files manage those configurations.
All the mentioned files are used in the *survey.js* file to dynamically generate the evaluation block for the study.
## Deployment
The *docker-compose.yml* file provides an example configuration for starting the Docker container with the study. It builds the container using the *Dockerfile* and mounts the volumes for the point cloud folder and the output folder for the captured user data.
Configure mounting and ports based on your case.
```bash
docker compose up
```
After deployment, it can be accessed on:
http://localhost/survey.html?worker=0&campaign=0&randkey=0
The parameters are necessary to pass the requirement test for the study.
## Authors
- Andreas Wilms

36
Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
# Verwenden Sie das offizielle PHP-Bild mit Apache
FROM php:8.1-apache
# Aktivieren Sie Apache-Module
RUN a2enmod rewrite ssl
# Kopieren Sie die PHP-Anwendungsdateien in das richtige Verzeichnis
COPY PCSurvey/ /var/www/html/
# Kopieren Sie die SSL-Zertifikate in die richtigen Verzeichnisse
COPY server.pem /etc/ssl/certs/server.pem
COPY server.key /etc/ssl/private/server.key
# Kopieren Sie die Apache SSL-Konfigurationsdatei
COPY httpd-ssl.conf /etc/apache2/sites-available/default-ssl.conf
# Fügen Sie eine ServerName Direktive in der Apache-Konfigurationsdatei hinzu
RUN echo "ServerName netcom-survey2.informatik.uni-augsburg.de" >> /etc/apache2/apache2.conf
# Aktivieren Sie die Standard-SSL-Site
RUN a2ensite default-ssl
# Exponieren Sie die Ports 80 und 443
EXPOSE 80 443
# Stellen Sie sicher, dass die Berechtigungen für die Anwendungsdateien korrekt sind
RUN chown -R www-data:www-data /var/www/html
RUN chmod -R 755 /var/www/html
# Create the /Data directory and set permissions
RUN mkdir -p /Data
RUN chown -R www-data:www-data /Data
RUN chmod -R 755 /Data
# Starten Sie den Apache-Server
CMD ["apache2-foreground"]

BIN
PCSurvey/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,92 @@
@font-face {
font-family: 'Cardo';
src: url(https://jonathan-harrell.com/wp-content/themes/jonathanharrell/fonts/Cardo-Italic.woff) format('woff2');
}
#intro_text {
max-width: 40rem;
margin: 0 auto;
padding: 1rem;
text-align: left;
font-size: 16px;
}
section {
margin-top: 3rem;
}
#intro_text h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.2;
}
a {
text-decoration: none;
color: #2eec96;
}
hr {
height: 1px;
padding: 0;
border: 0;
background-color: #ccc;
}
.instructions ul,
.instructions ol {
margin: 0.075rem 0;
}
.instructions li {
margin-bottom: 0.25rem;
}
.instructions ul {
list-style: none;
padding-left: 50px;
margin: 0;
}
.instructions ul li {
position: relative;
padding-left: 1em;
}
.instructions ul li::before {
position: absolute;
left: 0;
content: '•';
}
.instructions ol {
padding-left: 1.2rem;
}
figure {
margin: 2rem 0;
padding: 1rem 0;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
#intro_text header h1 {
margin-top: 0;
text-align: center;
}
header p {
text-align: center;
}
p.thick {
font-weight: bold;
}
#consent {
margin-bottom: 50px
}

View File

@@ -0,0 +1,125 @@
/*
* CSS for jsPsych experiments.
*
* This stylesheet provides minimal styling to make jsPsych
* experiments look polished without any additional styles.
*/
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700);
/* Container holding jsPsych content */
.jspsych-display-element {
display: flex;
flex-direction: column;
overflow-y: auto;
}
.jspsych-display-element:focus {
outline: none;
}
.jspsych-content-wrapper {
display: flex;
margin: auto;
flex: 1 1 100%;
width: 100%;
}
.jspsych-content {
max-width: 95%; /* this is mainly an IE 10-11 fix */
text-align: center;
margin: auto; /* this is for overflowing content */
}
.jspsych-top {
align-items: flex-start;
}
.jspsych-middle {
align-items: center;
}
/* fonts and type */
.jspsych-display-element {
font-family: 'Open Sans', 'Arial', sans-serif;
font-size: 18px;
line-height: 1.6em;
}
/* Form elements like input fields and buttons */
.jspsych-display-element input[type="text"] {
font-family: 'Open Sans', 'Arial', sans-serif;
font-size: 14px;
}
/* borrowing Bootstrap style for btn elements, but combining styles a bit */
.jspsych-btn {
display: inline-block;
padding: 6px 12px;
margin: 0px;
font-size: 14px;
font-weight: 400;
font-family: 'Open Sans', 'Arial', sans-serif;
cursor: pointer;
line-height: 1.4;
text-align: center;
white-space: nowrap;
vertical-align: middle;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
color: #333;
background-color: #fff;
border-color: #ccc;
}
.jspsych-btn:hover {
background-color: #ddd;
border-color: #aaa;
}
.jspsych-btn:disabled {
background-color: #eee;
color: #aaa;
border-color: #ccc;
cursor: not-allowed;
}
/* jsPsych progress bar */
#jspsych-progressbar-container {
color: #555;
border-bottom: 1px solid #dedede;
background-color: #f9f9f9;
margin-bottom: 1em;
text-align: center;
padding: 8px 0px;
width: 100%;
line-height: 1em;
}
#jspsych-progressbar-container span {
font-size: 14px;
padding-right: 14px;
}
#jspsych-progressbar-outer {
background-color: #eee;
width: 50%;
margin: auto;
height: 14px;
display: inline-block;
vertical-align: middle;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
}
#jspsych-progressbar-inner {
background-color: #aaa;
width: 0%;
height: 100%;
}
/* Control appearance of jsPsych.data.displayData() */
#jspsych-data-display {
text-align: left;
}

View File

@@ -0,0 +1,187 @@
.clear_content {
all:initial;
}
.test {
display: none;
}
#end-trial {
}
#replay {
}
.container > div {
visibility:hidden;
}
.debug {
display:none;
background: #000;
color: #ff2277
}
.hidden {
visibility: hidden;
}
.object-fit_fill { object-fit: fill }
.object-fit_contain { object-fit: contain }
.object-fit_cover { object-fit: cover }
.object-fit_none { object-fit: none }
.object-fit_scale-down { object-fit: scale-down }
.work_container {
display: grid;
grid-template-rows: auto;
grid-template-columns: 188px auto 188px;
grid-template-areas:
"work1 work2 work3 "
}
.container {
font-family: "Courier New", Courier, serif;
display: grid;
grid-template-rows: 30px 100px 120px 40px 377px 377px 200px;
grid-template-columns: auto 565px 565px auto;
grid-gap: 20px 20px;
grid-template-areas:
". blank blank . "
". header_l header_r . "
". title_l title_r . "
". spacer spacer ."
". image1 image2 ."
". image3 image4 ."
". about work ."
}
.container_noimages {
font-family: "Courier New", Courier, serif;
display: grid;
grid-template-rows: 30px 100px 120px 40px 200px;
grid-template-columns: auto 565px 565px auto;
grid-gap: 20px 20px;
grid-template-areas:
". blank blank . "
". header_l header_r . "
". title_l title_r . "
". spacer spacer ."
". about work ."
}
.footer {
visibility: hidden;
font-family: "Courier New", Courier, serif;
display: grid;
grid-template-rows: 30px 100px 30px;
grid-template-columns: auto 585px 565px auto;
background: #001122;
grid-template-areas:
". . . . "
". footer_left footer_right . "
". . . . "
}
.blank {
grid-area: blank;
}
.spacer {
grid-area: spacer;
border-top: 1px solid #eee;
}
.left_header {
grid-area: header_l;
}
.right_header {
grid-area: header_r;
text-align: right;
font-size: 10px
}
.left_title {
grid-area: title_l;
}
.left_title {
font-size: 48px;
}
.right_title {
grid-area: title_r;
}
.img1{
grid-area: image1;
}
.img2{
grid-area: image2;
}
.img3 {
grid-area: image3;
}
.img4 {
grid-area: image4;
}
.worklist {
grid-area: work;
text-align: center;
}
.about {
grid-area: about;
}
.worklist.work1{
grid-area: work1;
display: inline-block;
}
.worklist.work2{
grid-area: work2;
display: inline-block;
}
.worklist.work3{
grid-area: work3;
display: inline-block;
}
.footer_left {
grid-area: footer_left;
color: #fff
}
.header {
background: #001122;
height: 130px
}
img[class] {
width: 100%;
}
.left_header > img {
width: 50px;
}
.smallimg {
height:48px;
padding-top: 20px;
padding-bottom: 10px
}
.experts {
font-size: 19px
}

View File

@@ -0,0 +1,259 @@
#currentscore, #total_current_score {
visibility:hidden;
}
.clear_content {
all:initial;
}
.test {
display: none;
}
#end-trial {
width: 110px;
height: 45px;
}
#infotext {
width: 100%;
}
#left-right{
width: 45%;
height: 20px;
}
#right-left{
width: 45%;
height: 20px;
}
#replay {
width: 100px;
height: 47px;
}
.container > div {
visibility:hidden;
}
.head > div {
visibility:hidden;
}
.footer > div {
visibility:hidden;
}
.debug {
display:none;
background: #000;
color: #ff2277
}
.hidden {
visibility: hidden;
}
.display_toggle {
display: none;
}
.object-fit_fill { object-fit: fill }
.object-fit_contain { object-fit: contain }
.object-fit_cover { object-fit: cover }
.object-fit_none { object-fit: none }
.object-fit_scale-down { object-fit: scale-down }
.work_container {
display: grid;
grid-template-rows: auto;
grid-template-columns: 120px 200px 200px 200px;
grid-template-areas:
" . work1 work2 work3 "
}
.head {
visibility: hidden;
font-family: "Courier New", Courier, serif;
display: grid;
/* grid-template-rows: 30px 100px 120px 40px 377px 377px 250 1fr; */
grid-template-columns: auto 350px 350px 350px auto;
grid-gap: 20px 20px;
grid-template-areas:
". blank blank blank . "
". header_l header_l header_r . "
". title_l title_l title_r . "
". spacer spacer spacer ."
}
.container {
align-items: center;
font-family: "Courier New", Courier, serif;
display: grid;
/* grid-template-rows: 30px 100px 120px 40px 377px 377px 250 1fr; */
grid-template-columns: auto 350px 350px 350px auto;
grid-gap: 20px 20px;
grid-template-areas:
/* ". blank blank blank . "
". header_l header_l header_r . "
". title_l title_l title_r . "
". spacer spacer spacer ." */
". text1 image1 image2 ."
". image3 image4 text2 ."
". image5 text3 image6 ."
". about work work ."
". hfh hfh hfh ."
}
.footer {
align-items: left;
visibility: hidden;
font-family: "Courier New", Courier, serif;
display: grid;
grid-template-rows: 30px 100px 30px;
grid-gap: 20px 20px;
grid-template-columns: auto 350px 350px 350px auto;
background: #001122;
grid-template-areas:
". . . . . "
". footer_left footer_left footer_right . "
". . . . . "
}
.blank {
grid-area: blank;
}
.spacer {
grid-area: spacer;
border-top: 1px solid #eee;
height: 30px
}
.text1 {
grid-area: text1;
}
.text2 {
grid-area: text2;
}
.text3 {
grid-area: text3;
}
.left_header {
grid-area: header_l;
}
.right_header {
grid-area: header_r;
text-align: right;
font-size: 10px
}
.left_title {
grid-area: title_l;
}
.left_title {
font-size: 48px;
}
.right_title {
grid-area: title_r;
}
.img1{
grid-area: image1;
}
.img2{
grid-area: image2;
}
.img3 {
grid-area: image3;
}
.img4 {
grid-area: image4;
}
.img5 {
grid-area: image5;
}
.img6 {
grid-area: image6;
}
.worklist {
grid-area: work;
text-align: center;
}
.about {
grid-area: about;
}
.work1{
grid-area: work1;
display: inline-block
}
.work2{
grid-area: work2;
display: inline-block;
}
.work3{
grid-area: work3;
display: inline-block;
}
.footer_left {
grid-area: footer_left;
color: #fff
}
.header {
background: #001122;
height: 130px
}
.test img {
width: 100%;
}
.left_header > img {
width: 50px;
}
.smallimg {
height:48px;
padding-top: 20px;
padding-bottom: 10px
}
.experts {
font-size: 19px
}
h4 {
font-size: 19px
}
#screenshot {
position: auto;
}
#slider {
width: 40%;
height: auto;
padding-top: 20px;
padding-bottom: 20px
}

View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
button {
display: block;
width: 20%;
padding: 10px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
}
section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
button:not(:disabled):hover {
background: #5d0570;
}
div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div>
<section>
<h1 id="caption">Incorrect! You selected the wrong letter.</h1>
<h3 id="info-text">Don't worry, you can try again!</h3>
</section>
<button id="end-trial">Restart</button>
</div>
</body>
<script>
var responses = JSON.parse(
jsPsych.data.get().last(1).select("responses").values
);
var controlLetter = jsPsych.data.get().last(1).select("controlLetter1")
.values[0];
var controlLetter2 = jsPsych.data.get().last(1).select("controlLetter2")
.values[0];
var controlLetter3 = jsPsych.data.get().last(1).select("controlLetter3")
.values[0];
if (responses.Q1 == controlLetter && responses.Q2 == controlLetter2 && responses.Q3 == controlLetter3) {
document.getElementById("caption").innerText = "Correct!";
document.getElementById("info-text").innerText =
"While searching for control letters, remember that rating the cloud quality is the top priority.";
document.getElementById("end-trial").innerText = "Let's go!";
} else {
document.getElementById("caption").innerText =
"Incorrect! You selected the wrong letter.";
document.getElementById("info-text").innerText =
"Don't worry, you can try again!";
document.getElementById("end-trial").innerText = "Restart";
}
</script>
</html>

View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
button {
display: block;
width: 20%;
padding: 10px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
}
section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
button:not(:disabled):hover {
background: #5d0570;
}
div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div>
<section>
<h1 id="caption">Incorrect! You selected the wrong letter.</h1>
<h3 id="info-text">Don't worry, you can try again!</h3>
</section>
<button id="end-trial">Restart</button>
</div>
</body>
<script>
var responses = JSON.parse(
jsPsych.data.get().last(1).select("responses").values
);
var controlLetter = jsPsych.data.get().last(1).select("controlLetter1")
.values[0];
if (responses.Q1 == controlLetter) {
document.getElementById("caption").innerText = "Correct!";
document.getElementById("info-text").innerText =
"You selected the correct letter! Now you can proceed to the study.";
document.getElementById("end-trial").innerText = "Let's go!";
} else {
document.getElementById("caption").innerText =
"Incorrect! You selected the wrong letter.";
document.getElementById("info-text").innerText =
"Don't worry, you can try again!";
document.getElementById("end-trial").innerText = "Restart";
}
</script>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<!-- <link rel="stylesheet" href="https://use.typekit.net/hpq3bof.css">
<link href="https://fonts.googleapis.com/css?family=Lancelot" rel="stylesheet"> -->
</head>
<body>
<div id="intro_text">
<header>
<h1>Test Instructions</h1>
</header>
<main>
<section>
<h2>Your Task</h2>
<p>You will first see a picture in original quality (left).</p>
<div>
<img id="Bsp1" style="width:420px;height:280px;margin:10px" src="PCstatic/img/Text/Text-OR-G-E.png" />
<img id="Bsp2" style="width:420px;height:280px;margin:10px" src="PCstatic/img/Text/Text-OC-V8-G-H.png" />
</div>
<p>After that you see a picture of a reduced quality (right).</p>
<p>Your Task:</p>
<ul>
<li>Rate the quality of the picture.</li>
<ul>
<li><b>Good quality</b> is given if the surfaces of objects are smooth and easily recognisable.</li>
<li>A picture of <b>bad quality</b> has less recognisable surfaces.</li>
</ul>
<li>Rate the difference in quality between the original picture and the reduced picture.</li>
<li>In addition on each picture there will be a red letter in the top right corner. Memorize it and Tick/Check the corresponding box your rating.</li>
</ul>
<p></p>
<p>Further informations on the topic are at the end of the survey.</p>
</section>
</main>
</div>
<button type="jspsych-btn" id="consent">I agree to take part in this study.</button>
</body>
</html>
<!-- Cache served by Simple Cache - Last modified: Thu, 05 Jul 2018 02:27:42 GMT -->

View File

@@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Point Clouds and VR: Research Introduction</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f8f8f8;
color: #333;
}
section {
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
margin-bottom: 20px;
color: #333;
text-align: center;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
h2 {
font-size: 20px;
color: #444;
margin-top: 20px;
}
p {
margin-bottom: 10px;
}
img {
border: 1px solid #ccc;
display: block;
margin: 20px auto;
max-width: 100%;
height: auto;
}
button {
display: block;
width: 20%;
padding: 10px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
margin-bottom: 50px;
}
button:disabled {
background: #ccc;
color: #666;
cursor: not-allowed;
}
button:not(:disabled):hover {
background: #5d0570;
}
div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.highlight {
background-color: #f0f0f0;
border-left: 4px solid #4a4a4a;
padding: 10px;
margin: 10px 0;
}
.important-note {
background-color: #e6e6e6;
border: 1px solid #4a4a4a;
border-radius: 3px;
padding: 15px;
margin-top: 20px;
font-weight: bold;
text-align: center;
}
</style>
</head>
<body>
<div>
<section>
<h1>Point Clouds and Virtual Reality: Essential Research Introduction</h1>
<div class="important-note">
Please read the following introduction as it can be helpful for understanding the research.
</div>
<h2>What is a Point Cloud?</h2>
<p>A point cloud is a set of data points in 3D space, typically representing the external surface of an object or environment. Each point has X, Y, and Z coordinates and may include additional information like color or intensity.</p>
<div class="highlight">
<strong>Key Features of Point Clouds:</strong>
<ul>
<li>Detailed 3D representation of objects and environments</li>
<li>Created using 3D scanners or LiDAR technology</li>
<li>Can contain millions of points for high-resolution models</li>
</ul>
</div>
<img src="PCstatic/img/pointcloud_example.png" alt="Point Cloud Visualization">
<h2>Connection to Virtual Reality (VR)</h2>
<p>Point clouds play a crucial role in creating immersive VR experiences by bridging the gap between physical and virtual worlds.</p>
<div class="highlight">
<strong>How Point Clouds Enhance VR:</strong>
<ul>
<li>Create detailed and accurate 3D models of real-world environments</li>
<li>Enable the generation of immersive VR experiences</li>
<li>Allow users to explore digitally recreated spaces</li>
<li>Provide a data-driven foundation for realistic environmental representations</li>
</ul>
</div>
<p>By utilizing point clouds in VR, researchers and developers can create highly realistic and interactive virtual environments, revolutionizing fields such as architecture, gaming, virtual tourism, and more.</p>
<p>Understanding these concepts is essential for delving into advanced research topics in 3D visualization, computer vision, and virtual reality applications.</p>
</section>
</div>
<button type="button" id="consent" disabled="true"></button>
</body>
<script>
var timerElement = document.getElementById('consent');
var timeLeft = 10;
var countdown = setInterval(() => {
if (timeLeft <= 0) {
clearInterval(countdown);
timerElement.textContent = "Continue";
timerElement.disabled = false;
} else {
timerElement.textContent = timeLeft;
timeLeft--;
}
}, 1000);
</script>
</html>

View File

@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Instructions</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: left;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
p {
margin-bottom: 10px;
}
img {
border: 2px solid black;
display: block;
margin: 20px auto;
max-width: 100%;
height: auto;
}
button {
display: block;
width: 20%;
padding: 10px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 20px auto; /* Zentriert den Button */
margin-bottom: 50px;
}
button:disabled {
background: #ccc;
color: #666;
cursor: not-allowed;
}
button:not(:disabled):hover {
background: #5d0570;
}
.highlight {
background-color: #f9e6f8;
border-left: 4px solid #bc0ba8;
padding: 10px;
margin: 15px 0;
}
.step {
background-color: #f9f9f9;
border: 1px solid #e0e0e0;
padding: 10px;
margin-bottom: 10px;
}
.step-number {
font-weight: bold;
color: #bc0ba8;
margin-right: 5px;
}
</style>
</head>
<body>
<div>
<section>
<h1>Research Experiment: Point Cloud Quality Evaluation</h1>
<div class="highlight">
<p><strong>Key Focus:</strong> Evaluate the <strong>QUALITY</strong> of point clouds in virtual reality environments.</p>
</div>
<h2>Experiment Structure</h2>
<p>You will participate in 4 blocks of evaluations. Each block consists of the following:</p>
<ol>
<li>Viewing and evaluating an initial point cloud</li>
<li>Viewing and evaluating a second point cloud</li>
<li>Rating the difference between the two point clouds</li>
</ol>
<h2>Your Task</h2>
<div class="step">
<span class="step-number">1.</span>
<span>You will be immersed in a point cloud environment.</span>
</div>
<div class="step">
<span class="step-number">2.</span>
<span>Explore the point cloud to familiarize yourself with it for later evaluation.</span>
</div>
<img width="640" height="360" src="PCstatic/img/pointcloud.png" alt="Point Cloud Image">
<div class="step">
<span class="step-number">3.</span>
<span>As a control measure, there are <b>three letters/numbers</b> hidden in the cloud which you must identify. You will be asked about it later during the evaluation.</span>
</div>
<img width="640" height="360" src="PCstatic/img/letter_pointcloud.png" alt="Letter Point Cloud Image">
<div class="step">
<span class="step-number">4.</span>
<span>Please rate the <b>quality</b> of the point cloud on a scale and enter the corresponding control <b>letters/numbers</b> that you observed.</span>
</div>
<img width="680" height="360" src="PCstatic/img/seq_1.png" alt="Rating Scale Image">
<div class="step">
<span class="step-number">5.</span>
<span>You will then experience a second point cloud.</span>
</div>
<div class="step">
<span class="step-number">6.</span>
<span>Repeat steps 2 and 3 for the new cloud, then rate its <b>quality</b>, enter the control <b>letters/numbers</b> from the cloud, and <b>assess the difference in quality</b> compared to the previous one.</span>
</div>
<img width="640" height="360" src="PCstatic/img/seq_2.png" alt="Different Compression Settings Image">
<div class="highlight">
<p><strong>Important:</strong> While finding the hidden letters/numbers is part of the mechanism to validate the interaction, the primary focus is on evaluating the overall quality of the point cloud.</p>
</div>
<p>This sequence of two point clouds and their evaluations will be repeated 4 times (4 blocks).</p>
<p>After completing all blocks, you will have the opportunity to provide feedback, which we greatly appreciate for further improvement of our research.</p>
<p>To help you familiarize yourself with the survey, you will complete a trial run beforehand.</p>
</section>
</div>
<button type="button" id="consent" disabled="true"></button>
</body>
<script>
var timerElement = document.getElementById('consent');
var timeLeft = 10;
var countdown = setInterval(() => {
if (timeLeft <= 0) {
clearInterval(countdown);
timerElement.textContent = "I Agree";
timerElement.disabled = false;
} else {
timerElement.textContent = timeLeft;
timeLeft--;
}
}, 1000);
</script>
</html>

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Instructions</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: left;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
p {
margin-bottom: 10px;
}
img {
border: 2px solid black;
display: block;
margin: 20px auto;
max-width: 100%;
height: auto;
}
button {
display: block;
width: 20%;
padding: 10px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 20px auto; /* Zentriert den Button */
margin-bottom: 50px;
}
button:disabled {
background: #ccc;
color: #666;
cursor: not-allowed;
}
button:not(:disabled):hover {
background: #5d0570;
}
.highlight {
background-color: #f9e6f8;
border-left: 4px solid #bc0ba8;
padding: 10px;
margin: 15px 0;
}
.step {
background-color: #f9f9f9;
border: 1px solid #e0e0e0;
padding: 10px;
margin-bottom: 10px;
}
.step-number {
font-weight: bold;
color: #bc0ba8;
margin-right: 5px;
}
</style>
</head>
<body>
<div>
<section>
<h1>Research Experiment: Point Cloud Quality Evaluation</h1>
<div class="highlight">
<p><strong>Key Focus:</strong> Evaluate the <strong>QUALITY</strong> of point clouds in virtual reality environments.</p>
</div>
<h2>Experiment Structure</h2>
<p>You will participate in 4 blocks of evaluations. Each block consists of the following:</p>
<ol>
<li>Viewing and evaluating an initial point cloud</li>
<li>Viewing and evaluating a second point cloud</li>
<li>Rating the difference between the two point clouds</li>
</ol>
<h2>Your Task</h2>
<div class="step">
<span class="step-number">1.</span>
<span>You will be immersed in a point cloud environment.</span>
</div>
<div class="step">
<span class="step-number">2.</span>
<span>Explore the point cloud to familiarize yourself with it for later evaluation.</span>
</div>
<img width="640" height="360" src="PCstatic/img/pointcloud.png" alt="Point Cloud Image">
<div class="step">
<span class="step-number">3.</span>
<span>As a control measure, there are <b>one letter/number</b> hidden in the cloud which you must identify. You will be asked about it later during the evaluation.</span>
</div>
<img width="640" height="360" src="PCstatic/img/letter_pointcloud.png" alt="Letter Point Cloud Image">
<div class="step">
<span class="step-number">4.</span>
<span>Please rate the <b>quality</b> of the point cloud on a scale and enter the corresponding control <b>letter/number</b> that you observed.</span>
</div>
<img width="680" height="360" src="PCstatic/img/seq_1.png" alt="Rating Scale Image">
<div class="step">
<span class="step-number">5.</span>
<span>You will then experience a second point cloud.</span>
</div>
<div class="step">
<span class="step-number">6.</span>
<span>Repeat steps 2 and 3 for the new cloud, then rate its <b>quality</b>, enter the control <b>letter/number</b> from the cloud, and <b>assess the difference in quality</b> compared to the previous one.</span>
</div>
<img width="640" height="360" src="PCstatic/img/seq_2.png" alt="Different Compression Settings Image">
<p>This sequence of two point clouds and their evaluations will be repeated 4 times (4 blocks).</p>
<p>After completing all blocks, you will have the opportunity to provide feedback, which we greatly appreciate for further improvement of our research.</p>
<p>To help you familiarize yourself with the survey, you will complete a trial run beforehand.</p>
</section>
</div>
<button type="button" id="consent" disabled="true"></button>
</body>
<script>
var timerElement = document.getElementById('consent');
var timeLeft = 10;
var countdown = setInterval(() => {
if (timeLeft <= 0) {
clearInterval(countdown);
timerElement.textContent = "I Agree";
timerElement.disabled = false;
} else {
timerElement.textContent = timeLeft;
timeLeft--;
}
}, 1000);
</script>
</html>

View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Instructions</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
p, ul {
margin-bottom: 10px;
}
ul {
list-style-type: none;
padding-left: 20px;
}
ul li {
margin-bottom: 5px;
}
img {
border: 2px solid black;
display: block;
margin: 20px auto;
max-width: 100%;
height: auto;
}
button {
display: block;
width: auto;
padding: 10px 20px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 20px auto;
font-size: 16px;
margin-bottom: 50px;
}
button:disabled {
background: #ccc;
color: #666;
cursor: not-allowed;
}
button:not(:disabled):hover {
background: #5d0570;
}
div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div>
<section>
<h1>Controls</h1>
<p>Hold the left mouse button and move the mouse to adjust your perspective.</p>
<img src="PCstatic/img/perspective.gif" width="640" height="640" alt="">
<p>Use the W, A, S, and D keys to change your position:</p>
<ul>
<li><b>W</b> - Move forward</li>
<li><b>S</b> - Move backward</li>
<li><b>D</b> - Move right</li>
<li><b>A</b> - Move left</li>
</ul>
<img src="PCstatic/img/position.gif" width="640" height="640" alt="">
<p>Combine mouse and keyboard controls to navigate through the cloud.</p>
<img src="PCstatic/img/movement.gif" width="640" height="640" alt="">
</section>
</div>
<button type="button" id="consent" disabled="true"></button>
</body>
<script>
var timerElement = document.getElementById('consent');
var timeLeft = 3;
var countdown = setInterval(() => {
if (timeLeft <= 0) {
clearInterval(countdown);
timerElement.textContent = "Let's try!";
timerElement.disabled = false;
} else {
timerElement.textContent = timeLeft;
timeLeft--;
}
}, 1000);
</script>
</html>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<!-- <link rel="stylesheet" href="https://use.typekit.net/hpq3bof.css">
<link href="https://fonts.googleapis.com/css?family=Lancelot" rel="stylesheet"> -->
</head>
<body>
<div id="intro_text">
<header>
<h1>Test Instructions</h1>
</header>
<main>
<section>
<h2>Your Task</h2>
<p>You will first see a video in original quality:</p>
<div style="block">
<img id="Bsp1" style="width:420px;height:280px;margin:10px" src="PCstatic/img/Text/Text-OR-G-E.png" />
</div>
<p>After that, you see the same video in reduced quality:</p>
<div style="block">
<img id="Bsp2" style="width:420px;height:280px;margin:10px" src="PCstatic/img/Text/Text-OC-V8-G-H.png" />
</div>
<p>Your Task:</p>
<ul class="instructions">
<li>Rate the quality of the videos.</li>
<ul class="insturcuions">
<li><b>Good quality</b> is given if the surfaces of objects are smooth and easily recognisable.</li>
<li>A video of <b>bad quality</b> has less recognisable surfaces.</li>
</ul>
<li>Rate the difference in quality between the original video and the reduced video.</li>
<li>Even in the original video you might see areas that are not fully scanned (Areas are missing e.g. a roof). This should not influence your rating!</li>
<li>In addition at the end of each video there will be a red letter in the top right corner. Memorize it and tick/check the corresponding box below your rating.</li>
</ul>
<p></p>
<p>Further informations on the topic are at the end of the survey.</p>
</section>
</main>
</div>
<button type="jspsych-btn" id="consent">I agree to take part in this study.</button>
</body>
</html>
<!-- Cache served by Simple Cache - Last modified: Thu, 05 Jul 2018 02:27:42 GMT -->

View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
section {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h2, h3 {
margin-bottom: 15px;
}
p, ul {
margin-bottom: 10px;
}
ul {
list-style-type: none;
padding-left: 20px;
}
ul li {
margin-bottom: 5px;
}
button {
display: block;
width: 20%;
padding: 10px;
background: #bc0ba8;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-top: 20px;
}
button:disabled {
background: #ccc;
color: #666;
cursor: not-allowed;
}
button:not(:disabled):hover {
background: #5d0570;
}
div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div>
<section>
<h3>Thank you for your participation in this Survey.</h3>
<p>Before starting the survey, please read the test instructions on the next page carefully.</p>
<br>
<h1>Important!</h1>
<b>Please follow the following rules to ensure your participation is rated as successful:</b>
<ul>
<li>Please do <b>NOT Switch</b> tabs or programs during the test.</li>
<li>Please do <b>NOT Zoom</b> in or out during the test.</li>
<li>Please do <b>NOT Reload</b> the page once you have begun the test.</li>
</ul>
</section>
<button type="jspsych-btn" id="consent" disabled="true"></button>
</div>
</body>
<script>
var timerElement = document.getElementById('consent');
var timeLeft = 5;
var countdown = setInterval(() => {
if (timeLeft <= 0) {
clearInterval(countdown);
timerElement.textContent = "Continue";
timerElement.disabled = false;
} else {
timerElement.textContent = timeLeft;
timeLeft--;
}
}, 1000);
</script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1 @@
if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==="string"){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());

2721
PCSurvey/PCstatic/js/external/jspsych.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
/*! evtrack -- Load module */
(function(){function a(m){var l=document.createElement("script");l.type="text/javascript";l.src=m;return l}var d=document.getElementsByTagName("script");var c=d[d.length-1];var j=c.src.split("/");j.splice(j.length-1,1);var k=j.join("/");var b=j[j.length-1]=="src"?".js":".min.js";function i(l,n){var m=a(k+"/"+l+b);c.parentNode.insertBefore(m,c);m.onload=function(){n()}}var h;window.TrackUI={record:function(l){h=function(){window.TrackUI.record(l)}}};var f=["json2","tracklib","trackui"];for(var g in f){i(f[g],e)}function e(){f.shift();if(f.length===0){c.parentNode.removeChild(c);if(typeof h==="function"){h()}}}})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,223 @@
/*! evtrack -- UI module */
(function(f) {
var g = f.document;
var c = "mousedown mouseup mousemove mouseover mouseout mousewheel ";
c += "touchstart touchend touchmove keydown keyup keypress ";
c += "click dblclick scroll change select submit reset contextmenu cut copy paste";
var d = "load unload beforeunload blur focus resize error online offline";
c = c.split(" ");
d = d.split(" ");
var a = c.concat(d);
var b = " "
, e = "|||";
var h = 0
, k = 0
, j = [];
var i = {
settings: {
postServer: "//my.server.org/save.script",
postInterval: 30,
regularEvents: "*",
pollingEvents: "",
pollingMs: 150,
taskName: "evtrack",
callback: null,
saveAttributes: true,
layoutType: "liquid",
debug: false
},
record: function(l) {
k = new Date().getTime();
for (var m in i.settings) {
if (l.hasOwnProperty(m) && l[m] !== null) {
i.settings[m] = l[m]
}
}
i.log("Recording starts...", k, i.settings);
i.addEventListeners();
setTimeout(function() {
i.initNewData(true)
}, i.settings.postInterval * 1000)
},
addEventListeners: function() {
if (i.settings.regularEvents == "*") {
i.addCustomEventListeners(a)
} else {
i.log("Settings regular events...");
i.settings.regularEvents = i.settings.regularEvents.split(" ");
i.addCustomEventListeners(i.settings.regularEvents)
}
if (i.settings.pollingEvents == "*") {
i.addCustomEventListeners(a)
} else {
i.log("Settings polling events...");
i.settings.pollingEvents = i.settings.pollingEvents.split(" ");
i.addCustomEventListeners(i.settings.pollingEvents)
}
var l = (typeof f.onbeforeunload === "function") ? "beforeunload" : "unload";
TrackLib.Events.add(f, l, i.flush)
},
addCustomEventListeners: function(n) {
i.log("Adding event listeners:", n);
for (var l = 0; l < n.length; ++l) {
var m = n[l];
if (!m) {
continue
}
if (c.indexOf(m) > -1) {
TrackLib.Events.add(g, m, i.docHandler);
i.log("Adding document event:", m);
if (g.attachEvent) {
if (m == "focus") {
TrackLib.Events.add(g.body, "focusin", i.winHandler)
}
if (m == "blur") {
TrackLib.Events.add(g.body, "focusout", i.winHandler)
}
}
} else {
if (d.indexOf(m) > -1) {
TrackLib.Events.add(f, m, i.winHandler);
i.log("Adding window event:", m)
}
}
}
},
initNewData: function(l) {
var o = TrackLib.Dimension.getWindowSize()
, n = TrackLib.Dimension.getDocumentSize()
, m = "url=" + encodeURIComponent(f.location.href);
m += "&screenw=" + screen.width;
m += "&screenh=" + screen.height;
m += "&winw=" + o.width;
m += "&winh=" + o.height;
m += "&docw=" + n.width;
m += "&doch=" + n.height;
m += "&info=" + encodeURIComponent(j.join(e));
m += "&task=" + encodeURIComponent(i.settings.taskName);
m += "&action=init";
i.send({
async: l,
postdata: m,
callback: i.setUserId
});
j = []
},
setUserId: function(l) {
h = parseInt(l.responseText);
i.log("setUserId:", h);
if (h) {
setInterval(function() {
i.appendData(true)
}, i.settings.postInterval * 1000)
}
},
appendData: function(l) {
var m = "uid=" + h;
m += "&info=" + encodeURIComponent(j.join(e));
m += "&task=" + encodeURIComponent(i.settings.taskName);
m += "&action=append";
i.send({
async: l,
postdata: m
});
j = []
},
send: function(l) {
l.url = i.settings.postServer;
TrackLib.XHR.sendAjaxRequest(l)
},
docHandler: function(l) {
if (l.type.indexOf("touch") > -1) {
i.touchHandler(l)
} else {
i.eventHandler(l)
}
},
winHandler: function(l) {
i.eventHandler(l)
},
eventHandler: function(q) {
q = TrackLib.Events.fix(q);
var n = new Date().getTime()
, m = q.type
, o = true;
if (i.settings.pollingMs > 0 && i.settings.pollingEvents.indexOf(m) > -1) {
o = (n - k >= i.settings.pollingMs)
}
if (o) {
var s = i.getMousePos(q)
, r = TrackLib.XPath.getXPath(q.target)
, l = i.settings.saveAttributes ? TrackLib.Util.serializeAttrs(q.target) : "{}"
, p = {};
if (typeof i.settings.callback === "function") {
p = i.settings.callback(q)
}
i.fillInfo(q.id, n, s.x, s.y, m, r, l, JSON.stringify(p));
k = n
}
},
touchHandler: function(n) {
n = TrackLib.Events.fix(n);
var m = n.changedTouches;
if (m) {
for (var l = 0, o; l < m.length; ++l) {
o = m[l];
o.type = n.type;
i.eventHandler(o)
}
}
},
getMousePos: function(m) {
m = TrackLib.Events.fix(m);
var l = 0
, n = 0;
if (m.pageX || m.pageY) {
l = m.pageX;
n = m.pageY
} else {
if (m.clientX || m.clientY) {
l = m.clientX + g.body.scrollLeft + g.documentElement.scrollLeft;
n = m.clientY + g.body.scrollTop + g.documentElement.scrollTop
}
}
if (!l || l < 0) {
l = 0
}
if (!n || n < 0) {
n = 0
}
return {
x: l,
y: n
}
},
fillInfo: function() {
var l = [].slice.apply(arguments);
j.push(l.join(b));
i.log(l)
},
flush: function(m) {
i.log("Flushing data...", h);
var l;
for (l = 0; l < c.length; ++l) {
TrackLib.Events.remove(g, c[l], i.docHandler)
}
for (l = 0; l < d.length; ++l) {
TrackLib.Events.remove(f, d[l], i.winHandler)
}
if (h) {
i.appendData(false)
} else {
i.initNewData(false)
}
},
log: function() {
if (i.settings.debug && typeof console.log === "function") {
console.log.apply(console, arguments)
}
}
};
f.TrackUI = i
}
)(this);

2
PCSurvey/PCstatic/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

2
PCSurvey/PCstatic/js/jstat.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
// ******
// Please define your load animation in the configuration variable.
// Note that each page needs to have a (hidden) button called #end-trial that will
// be used to close the trial and move on to the user rating task
// ******
//$('#b1').on('click', function() {
// $('#launch').addClass('display_toggle');
//
// if (jsPsych.data.getURLVariable('debug') === 'true') {
// $('.debug').css({'display': 'block'});
// }
//});
var reloads = 0;
document.getElementById("end-trial").style.display = "none"
document.getElementById("timer").innerHTML = picDuration;
var distance = picDuration - 1;
var x = setInterval(function() {
// Time calculations for days, hours, minutes and seconds
var seconds = distance;
distance--;
// If the count down is finished, write some text
if (seconds <= 0) {
document.getElementById("timer").innerHTML = "";
document.getElementById("end-trial").style.display = ""
clearInterval(x);
} else {
// Display the result in the element with id="demo"
document.getElementById("timer").innerHTML = seconds;
}
}, 1000);

View File

@@ -0,0 +1,46 @@
// ******
// Please define your load animation in the configuration variable.
// Note that each page needs to have a (hidden) button called #end-trial that will
// be used to close the trial and move on to the user rating task
// ******
//$('#b1').on('click', function() {
// $('#launch').addClass('display_toggle');
//
// if (jsPsych.data.getURLVariable('debug') === 'true') {
// $('.debug').css({'display': 'block'});
// }
//});
var reloads = 0;
function replayVideo(){
document.getElementById("vid").load();
reloads = reloads + 1;
}
document.getElementById("end-trial").style.display = "none"
document.getElementById("replay").style.display = "none"
document.getElementById("timer").innerHTML = picDuration;
var distance = picDuration - 1;
var x = setInterval(function() {
// Time calculations for days, hours, minutes and seconds
var seconds = distance;
distance--;
// If the count down is finished, write some text
if (seconds <= 0) {
document.getElementById("timer").innerHTML = "";
document.getElementById("end-trial").style.display = ""
document.getElementById("replay").style.display = ""
clearInterval(x);
} else {
// Display the result in the element with id="demo"
document.getElementById("timer").innerHTML = seconds;
}
}, 1000);

View File

@@ -0,0 +1,154 @@
/**
* jsPsych plugin for showing animations and recording keyboard responses
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*/
jsPsych.plugins.animation = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('animation', 'stimuli', 'image');
plugin.info = {
name: 'animation',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'The images to be displayed.'
},
frame_time: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Frame time',
default: 250,
description: 'Duration to display each image.'
},
frame_isi: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Frame gap',
default: 0,
description: 'Length of gap to be shown between each image.'
},
sequence_reps: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sequence repetitions',
default: 1,
description: 'Number of times to show entire sequence.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'Keys subject uses to respond to stimuli.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below stimulus.'
}
}
}
plugin.trial = function(display_element, trial) {
var interval_time = trial.frame_time + trial.frame_isi;
var animate_frame = -1;
var reps = 0;
var startTime = performance.now();
var animation_sequence = [];
var responses = [];
var current_stim = "";
var animate_interval = setInterval(function() {
var showImage = true;
display_element.innerHTML = ''; // clear everything
animate_frame++;
if (animate_frame == trial.stimuli.length) {
animate_frame = 0;
reps++;
if (reps >= trial.sequence_reps) {
endTrial();
clearInterval(animate_interval);
showImage = false;
}
}
if (showImage) {
show_next_frame();
}
}, interval_time);
function show_next_frame() {
// show image
display_element.innerHTML = '<img src="'+trial.stimuli[animate_frame]+'" id="jspsych-animation-image"></img>';
current_stim = trial.stimuli[animate_frame];
// record when image was shown
animation_sequence.push({
"stimulus": trial.stimuli[animate_frame],
"time": performance.now() - startTime
});
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
if (trial.frame_isi > 0) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-animation-image').style.visibility = 'hidden';
current_stim = 'blank';
// record when blank image was shown
animation_sequence.push({
"stimulus": 'blank',
"time": performance.now() - startTime
});
}, trial.frame_time);
}
}
var after_response = function(info) {
responses.push({
key_press: info.key,
rt: info.rt,
stimulus: current_stim
});
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-animation-image').className += ' responded';
}
// hold the jspsych response listener object in memory
// so that we can turn off the response collection when
// the trial ends
var response_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: true,
allow_held_key: false
});
function endTrial() {
jsPsych.pluginAPI.cancelKeyboardResponse(response_listener);
var trial_data = {
"animation_sequence": JSON.stringify(animation_sequence),
"responses": JSON.stringify(responses)
};
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,216 @@
/**
* jspsych-audio-button-response
* Kristin Diep
*
* plugin for playing an audio file and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["audio-button-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('audio-button-response', 'stimulus', 'audio');
plugin.info = {
name: 'audio-button-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.AUDIO,
pretty_name: 'Stimulus',
default: undefined,
description: 'The audio to be played.'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The button labels.'
},
button_html: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'Custom button. Can make your own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'The maximum duration to wait for a response.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'Vertical margin of button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'Horizontal margin of button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when user makes a response.'
},
trial_ends_after_audio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Trial ends after audio',
default: false,
description: 'If true, then the trial will end as soon as the audio file finishes playing.'
},
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var context = jsPsych.pluginAPI.audioContext();
if(context !== null){
var source = context.createBufferSource();
source.buffer = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus);
source.connect(context.destination);
} else {
var audio = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus);
audio.currentTime = 0;
}
// set up end event if trial needs it
if(trial.trial_ends_after_audio){
if(context !== null){
source.onended = function() {
end_trial();
}
} else {
audio.addEventListener('ended', end_trial);
}
}
//display buttons
var buttons = [];
if (Array.isArray(trial.button_html)) {
if (trial.button_html.length == trial.choices.length) {
buttons = trial.button_html;
} else {
console.error('Error in image-button-response plugin. The length of the button_html array does not equal the length of the choices array');
}
} else {
for (var i = 0; i < trial.choices.length; i++) {
buttons.push(trial.button_html);
}
}
var html = '<div id="jspsych-audio-button-response-btngroup">';
for (var i = 0; i < trial.choices.length; i++) {
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
html += '<div class="jspsych-audio-button-response-button" style="cursor: pointer; display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-audio-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
html += '</div>';
//show prompt if there is one
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-audio-button-response-button-' + i).addEventListener('click', function(e){
var choice = e.currentTarget.getAttribute('data-choice'); // don't use dataset for jsdom compatibility
after_response(choice);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-audio-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// function to end trial when it is time
function end_trial() {
// stop the audio file if it is playing
// remove end event listeners if they exist
if(context !== null){
source.stop();
source.onended = function() { }
} else {
audio.pause();
audio.removeEventListener('ended', end_trial);
}
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// start time
var start_time = performance.now();
// start audio
if(context !== null){
startTime = context.currentTime;
source.start(startTime);
} else {
audio.play();
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,185 @@
/**
* jspsych-audio-keyboard-response
* Josh de Leeuw
*
* plugin for playing an audio file and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["audio-keyboard-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('audio-keyboard-response', 'stimulus', 'audio');
plugin.info = {
name: 'audio-keyboard-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.AUDIO,
pretty_name: 'Stimulus',
default: undefined,
description: 'The audio to be played.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'The maximum duration to wait for a response.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when user makes a response.'
},
trial_ends_after_audio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Trial ends after audio',
default: false,
description: 'If true, then the trial will end as soon as the audio file finishes playing.'
},
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var context = jsPsych.pluginAPI.audioContext();
if(context !== null){
var source = context.createBufferSource();
source.buffer = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus);
source.connect(context.destination);
} else {
var audio = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus);
audio.currentTime = 0;
}
// set up end event if trial needs it
if(trial.trial_ends_after_audio){
if(context !== null){
source.onended = function() {
end_trial();
}
} else {
audio.addEventListener('ended', end_trial);
}
}
// show prompt if there is one
if (trial.prompt !== null) {
display_element.innerHTML = trial.prompt;
}
// store response
var response = {
rt: null,
key: null
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// stop the audio file if it is playing
// remove end event listeners if they exist
if(context !== null){
source.stop();
source.onended = function() { }
} else {
audio.pause();
audio.removeEventListener('ended', end_trial);
}
// kill keyboard listeners
jsPsych.pluginAPI.cancelAllKeyboardResponses();
// gather the data to store for the trial
if(context !== null && response.rt !== null){
response.rt = Math.round(response.rt * 1000);
}
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"key_press": response.key
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
var after_response = function(info) {
// only record the first response
if (response.key == null) {
response = info;
}
if (trial.response_ends_trial) {
end_trial();
}
};
// start audio
if(context !== null){
startTime = context.currentTime;
source.start(startTime);
} else {
audio.play();
}
// start the response listener
if(context !== null) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'audio',
persist: false,
allow_held_key: false,
audio_context: context,
audio_context_start_time: startTime
});
} else {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,214 @@
jsPsych.plugins['audio-slider-response'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('audio-slider-response', 'stimulus', 'audio');
plugin.info = {
name: 'audio-slider-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.AUDIO,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the slider.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var context = jsPsych.pluginAPI.audioContext();
if(context !== null){
var source = context.createBufferSource();
source.buffer = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus);
source.connect(context.destination);
} else {
var audio = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus);
audio.currentTime = 0;
}
// set up end event if trial needs it
if(trial.trial_ends_after_audio){
if(context !== null){
source.onended = function() {
end_trial();
}
} else {
audio.addEventListener('ended', end_trial);
}
}
var html = '<div id="jspsych-audio-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div class="jspsych-audio-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-audio-slider-response-response"></input>';
html += '<div>'
for(var j=0; j < trial.labels.length; j++){
var width = 100/(trial.labels.length-1);
var left_offset = (j * (100 /(trial.labels.length - 1))) - (width/2);
html += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
// add submit button
html += '<button id="jspsych-audio-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
var response = {
rt: null,
response: null
};
if(trial.require_movement){
display_element.querySelector('#jspsych-audio-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-audio-slider-response-next').disabled = false;
})
}
display_element.querySelector('#jspsych-audio-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
var rt = endTime - startTime;
if(context !== null){
endTime = context.currentTime;
rt = Math.round((endTime - startTime) * 1000);
}
response.rt = rt;
response.response = display_element.querySelector('#jspsych-audio-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-audio-slider-response-next').disabled = true;
}
});
function end_trial(){
jsPsych.pluginAPI.clearAllTimeouts();
if(context !== null){
source.stop();
source.onended = function() { }
} else {
audio.pause();
audio.removeEventListener('ended', end_trial);
}
// save data
var trialdata = {
"rt": response.rt,
"stimulus": trial.stimulus,
"response": response.response
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
}
var startTime = performance.now();
// start audio
if(context !== null){
startTime = context.currentTime;
source.start(startTime);
} else {
audio.play();
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,58 @@
/**
* jspsych-call-function
* plugin for calling an arbitrary function during a jspsych experiment
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins['call-function'] = (function() {
var plugin = {};
plugin.info = {
name: 'call-function',
description: '',
parameters: {
func: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Function',
default: undefined,
description: 'Function to call'
},
async: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Asynchronous',
default: false,
description: 'Is the function call asynchronous?'
}
}
}
plugin.trial = function(display_element, trial) {
trial.post_trial_gap = 0;
var return_val;
if(trial.async){
var done = function(data){
return_val = data;
end_trial();
}
trial.func(done);
} else {
return_val = trial.func();
end_trial();
}
function end_trial(){
var trial_data = {
value: return_val
};
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,200 @@
/**
* jspsych plugin for categorization trials with feedback and animated stimuli
* Josh de Leeuw
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins["categorize-animation"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('categorize-animation', 'stimuli', 'image');
plugin.info = {
name: 'categorize-animation',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
default: undefined,
description: 'Array of paths to image files.'
},
key_answer: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key answer',
default: undefined,
description: 'The key to indicate correct response'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'The keys subject is allowed to press to respond to stimuli.'
},
text_answer: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Text answer',
default: null,
description: 'Text to describe correct answer.'
},
correct_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Correct text',
default: 'Correct.',
description: 'String to show when subject gives correct answer'
},
incorrect_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Incorrect text',
default: 'Wrong.',
description: 'String to show when subject gives incorrect answer.'
},
frame_time: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Frame time',
default: 500,
description: 'Duration to display each image.'
},
sequence_reps: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sequence repetitions',
default: 1,
description: 'How many times to display entire sequence.'
},
allow_response_before_complete: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow response before complete',
default: false,
description: 'If true, subject can response before the animation sequence finishes'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 2000,
description: 'How long to show feedback'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
}
}
plugin.trial = function(display_element, trial) {
var animate_frame = -1;
var reps = 0;
var showAnimation = true;
var responded = false;
var timeoutSet = false;
var correct;
// show animation
var animate_interval = setInterval(function() {
display_element.innerHTML = ''; // clear everything
animate_frame++;
if (animate_frame == trial.stimuli.length) {
animate_frame = 0;
reps++;
// check if reps complete //
if (trial.sequence_reps != -1 && reps >= trial.sequence_reps) {
// done with animation
showAnimation = false;
}
}
if (showAnimation) {
display_element.innerHTML += '<img src="'+trial.stimuli[animate_frame]+'" class="jspsych-categorize-animation-stimulus"></img>';
}
if (!responded && trial.allow_response_before_complete) {
// in here if the user can respond before the animation is done
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
} else if (!responded) {
// in here if the user has to wait to respond until animation is done.
// if this is the case, don't show the prompt until the animation is over.
if (!showAnimation) {
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
}
} else {
// user has responded if we get here.
// show feedback
var feedback_text = "";
if (correct) {
feedback_text = trial.correct_text.replace("%ANS%", trial.text_answer);
} else {
feedback_text = trial.incorrect_text.replace("%ANS%", trial.text_answer);
}
display_element.innerHTML += feedback_text;
// set timeout to clear feedback
if (!timeoutSet) {
timeoutSet = true;
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.feedback_duration);
}
}
}, trial.frame_time);
var keyboard_listener;
var trial_data = {};
var after_response = function(info) {
// ignore the response if animation is playing and subject
// not allowed to respond before it is complete
if (!trial.allow_response_before_complete && showAnimation) {
return false;
}
correct = false;
if (trial.key_answer == info.key) {
correct = true;
}
responded = true;
trial_data = {
"stimulus": JSON.stringify(trial.stimuli),
"rt": info.rt,
"correct": correct,
"key_press": info.key
};
jsPsych.pluginAPI.cancelKeyboardResponse(keyboard_listener);
}
keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: true,
allow_held_key: false
});
function endTrial() {
clearInterval(animate_interval); // stop animation!
display_element.innerHTML = ''; // clear everything
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,220 @@
/**
* jspsych plugin for categorization trials with feedback
* Josh de Leeuw
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins['categorize-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'categorize-html',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML content to be displayed.'
},
key_answer: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key answer',
default: undefined,
description: 'The key to indicate the correct response.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
text_answer: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Text answer',
default: null,
description: 'Label that is associated with the correct answer.'
},
correct_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Correct text',
default: "<p class='feedback'>Correct</p>",
description: 'String to show when correct answer is given.'
},
incorrect_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Incorrect text',
default: "<p class='feedback'>Incorrect</p>",
description: 'String to show when incorrect answer is given.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
force_correct_button_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct button press',
default: false,
description: 'If set to true, then the subject must press the correct response key after feedback in order to advance to next trial.'
},
show_stim_with_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
default: true,
no_function: false,
description: ''
},
show_feedback_on_timeout: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show feedback on timeout',
default: false,
description: 'If true, stimulus will be shown during feedback. If false, only the text feedback will be displayed during feedback.'
},
timeout_message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Timeout message',
default: "<p>Please respond faster.</p>",
description: 'The message displayed on a timeout non-response.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 2000,
description: 'How long to show feedback.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<div id="jspsych-categorize-html-stimulus" class="jspsych-categorize-html-stimulus">'+trial.stimulus+'</div>';
// hide image after time if the timing parameter is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-categorize-html-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// if prompt is set, show prompt
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
var trial_data = {};
// create response function
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// clear keyboard listener
jsPsych.pluginAPI.cancelAllKeyboardResponses();
var correct = false;
if (trial.key_answer == info.key) {
correct = true;
}
// save data
trial_data = {
"rt": info.rt,
"correct": correct,
"stimulus": trial.stimulus,
"key_press": info.key
};
display_element.innerHTML = '';
var timeout = info.rt == null;
doFeedback(correct, timeout);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
after_response({
key: null,
rt: null
});
}, trial.trial_duration);
}
function doFeedback(correct, timeout) {
if (timeout && !trial.show_feedback_on_timeout) {
display_element.innerHTML += trial.timeout_message;
} else {
// show image during feedback if flag is set
if (trial.show_stim_with_feedback) {
display_element.innerHTML = '<div id="jspsych-categorize-html-stimulus" class="jspsych-categorize-html-stimulus">'+trial.stimulus+'</div>';
}
// substitute answer in feedback string.
var atext = "";
if (correct) {
atext = trial.correct_text.replace("%ANS%", trial.text_answer);
} else {
atext = trial.incorrect_text.replace("%ANS%", trial.text_answer);
}
// show the feedback
display_element.innerHTML += atext;
}
// check if force correct button press is set
if (trial.force_correct_button_press && correct === false && ((timeout && trial.show_feedback_on_timeout) || !timeout)) {
var after_forced_response = function(info) {
endTrial();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_forced_response,
valid_responses: [trial.key_answer],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
} else {
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.feedback_duration);
}
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,222 @@
/**
* jspsych plugin for categorization trials with feedback
* Josh de Leeuw
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins['categorize-image'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('categorize-image', 'stimulus', 'image');
plugin.info = {
name: 'categorize-image',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image content to be displayed.'
},
key_answer: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key answer',
default: undefined,
description: 'The key to indicate the correct response.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
text_answer: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Text answer',
default: null,
description: 'Label that is associated with the correct answer.'
},
correct_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Correct text',
default: "<p class='feedback'>Correct</p>",
description: 'String to show when correct answer is given.'
},
incorrect_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Incorrect text',
default: "<p class='feedback'>Incorrect</p>",
description: 'String to show when incorrect answer is given.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
force_correct_button_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct button press',
default: false,
description: 'If set to true, then the subject must press the correct response key after feedback in order to advance to next trial.'
},
show_stim_with_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
default: true,
no_function: false,
description: ''
},
show_feedback_on_timeout: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show feedback on timeout',
default: false,
description: 'If true, stimulus will be shown during feedback. If false, only the text feedback will be displayed during feedback.'
},
timeout_message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Timeout message',
default: "<p>Please respond faster.</p>",
description: 'The message displayed on a timeout non-response.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 2000,
description: 'How long to show feedback.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<img id="jspsych-categorize-image-stimulus" class="jspsych-categorize-image-stimulus" src="'+trial.stimulus+'"></img>';
// hide image after time if the timing parameter is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-categorize-image-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// if prompt is set, show prompt
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
var trial_data = {};
// create response function
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// clear keyboard listener
jsPsych.pluginAPI.cancelAllKeyboardResponses();
var correct = false;
if (trial.key_answer == info.key) {
correct = true;
}
// save data
trial_data = {
"rt": info.rt,
"correct": correct,
"stimulus": trial.stimulus,
"key_press": info.key
};
display_element.innerHTML = '';
var timeout = info.rt == null;
doFeedback(correct, timeout);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
after_response({
key: null,
rt: null
});
}, trial.trial_duration);
}
function doFeedback(correct, timeout) {
if (timeout && !trial.show_feedback_on_timeout) {
display_element.innerHTML += trial.timeout_message;
} else {
// show image during feedback if flag is set
if (trial.show_stim_with_feedback) {
display_element.innerHTML = '<img id="jspsych-categorize-image-stimulus" class="jspsych-categorize-image-stimulus" src="'+trial.stimulus+'"></img>';
}
// substitute answer in feedback string.
var atext = "";
if (correct) {
atext = trial.correct_text.replace("%ANS%", trial.text_answer);
} else {
atext = trial.incorrect_text.replace("%ANS%", trial.text_answer);
}
// show the feedback
display_element.innerHTML += atext;
}
// check if force correct button press is set
if (trial.force_correct_button_press && correct === false && ((timeout && trial.show_feedback_on_timeout) || !timeout)) {
var after_forced_response = function(info) {
endTrial();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_forced_response,
valid_responses: [trial.key_answer],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
} else {
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.feedback_duration);
}
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,112 @@
/**
* jspsych-cloze
* Philipp Sprengholz
*
* Plugin for displaying a cloze test and checking participants answers against a correct solution.
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins['cloze'] = (function () {
var plugin = {};
plugin.info = {
name: 'cloze',
description: '',
parameters: {
text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Cloze text',
default: undefined,
description: 'The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. %solution%).'
},
button_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button text',
default: 'OK',
description: 'Text of the button participants have to press for finishing the cloze test.'
},
check_answers: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Check answers',
default: false,
description: 'Boolean value indicating if the answers given by participants should be compared against a correct solution given in the text (between % signs) after the button was clicked.'
},
mistake_fn: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Mistake function',
default: function () {},
description: 'Function called if check_answers is set to TRUE and there is a difference between the participants answers and the correct solution provided in the text.'
}
}
};
plugin.trial = function (display_element, trial) {
var html = '<div class="cloze">';
var elements = trial.text.split('%');
var solutions = [];
for (i=0; i<elements.length; i++)
{
if (i%2 === 0)
{
html += elements[i];
}
else
{
solutions.push(elements[i].trim());
html += '<input type="text" id="input'+(solutions.length-1)+'" value="">';
}
}
html += '</div>';
display_element.innerHTML = html;
var check = function() {
var answers = [];
var answers_correct = true;
for (i=0; i<solutions.length; i++)
{
var field = document.getElementById('input'+i);
answers.push(field.value.trim());
if (trial.check_answers)
{
if (answers[i] !== solutions[i])
{
field.style.color = 'red';
answers_correct = false;
}
else
{
field.style.color = 'black';
}
}
}
if (!trial.check_answers || (trial.check_answers && answers_correct))
{
var trial_data = {
'answers' : answers
};
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
else
{
trial.mistake_fn();
}
};
display_element.innerHTML += '<br><button class="jspsych-html-button-response-button" type="button" id="finish_cloze_button">'+trial.button_text+'</button>';
display_element.querySelector('#finish_cloze_button').addEventListener('click', check);
};
return plugin;
})();

View File

@@ -0,0 +1,108 @@
/** (July 2012, Erik Weitnauer)
The html-plugin will load and display an external html pages. To proceed to the next, the
user might either press a button on the page or a specific key. Afterwards, the page get hidden and
the plugin will wait of a specified time before it proceeds.
documentation: docs.jspsych.org
*/
jsPsych.plugins['external-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'external-html',
description: '',
parameters: {
url: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'URL',
default: undefined,
description: 'The url of the external html page'
},
cont_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Continue key',
default: null,
description: 'The key to continue to the next page.'
},
cont_btn: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Continue button',
default: null,
description: 'The button to continue to the next page.'
},
check_fn: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Check function',
default: function() { return true; },
description: ''
},
force_refresh: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force refresh',
default: false,
description: 'Refresh page.'
},
executeScript: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Execute scripts',
default: false,
description: 'If true, execute scripts on the external html file.'
}
}
}
plugin.trial = function(display_element, trial) {
var url = trial.url;
if (trial.force_refresh) {
url = trial.url + "?time=" + (new Date().getTime());
}
load(display_element, url, function() {
var t0 = (new Date()).getTime();
var finish = function() {
if (trial.check_fn && !trial.check_fn(display_element)) { return };
if (trial.cont_key) { document.removeEventListener('keydown', key_listener); }
var trial_data = {
rt: (new Date()).getTime() - t0,
url: trial.url
};
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
};
if (trial.executeScript) {
for (const scriptEl of display_element.getElementsByTagName("script")) {
const relocatedScript = document.createElement("script");
relocatedScript.text = scriptEl.text;
scriptEl.parentNode.replaceChild(relocatedScript, scriptEl);
};
}
if (trial.cont_btn) { display_element.querySelector('#'+trial.cont_btn).addEventListener('click', finish); }
if (trial.cont_key) {
var key_listener = function(e) {
if (e.which == trial.cont_key) finish();
};
display_element.addEventListener('keydown', key_listener);
}
});
};
// helper to load via XMLHttpRequest
function load(element, file, callback){
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", file, true);
xmlhttp.onload = function(){
if(xmlhttp.status == 200 || xmlhttp.status == 0){ //Check if loaded
element.innerHTML = xmlhttp.responseText;
callback();
}
}
xmlhttp.send();
}
return plugin;
})();

View File

@@ -0,0 +1,194 @@
/**
* jspsych-free-sort
* plugin for drag-and-drop sorting of a collection of images
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*/
jsPsych.plugins['free-sort'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('free-sort', 'stimuli', 'image');
plugin.info = {
name: 'free-sort',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'Images to be displayed.'
},
stim_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus height',
default: 100,
description: 'Height of images in pixels.'
},
stim_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus width',
default: 100,
description: 'Width of images in pixels'
},
sort_area_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sort area height',
default: 800,
description: 'The height of the container that subjects can move the stimuli in.'
},
sort_area_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sort area width',
default: 800,
description: 'The width of the container that subjects can move the stimuli in.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'It can be used to provide a reminder about the action the subject is supposed to take.'
},
prompt_location: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Prompt location',
options: ['above','below'],
default: 'above',
description: 'Indicates whether to show prompt "above" or "below" the sorting area.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to continue to the next trial.'
}
}
}
plugin.trial = function(display_element, trial) {
var start_time = performance.now();
var html = "";
// check if there is a prompt and if it is shown above
if (trial.prompt !== null && trial.prompt_location == "above") {
html += trial.prompt;
}
html += '<div '+
'id="jspsych-free-sort-arena" '+
'class="jspsych-free-sort-arena" '+
'style="position: relative; width:'+trial.sort_area_width+'px; height:'+trial.sort_area_height+'px; border:2px solid #444;"'+
'></div>';
// check if prompt exists and if it is shown below
if (trial.prompt !== null && trial.prompt_location == "below") {
html += trial.prompt;
}
display_element.innerHTML = html;
// store initial location data
var init_locations = [];
for (var i = 0; i < trial.stimuli.length; i++) {
var coords = random_coordinate(trial.sort_area_width - trial.stim_width, trial.sort_area_height - trial.stim_height);
display_element.querySelector("#jspsych-free-sort-arena").innerHTML += '<img '+
'src="'+trial.stimuli[i]+'" '+
'data-src="'+trial.stimuli[i]+'" '+
'class="jspsych-free-sort-draggable" '+
'draggable="false" '+
'style="position: absolute; cursor: move; width:'+trial.stim_width+'px; height:'+trial.stim_height+'px; top:'+coords.y+'px; left:'+coords.x+'px;">'+
'</img>';
init_locations.push({
"src": trial.stimuli[i],
"x": coords.x,
"y": coords.y
});
}
display_element.innerHTML += '<button id="jspsych-free-sort-done-btn" class="jspsych-btn">'+trial.button_label+'</button>';
var maxz = 1;
var moves = [];
var draggables = display_element.querySelectorAll('.jspsych-free-sort-draggable');
for(var i=0;i<draggables.length; i++){
draggables[i].addEventListener('mousedown', function(event){
var x = event.pageX - event.currentTarget.offsetLeft;
var y = event.pageY - event.currentTarget.offsetTop - window.scrollY;
var elem = event.currentTarget;
elem.style.zIndex = ++maxz;
var mousemoveevent = function(e){
elem.style.top = Math.min(trial.sort_area_height - trial.stim_height, Math.max(0,(e.clientY - y))) + 'px';
elem.style.left = Math.min(trial.sort_area_width - trial.stim_width, Math.max(0,(e.clientX - x))) + 'px';
}
document.addEventListener('mousemove', mousemoveevent);
var mouseupevent = function(e){
document.removeEventListener('mousemove', mousemoveevent);
moves.push({
"src": elem.dataset.src,
"x": elem.offsetLeft,
"y": elem.offsetTop
});
document.removeEventListener('mouseup', mouseupevent);
}
document.addEventListener('mouseup', mouseupevent);
});
}
display_element.querySelector('#jspsych-free-sort-done-btn').addEventListener('click', function(){
var end_time = performance.now();
var rt = end_time - start_time;
// gather data
// get final position of all objects
var final_locations = [];
var matches = display_element.querySelectorAll('.jspsych-free-sort-draggable');
for(var i=0; i<matches.length; i++){
final_locations.push({
"src": matches[i].dataset.src,
"x": parseInt(matches[i].style.left),
"y": parseInt(matches[i].style.top)
});
}
var trial_data = {
"init_locations": JSON.stringify(init_locations),
"moves": JSON.stringify(moves),
"final_locations": JSON.stringify(final_locations),
"rt": rt
};
// advance to next part
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
});
};
// helper functions
function random_coordinate(max_width, max_height) {
var rnd_x = Math.floor(Math.random() * (max_width - 1));
var rnd_y = Math.floor(Math.random() * (max_height - 1));
return {
x: rnd_x,
y: rnd_y
};
}
return plugin;
})();

View File

@@ -0,0 +1,104 @@
/* jspsych-fullscreen.js
* Josh de Leeuw
*
* toggle fullscreen mode in the browser
*
*/
jsPsych.plugins.fullscreen = (function() {
var plugin = {};
plugin.info = {
name: 'fullscreen',
description: '',
parameters: {
fullscreen_mode: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Fullscreen mode',
default: true,
array: false,
description: 'If true, experiment will enter fullscreen mode. If false, the browser will exit fullscreen mode.'
},
message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Message',
default: '<p>The experiment will switch to full screen mode when you press the button below</p>',
array: false,
description: 'HTML content to display above the button to enter fullscreen mode.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'The text that appears on the button to enter fullscreen.'
},
delay_after: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Delay after',
default: 1000,
array: false,
description: 'The length of time to delay after entering fullscreen mode before ending the trial.'
},
}
}
plugin.trial = function(display_element, trial) {
// check if keys are allowed in fullscreen mode
var keyboardNotAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
if (keyboardNotAllowed) {
// This is Safari, and keyboard events will be disabled. Don't allow fullscreen here.
// do something else?
endTrial();
} else {
if(trial.fullscreen_mode){
display_element.innerHTML = trial.message + '<button id="jspsych-fullscreen-btn" class="jspsych-btn">'+trial.button_label+'</button>';
var listener = display_element.querySelector('#jspsych-fullscreen-btn').addEventListener('click', function() {
var element = document.documentElement;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
endTrial();
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
endTrial();
}
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.pluginAPI.setTimeout(function(){
var trial_data = {
success: !keyboardNotAllowed
};
jsPsych.finishTrial(trial_data);
}, trial.delay_after);
}
};
return plugin;
})();

View File

@@ -0,0 +1,188 @@
/**
* jspsych-html-button-response
* Josh de Leeuw
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["html-button-response"] = (function() {
var plugin = {};
plugin.info = {
name: 'html-button-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The labels for the buttons.'
},
button_html: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'The html of the button. Can create own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed under the button.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'The vertical margin of the button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'The horizontal margin of the button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, then trial will end when user responds.'
},
}
}
plugin.trial = function(display_element, trial) {
// display stimulus
var html = '<div id="jspsych-html-button-response-stimulus">'+trial.stimulus+'</div>';
//display buttons
var buttons = [];
if (Array.isArray(trial.button_html)) {
if (trial.button_html.length == trial.choices.length) {
buttons = trial.button_html;
} else {
console.error('Error in html-button-response plugin. The length of the button_html array does not equal the length of the choices array');
}
} else {
for (var i = 0; i < trial.choices.length; i++) {
buttons.push(trial.button_html);
}
}
html += '<div id="jspsych-html-button-response-btngroup">';
for (var i = 0; i < trial.choices.length; i++) {
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
html += '<div class="jspsych-html-button-response-button" style="display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-html-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
html += '</div>';
//show prompt if there is one
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
// start time
var start_time = performance.now();
// add event listeners to buttons
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-html-button-response-button-' + i).addEventListener('click', function(e){
var choice = e.currentTarget.getAttribute('data-choice'); // don't use dataset for jsdom compatibility
after_response(choice);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-html-button-response-stimulus').className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-html-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// hide image if timing is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-html-button-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,149 @@
/**
* jspsych-html-keyboard-response
* Josh de Leeuw
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["html-keyboard-response"] = (function() {
var plugin = {};
plugin.info = {
name: 'html-keyboard-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
array: true,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when subject makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
var new_html = '<div id="jspsych-html-keyboard-response-stimulus">'+trial.stimulus+'</div>';
// add prompt
if(trial.prompt !== null){
new_html += trial.prompt;
}
// draw
display_element.innerHTML = new_html;
// store response
var response = {
rt: null,
key: null
};
// function to end trial when it is time
var end_trial = function() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
if (typeof keyboardListener !== 'undefined') {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
}
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"key_press": response.key
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
var after_response = function(info) {
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-html-keyboard-response-stimulus').className += ' responded';
// only record the first response
if (response.key == null) {
response = info;
}
if (trial.response_ends_trial) {
end_trial();
}
};
// start the response listener
if (trial.choices != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// hide stimulus if stimulus_duration is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-html-keyboard-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,193 @@
/**
* jspsych-html-slider-response
* a jspsych plugin for free response survey questions
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['html-slider-response'] = (function() {
var plugin = {};
plugin.info = {
name: 'html-slider-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the slider.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
var html = '<div id="jspsych-html-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div id="jspsych-html-slider-response-stimulus">' + trial.stimulus + '</div>';
html += '<div class="jspsych-html-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-html-slider-response-response"></input>';
html += '<div>'
for(var j=0; j < trial.labels.length; j++){
var width = 100/(trial.labels.length-1);
var left_offset = (j * (100 /(trial.labels.length - 1))) - (width/2);
html += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
// add submit button
html += '<button id="jspsych-html-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
var response = {
rt: null,
response: null
};
if(trial.require_movement){
display_element.querySelector('#jspsych-html-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-html-slider-response-next').disabled = false;
})
}
display_element.querySelector('#jspsych-html-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
response.rt = endTime - startTime;
response.response = display_element.querySelector('#jspsych-html-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-html-slider-response-next').disabled = true;
}
});
function end_trial(){
jsPsych.pluginAPI.clearAllTimeouts();
// save data
var trialdata = {
"rt": response.rt,
"response": response.response,
"stimulus": trial.stimulus
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
}
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-html-slider-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,284 @@
/**
* jspsych-iat
* Kristin Diep
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins['iat-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'iat-html',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed.'
},
left_category_key: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Left category key',
default: 'E',
description: 'Key press that is associated with the left category label.'
},
right_category_key: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category key',
default: 'I',
description: 'Key press that is associated with the right category label.'
},
left_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Left category label',
array: true,
default: ['left'],
description: 'The label that is associated with the stimulus. Aligned to the left side of page.'
},
right_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category label',
array: true,
default: ['right'],
description: 'The label that is associated with the stimulus. Aligned to the right side of the page.'
},
key_to_move_forward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key to move forward',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys that allow the user to advance to the next trial if their key press was incorrect.'
},
display_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Display feedback',
default: false,
description: 'If true, then html when wrong will be displayed when user makes an incorrect key press.'
},
html_when_wrong: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'HTML when wrong',
default: '<span style="color: red; font-size: 80px">X</span>',
description: 'The image to display when a user presses the wrong key.'
},
bottom_instructions: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Bottom instructions',
default: '<p>If you press the wrong key, a red X will appear. Press any key to continue.</p>',
description: 'Instructions shown at the bottom of the page.'
},
force_correct_key_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct key press',
default: false,
description: 'If true, in order to advance to the next trial after a wrong key press the user will be forced to press the correct key.'
},
stim_key_association: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus key association',
options: ['left', 'right'],
default: 'undefined',
description: 'Stimulus will be associated with eight "left" or "right".'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
}
}
plugin.trial = function(display_element, trial) {
var html_str = "";
html_str += "<div style='position: absolute; height: 20%; width: 100%; margin-left: auto; margin-right: auto; top: 42%; left: 0; right: 0'><p id='jspsych-iat-stim'>" + trial.stimulus + "</p></div>";
html_str += "<div id='trial_left_align' style='position: absolute; top: 18%; left: 20%'>";
if(trial.left_category_label.length == 1) {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "</p></div>";
} else {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "<br>" + "or<br>" +
trial.left_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='trial_right_align' style='position: absolute; top: 18%; right: 20%'>";
if(trial.right_category_label.length == 1) {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + '</p></div>';
} else {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + "<br>" + "or<br>" +
trial.right_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='wrongImgID' style='position:relative; top: 300px; margin-left: auto; margin-right: auto; left: 0; right: 0'>";
if(trial.display_feedback === true) {
html_str += "<div id='wrongImgContainer' style='visibility: hidden; position: absolute; top: -75px; margin-left: auto; margin-right: auto; left: 0; right: 0'><p>"+trial.html_when_wrong+"</p></div>";
html_str += "<div>"+trial.bottom_instructions+"</div>";
} else {
html_str += "<div>"+trial.bottom_instructions+"</div>";
}
html_str += "</div>";
display_element.innerHTML = html_str;
// store response
var response = {
rt: null,
key: null,
correct: false
};
// function to end trial when it is time
var end_trial = function() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
if (typeof keyboardListener !== 'undefined') {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
}
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"key_press": response.key,
"correct": response.correct
};
// clears the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
var leftKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.left_category_key);
var rightKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.right_category_key);
// function to handle responses by the subject
var after_response = function(info) {
var wImg = document.getElementById("wrongImgContainer");
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-iat-stim').className += ' responded';
// only record the first response
if (response.key == null ) {
response = info;
}
if(trial.stim_key_association == "right") {
if(response.rt !== null && response.key == rightKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.right_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
} else if(trial.stim_key_association == "left") {
if(response.rt !== null && response.key == leftKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.left_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
}
};
// start the response listener
if (trial.left_category_key != jsPsych.NO_KEYS && trial.right_category_key != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.left_category_key, trial.right_category_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// end trial if time limit is set
if (trial.trial_duration !== null && trial.response_ends_trial != true) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,286 @@
/**
* jspsych-iat
* Kristin Diep
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins['iat-image'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('iat-image', 'stimulus', 'image');
plugin.info = {
name: 'iat-image',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
left_category_key: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Left category key',
default: 'E',
description: 'Key press that is associated with the left category label.'
},
right_category_key: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category key',
default: 'I',
description: 'Key press that is associated with the right category label.'
},
left_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Left category label',
array: true,
default: ['left'],
description: 'The label that is associated with the stimulus. Aligned to the left side of page.'
},
right_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category label',
array: true,
default: ['right'],
description: 'The label that is associated with the stimulus. Aligned to the right side of the page.'
},
key_to_move_forward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key to move forward',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys that allow the user to advance to the next trial if their key press was incorrect.'
},
display_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Display feedback',
default: false,
description: 'If true, then html when wrong will be displayed when user makes an incorrect key press.'
},
html_when_wrong: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'HTML when wrong',
default: '<span style="color: red; font-size: 80px">X</span>',
description: 'The image to display when a user presses the wrong key.'
},
bottom_instructions: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Bottom instructions',
default: '<p>If you press the wrong key, a red X will appear. Press any key to continue.</p>',
description: 'Instructions shown at the bottom of the page.'
},
force_correct_key_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct key press',
default: false,
description: 'If true, in order to advance to the next trial after a wrong key press the user will be forced to press the correct key.'
},
stim_key_association: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus key association',
options: ['left', 'right'],
default: 'undefined',
description: 'Stimulus will be associated with eight "left" or "right".'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
}
}
plugin.trial = function(display_element, trial) {
var html_str = "";
html_str += "<div style='position: absolute; height: 20%; width: 100%; margin-left: auto; margin-right: auto; top: 42%; left: 0; right: 0'><img src='"+trial.stimulus+"' id='jspsych-iat-stim'></img></div>";
html_str += "<div id='trial_left_align' style='position: absolute; top: 18%; left: 20%'>";
if(trial.left_category_label.length == 1) {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "</p></div>";
} else {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "<br>" + "or<br>" +
trial.left_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='trial_right_align' style='position: absolute; top: 18%; right: 20%'>";
if(trial.right_category_label.length == 1) {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + '</p></div>';
} else {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + "<br>" + "or<br>" +
trial.right_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='wrongImgID' style='position:relative; top: 300px; margin-left: auto; margin-right: auto; left: 0; right: 0'>";
if(trial.display_feedback === true) {
html_str += "<div id='wrongImgContainer' style='visibility: hidden; position: absolute; top: -75px; margin-left: auto; margin-right: auto; left: 0; right: 0'><p>"+trial.html_when_wrong+"</p></div>";
html_str += "<div>"+trial.bottom_instructions+"</div>";
} else {
html_str += "<div>"+trial.bottom_instructions+"</div>";
}
html_str += "</div>";
display_element.innerHTML = html_str;
// store response
var response = {
rt: null,
key: null,
correct: false
};
// function to end trial when it is time
var end_trial = function() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
if (typeof keyboardListener !== 'undefined') {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
}
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"key_press": response.key,
"correct": response.correct
};
// clears the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
var leftKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.left_category_key);
var rightKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.right_category_key);
// function to handle responses by the subject
var after_response = function(info) {
var wImg = document.getElementById("wrongImgContainer");
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-iat-stim').className += ' responded';
// only record the first response
if (response.key == null ) {
response = info;
}
if(trial.stim_key_association == "right") {
if(response.rt !== null && response.key == rightKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.right_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
} else if(trial.stim_key_association == "left") {
if(response.rt !== null && response.key == leftKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.left_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
}
};
// start the response listener
if (trial.left_category_key != jsPsych.NO_KEYS && trial.right_category_key != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.left_category_key, trial.right_category_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// end trial if time limit is set
if (trial.trial_duration !== null && trial.response_ends_trial != true) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,224 @@
/**
* jspsych-image-button-response
* Josh de Leeuw
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["image-button-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('image-button-response', 'stimulus', 'image');
plugin.info = {
name: 'image-button-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
stimulus_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image height',
default: null,
description: 'Set the image height in pixels'
},
stimulus_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image width',
default: null,
description: 'Set the image width in pixels'
},
maintain_aspect_ratio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Maintain aspect ratio',
default: true,
description: 'Maintain the aspect ratio after setting width or height'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The labels for the buttons.'
},
button_html: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'The html of the button. Can create own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed under the button.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'The vertical margin of the button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'The horizontal margin of the button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, then trial will end when user responds.'
},
}
}
plugin.trial = function(display_element, trial) {
// display stimulus
var html = '<img src="'+trial.stimulus+'" id="jspsych-image-button-response-stimulus" style="';
if(trial.stimulus_height !== null){
html += 'height:'+trial.stimulus_height+'px; '
if(trial.stimulus_width == null && trial.maintain_aspect_ratio){
html += 'width: auto; ';
}
}
if(trial.stimulus_width !== null){
html += 'width:'+trial.stimulus_width+'px; '
if(trial.stimulus_height == null && trial.maintain_aspect_ratio){
html += 'height: auto; ';
}
}
html +='"></img>';
//display buttons
var buttons = [];
if (Array.isArray(trial.button_html)) {
if (trial.button_html.length == trial.choices.length) {
buttons = trial.button_html;
} else {
console.error('Error in image-button-response plugin. The length of the button_html array does not equal the length of the choices array');
}
} else {
for (var i = 0; i < trial.choices.length; i++) {
buttons.push(trial.button_html);
}
}
html += '<div id="jspsych-image-button-response-btngroup">';
for (var i = 0; i < trial.choices.length; i++) {
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
html += '<div class="jspsych-image-button-response-button" style="display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-image-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
html += '</div>';
//show prompt if there is one
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
// start timing
var start_time = performance.now();
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-image-button-response-button-' + i).addEventListener('click', function(e){
var choice = e.currentTarget.getAttribute('data-choice'); // don't use dataset for jsdom compatibility
after_response(choice);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-image-button-response-stimulus').className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-image-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// hide image if timing is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-image-button-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,182 @@
/**
* jspsych-image-keyboard-response
* Josh de Leeuw
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["image-keyboard-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('image-keyboard-response', 'stimulus', 'image');
plugin.info = {
name: 'image-keyboard-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
stimulus_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image height',
default: null,
description: 'Set the image height in pixels'
},
stimulus_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image width',
default: null,
description: 'Set the image width in pixels'
},
maintain_aspect_ratio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Maintain aspect ratio',
default: true,
description: 'Maintain the aspect ratio after setting width or height'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
array: true,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when subject makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
// display stimulus
var html = '<img src="'+trial.stimulus+'" id="jspsych-image-keyboard-response-stimulus" style="';
if(trial.stimulus_height !== null){
html += 'height:'+trial.stimulus_height+'px; '
if(trial.stimulus_width == null && trial.maintain_aspect_ratio){
html += 'width: auto; ';
}
}
if(trial.stimulus_width !== null){
html += 'width:'+trial.stimulus_width+'px; '
if(trial.stimulus_height == null && trial.maintain_aspect_ratio){
html += 'height: auto; ';
}
}
html +='"></img>';
// add prompt
if (trial.prompt !== null){
html += trial.prompt;
}
// render
display_element.innerHTML = html;
// store response
var response = {
rt: null,
key: null
};
// function to end trial when it is time
var end_trial = function() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
if (typeof keyboardListener !== 'undefined') {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
}
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"key_press": response.key
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
var after_response = function(info) {
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-image-keyboard-response-stimulus').className += ' responded';
// only record the first response
if (response.key == null) {
response = info;
}
if (trial.response_ends_trial) {
end_trial();
}
};
// start the response listener
if (trial.choices != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// hide stimulus if stimulus_duration is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-image-keyboard-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,227 @@
/**
* jspsych-image-slider-response
* a jspsych plugin for free response survey questions
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['image-slider-response'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('image-slider-response', 'stimulus', 'image');
plugin.info = {
name: 'image-slider-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
stimulus_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image height',
default: null,
description: 'Set the image height in pixels'
},
stimulus_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image width',
default: null,
description: 'Set the image width in pixels'
},
maintain_aspect_ratio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Maintain aspect ratio',
default: true,
description: 'Maintain the aspect ratio after setting width or height'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the slider.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
var html = '<div id="jspsych-image-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div id="jspsych-image-slider-response-stimulus">';
html += '<img src="'+trial.stimulus+'" style="';
if(trial.stimulus_height !== null){
html += 'height:'+trial.stimulus_height+'px; '
if(trial.stimulus_width == null && trial.maintain_aspect_ratio){
html += 'width: auto; ';
}
}
if(trial.stimulus_width !== null){
html += 'width:'+trial.stimulus_width+'px; '
if(trial.stimulus_height == null && trial.maintain_aspect_ratio){
html += 'height: auto; ';
}
}
html += '"></img>';
html += '</div>';
html += '<div class="jspsych-image-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-image-slider-response-response"></input>';
html += '<div>'
for(var j=0; j < trial.labels.length; j++){
var width = 100/(trial.labels.length-1);
var left_offset = (j * (100 /(trial.labels.length - 1))) - (width/2);
html += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
// add submit button
html += '<button id="jspsych-image-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
var response = {
rt: null,
response: null
};
if(trial.require_movement){
display_element.querySelector('#jspsych-image-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-image-slider-response-next').disabled = false;
})
}
display_element.querySelector('#jspsych-image-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
response.rt = endTime - startTime;
response.response = display_element.querySelector('#jspsych-image-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-image-slider-response-next').disabled = true;
}
});
function end_trial(){
jsPsych.pluginAPI.clearAllTimeouts();
// save data
var trialdata = {
"rt": response.rt,
"response": response.response
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
}
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-image-slider-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,203 @@
/* jspsych-text.js
* Josh de Leeuw
*
* This plugin displays text (including HTML formatted strings) during the experiment.
* Use it to show instructions, provide performance feedback, etc...
*
* documentation: docs.jspsych.org
*
*
*/
jsPsych.plugins.instructions = (function() {
var plugin = {};
plugin.info = {
name: 'instructions',
description: '',
parameters: {
pages: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Pages',
default: undefined,
array: true,
description: 'Each element of the array is the content for a single page.'
},
key_forward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key forward',
default: 'rightarrow',
description: 'The key the subject can press in order to advance to the next page.'
},
key_backward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key backward',
default: 'leftarrow',
description: 'The key that the subject can press to return to the previous page.'
},
allow_backward: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow backward',
default: true,
description: 'If true, the subject can return to the previous page of the instructions.'
},
allow_keys: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow keys',
default: true,
description: 'If true, the subject can use keyboard keys to navigate the pages.'
},
show_clickable_nav: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show clickable nav',
default: false,
description: 'If true, then a "Previous" and "Next" button will be displayed beneath the instructions.'
},
button_label_previous: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label previous',
default: 'Previous',
description: 'The text that appears on the button to go backwards.'
},
button_label_next: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label next',
default: 'Next',
description: 'The text that appears on the button to go forwards.'
}
}
}
plugin.trial = function(display_element, trial) {
var current_page = 0;
var view_history = [];
var start_time = (new Date()).getTime();
var last_page_update_time = start_time;
function btnListener(evt){
evt.target.removeEventListener('click', btnListener);
if(this.id === "jspsych-instructions-back"){
back();
}
else if(this.id === 'jspsych-instructions-next'){
next();
}
}
function show_current_page() {
display_element.innerHTML = trial.pages[current_page];
if (trial.show_clickable_nav) {
var nav_html = "<div class='jspsych-instructions-nav' style='padding: 10px 0px;'>";
if (current_page != 0 && trial.allow_backward) {
nav_html += "<button id='jspsych-instructions-back' class='jspsych-btn' style='margin-right: 5px;'>&lt; "+trial.button_label_previous+"</button>";
}
nav_html += "<button id='jspsych-instructions-next' class='jspsych-btn' style='margin-left: 5px;'>"+trial.button_label_next+" &gt;</button></div>"
display_element.innerHTML += nav_html;
if (current_page != 0 && trial.allow_backward) {
display_element.querySelector('#jspsych-instructions-back').addEventListener('click', btnListener);
}
display_element.querySelector('#jspsych-instructions-next').addEventListener('click', btnListener);
}
}
function next() {
add_current_page_to_view_history()
current_page++;
// if done, finish up...
if (current_page >= trial.pages.length) {
endTrial();
} else {
show_current_page();
}
}
function back() {
add_current_page_to_view_history()
current_page--;
show_current_page();
}
function add_current_page_to_view_history() {
var current_time = (new Date()).getTime();
var page_view_time = current_time - last_page_update_time;
view_history.push({
page_index: current_page,
viewing_time: page_view_time
});
last_page_update_time = current_time;
}
function endTrial() {
if (trial.allow_keys) {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboard_listener);
}
display_element.innerHTML = '';
var trial_data = {
"view_history": JSON.stringify(view_history),
"rt": (new Date()).getTime() - start_time
};
jsPsych.finishTrial(trial_data);
}
var after_response = function(info) {
// have to reinitialize this instead of letting it persist to prevent accidental skips of pages by holding down keys too long
keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.key_forward, trial.key_backward],
rt_method: 'date',
persist: false,
allow_held_key: false
});
// check if key is forwards or backwards and update page
if (info.key === trial.key_backward || info.key === jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_backward)) {
if (current_page !== 0 && trial.allow_backward) {
back();
}
}
if (info.key === trial.key_forward || info.key === jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_forward)) {
next();
}
};
show_current_page();
if (trial.allow_keys) {
var keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.key_forward, trial.key_backward],
rt_method: 'date',
persist: false
});
}
};
return plugin;
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
/**
* jspsych-reconstruction
* a jspsych plugin for a reconstruction task where the subject recreates
* a stimulus from memory
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['reconstruction'] = (function() {
var plugin = {};
plugin.info = {
name: 'reconstruction',
description: '',
parameters: {
stim_function: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Stimulus function',
default: undefined,
description: 'A function with a single parameter that returns an HTML-formatted string representing the stimulus.'
},
starting_value: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Starting value',
default: 0.5,
description: 'The starting value of the stimulus parameter.'
},
step_size: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Step size',
default: 0.05,
description: 'The change in the stimulus parameter caused by pressing one of the modification keys.'
},
key_increase: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key increase',
default: 'h',
description: 'The key to press for increasing the parameter value.'
},
key_decrease: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key decrease',
default: 'g',
description: 'The key to press for decreasing the parameter value.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to finish the trial.'
}
}
}
plugin.trial = function(display_element, trial) {
// current param level
var param = trial.starting_value;
// set-up key listeners
var after_response = function(info) {
//console.log('fire');
var key_i = (typeof trial.key_increase == 'string') ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_increase) : trial.key_increase;
var key_d = (typeof trial.key_decrease == 'string') ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_decrease) : trial.key_decrease;
// get new param value
if (info.key == key_i) {
param = param + trial.step_size;
} else if (info.key == key_d) {
param = param - trial.step_size;
}
param = Math.max(Math.min(1, param), 0);
// refresh the display
draw(param);
}
// listen for responses
var key_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.key_increase, trial.key_decrease],
rt_method: 'performance',
persist: true,
allow_held_key: true
});
// draw first iteration
draw(param);
function draw(param) {
//console.log(param);
display_element.innerHTML = '<div id="jspsych-reconstruction-stim-container">'+trial.stim_function(param)+'</div>';
// add submit button
display_element.innerHTML += '<button id="jspsych-reconstruction-next" class="jspsych-btn jspsych-reconstruction">'+trial.button_label+'</button>';
display_element.querySelector('#jspsych-reconstruction-next').addEventListener('click', endTrial);
}
function endTrial() {
// measure response time
var endTime =performance.now();
var response_time = endTime - startTime;
// clear keyboard response
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
// save data
var trial_data = {
"rt": response_time,
"final_value": param,
"start_value": trial.starting_value
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
}
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,166 @@
/**
* jspsych-resize
* Steve Chao
*
* plugin for controlling the real world size of the display
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["resize"] = (function() {
var plugin = {};
plugin.info = {
name: 'resize',
description: '',
parameters: {
item_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Item height',
default: 1,
description: 'The height of the item to be measured.'
},
item_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Item width',
default: 1,
description: 'The width of the item to be measured.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'The content displayed below the resizable box and above the button.'
},
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.'
},
starting_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Starting size',
default: 100,
description: 'The initial size of the box, in pixels, along the larget dimension.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label to display on the button to complete calibration.'
},
}
}
plugin.trial = function(display_element, trial) {
var aspect_ratio = trial.item_width / trial.item_height;
// variables to determine div size
if(trial.item_width >= trial.item_height){
var start_div_width = trial.starting_size;
var start_div_height = Math.round(trial.starting_size / aspect_ratio);
} else {
var start_div_height = trial.starting_size;
var start_div_width = Math.round(trial.starting_size * aspect_ratio);
}
// create html for display
var html ='<div id="jspsych-resize-div" style="border: 2px solid steelblue; height: '+start_div_height+'px; width:'+start_div_width+'px; margin: 7px auto; background-color: lightsteelblue; position: relative;">';
html += '<div id="jspsych-resize-handle" style="cursor: nwse-resize; background-color: steelblue; width: 10px; height: 10px; border: 2px solid lightsteelblue; position: absolute; bottom: 0; right: 0;"></div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
html += '<a class="jspsych-btn" id="jspsych-resize-btn">'+trial.button_label+'</a>';
// render
display_element.innerHTML = html;
// listens for the click
document.getElementById("jspsych-resize-btn").addEventListener('click', function() {
scale();
end_trial();
});
var dragging = false;
var origin_x, origin_y;
var cx, cy;
var 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);
var mouseupevent = function(e){
dragging = false;
}
document.addEventListener('mouseup', mouseupevent);
var scale_div = display_element.querySelector('#jspsych-resize-div');
var resizeevent = function(e){
if(dragging){
var dx = (e.pageX - origin_x);
var 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";
}
}
}
document.addEventListener('mousemove', resizeevent);
// scales the stimulus
var scale_factor;
var final_height_px, final_width_px;
function scale() {
final_width_px = scale_div.offsetWidth;
//final_height_px = scale_div.offsetHeight;
var pixels_unit_screen = final_width_px / trial.item_width;
scale_factor = pixels_unit_screen / trial.pixels_per_unit;
document.getElementById("jspsych-content").style.transform = "scale(" + scale_factor + ")";
};
// function to end trial
function end_trial() {
// clear document event listeners
document.removeEventListener('mousemove', resizeevent);
document.removeEventListener('mouseup', mouseupevent);
// clear the screen
display_element.innerHTML = '';
// finishes trial
var trial_data = {
'final_height_px': final_height_px,
'final_width_px': final_width_px,
'scale_factor': scale_factor
}
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,168 @@
/**
* jspsych-same-different
* Josh de Leeuw
*
* plugin for showing two stimuli sequentially and getting a same / different judgment
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['same-different-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'same-different-html',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'The HTML content to be displayed.'
},
answer: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Answer',
options: ['same', 'different'],
default: 75,
description: 'Either "same" or "different".'
},
same_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Same key',
default: 'Q',
description: ''
},
different_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Different key',
default: 'P',
description: 'The key that subjects should press to indicate that the two stimuli are the same.'
},
first_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'First stimulus duration',
default: 1000,
description: 'How long to show the first stimulus for in milliseconds.'
},
gap_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Gap duration',
default: 500,
description: 'How long to show a blank screen in between the two stimuli.'
},
second_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Second stimulus duration',
default: 1000,
description: 'How long to show the second stimulus for in milliseconds.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<div class="jspsych-same-different-stimulus">'+trial.stimuli[0]+'</div>';
var first_stim_info;
if (trial.first_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
showBlankScreen();
}, trial.first_stim_duration);
} else {
function afterKeyboardResponse(info) {
first_stim_info = info;
showBlankScreen();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: afterKeyboardResponse,
valid_responses: trial.advance_key,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
function showBlankScreen() {
display_element.innerHTML = '';
jsPsych.pluginAPI.setTimeout(function() {
showSecondStim();
}, trial.gap_duration);
}
function showSecondStim() {
var html = '<div class="jspsych-same-different-stimulus">'+trial.stimuli[1]+'</div>';
//show prompt here
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
if (trial.second_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('.jspsych-same-different-stimulus').style.visibility = 'hidden';
}, trial.second_stim_duration);
}
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
var correct = false;
var skey = typeof trial.same_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.same_key) : trial.same_key;
var dkey = typeof trial.different_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.different_key) : trial.different_key;
if (info.key == skey && trial.answer == 'same') {
correct = true;
}
if (info.key == dkey && trial.answer == 'different') {
correct = true;
}
var trial_data = {
"rt": info.rt,
"answer": trial.answer,
"correct": correct,
"stimulus": JSON.stringify([trial.stimuli[0], trial.stimuli[1]]),
"key_press": info.key
};
if (first_stim_info) {
trial_data["rt_stim1"] = first_stim_info.rt;
trial_data["key_press_stim1"] = first_stim_info.key;
}
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.same_key, trial.different_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
};
return plugin;
})();

View File

@@ -0,0 +1,169 @@
/**
* jspsych-same-different
* Josh de Leeuw
*
* plugin for showing two stimuli sequentially and getting a same / different judgment
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['same-different-image'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('same-different-image', 'stimuli', 'image')
plugin.info = {
name: 'same-different-image',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'The images to be displayed.'
},
answer: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Answer',
options: ['same', 'different'],
default: 75,
description: 'Either "same" or "different".'
},
same_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Same key',
default: 'Q',
description: ''
},
different_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Different key',
default: 'P',
description: 'The key that subjects should press to indicate that the two stimuli are the same.'
},
first_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'First stimulus duration',
default: 1000,
description: 'How long to show the first stimulus for in milliseconds.'
},
gap_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Gap duration',
default: 500,
description: 'How long to show a blank screen in between the two stimuli.'
},
second_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Second stimulus duration',
default: 1000,
description: 'How long to show the second stimulus for in milliseconds.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<img class="jspsych-same-different-stimulus" src="'+trial.stimuli[0]+'"></img>';
var first_stim_info;
if (trial.first_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
showBlankScreen();
}, trial.first_stim_duration);
} else {
function afterKeyboardResponse(info) {
first_stim_info = info;
showBlankScreen();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: afterKeyboardResponse,
valid_responses: trial.advance_key,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
function showBlankScreen() {
display_element.innerHTML = '';
jsPsych.pluginAPI.setTimeout(function() {
showSecondStim();
}, trial.gap_duration);
}
function showSecondStim() {
var html = '<img class="jspsych-same-different-stimulus" src="'+trial.stimuli[1]+'"></img>';
//show prompt
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
if (trial.second_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('.jspsych-same-different-stimulus').style.visibility = 'hidden';
}, trial.second_stim_duration);
}
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
var correct = false;
var skey = typeof trial.same_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.same_key) : trial.same_key;
var dkey = typeof trial.different_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.different_key) : trial.different_key;
if (info.key == skey && trial.answer == 'same') {
correct = true;
}
if (info.key == dkey && trial.answer == 'different') {
correct = true;
}
var trial_data = {
"rt": info.rt,
"answer": trial.answer,
"correct": correct,
"stimulus": JSON.stringify([trial.stimuli[0], trial.stimuli[1]]),
"key_press": info.key
};
if (first_stim_info) {
trial_data["rt_stim1"] = first_stim_info.rt;
trial_data["key_press_stim1"] = first_stim_info.key;
}
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.same_key, trial.different_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
};
return plugin;
})();

View File

@@ -0,0 +1,213 @@
/**
* jspsych-serial-reaction-time
* Josh de Leeuw
*
* plugin for running a serial reaction time task
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["serial-reaction-time-mouse"] = (function() {
var plugin = {};
plugin.info = {
name: 'serial-reaction-time-mouse',
description: '',
parameters: {
target: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Target',
array: true,
default: undefined,
description: 'The location of the target. The array should be the [row, column] of the target.'
},
grid: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Grid',
array: true,
default: [[1,1,1,1]],
description: 'This array represents the grid of boxes shown on the screen.'
},
grid_square_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Grid square size',
default: 100,
description: 'The width and height in pixels of each square in the grid.'
},
target_color: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Target color',
default: "#999",
description: 'The color of the target square.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial ends after a key press.'
},
pre_target_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pre-target duration',
default: 0,
description: 'The number of milliseconds to display the grid before the target changes color.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial'
},
fade_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fade duration',
default: null,
description: 'If a positive number, the target will progressively change color at the start of the trial, with the transition lasting this many milliseconds.'
},
allow_nontarget_responses: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow nontarget response',
default: false,
description: 'If true, then user can make nontarget response.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus'
},
}
}
plugin.trial = function(display_element, trial) {
var startTime = -1;
var response = {
rt: null,
row: null,
column: null
}
// display stimulus
var stimulus = this.stimulus(trial.grid, trial.grid_square_size);
display_element.innerHTML = stimulus;
if(trial.pre_target_duration <= 0){
showTarget();
} else {
jsPsych.pluginAPI.setTimeout(function(){
showTarget();
}, trial.pre_target_duration);
}
//show prompt if there is one
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
function showTarget(){
var resp_targets;
if(!trial.allow_nontarget_responses){
resp_targets = [display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1])]
} else {
resp_targets = display_element.querySelectorAll('.jspsych-serial-reaction-time-stimulus-cell');
}
for(var i=0; i<resp_targets.length; i++){
resp_targets[i].addEventListener('mousedown', function(e){
if(startTime == -1){
return;
} else {
var info = {}
info.row = e.currentTarget.getAttribute('data-row');
info.column = e.currentTarget.getAttribute('data-column');
info.rt = performance.now() - startTime;
after_response(info);
}
});
}
startTime = performance.now();
if(trial.fade_duration == null){
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
} else {
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.transition = "background-color "+trial.fade_duration;
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
}
if(trial.trial_duration !== null){
jsPsych.pluginAPI.setTimeout(endTrial, trial.trial_duration);
}
}
function endTrial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"grid": JSON.stringify(trial.grid),
"target": JSON.stringify(trial.target),
"response_row": response.row,
"response_column": response.column,
"correct": response.row == trial.target[0] && response.column == trial.target[1]
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
function after_response(info) {
// only record first response
response = response.rt == null ? info : response;
if (trial.response_ends_trial) {
endTrial();
}
};
};
plugin.stimulus = function(grid, square_size, target, target_color, labels) {
var stimulus = "<div id='jspsych-serial-reaction-time-stimulus' style='margin:auto; display: table; table-layout: fixed; border-spacing:"+square_size/4+"px'>";
for(var i=0; i<grid.length; i++){
stimulus += "<div class='jspsych-serial-reaction-time-stimulus-row' style='display:table-row;'>";
for(var j=0; j<grid[i].length; j++){
var classname = 'jspsych-serial-reaction-time-stimulus-cell';
stimulus += "<div class='"+classname+"' id='jspsych-serial-reaction-time-stimulus-cell-"+i+"-"+j+"' "+
"data-row="+i+" data-column="+j+" "+
"style='width:"+square_size+"px; height:"+square_size+"px; display:table-cell; vertical-align:middle; text-align: center; cursor: pointer; font-size:"+square_size/2+"px;";
if(grid[i][j] == 1){
stimulus += "border: 2px solid black;"
}
if(typeof target !== 'undefined' && target[0] == i && target[1] == j){
stimulus += "background-color: "+target_color+";"
}
stimulus += "'>";
if(typeof labels !=='undefined' && labels[i][j] !== false){
stimulus += labels[i][j]
}
stimulus += "</div>";
}
stimulus += "</div>";
}
stimulus += "</div>";
return stimulus
}
return plugin;
})();

View File

@@ -0,0 +1,247 @@
/**
* jspsych-serial-reaction-time
* Josh de Leeuw
*
* plugin for running a serial reaction time task
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["serial-reaction-time"] = (function() {
var plugin = {};
plugin.info = {
name: 'serial-reaction-time',
description: '',
parameters: {
grid: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Grid',
array: true,
default: [[1,1,1,1]],
description: 'This array represents the grid of boxes shown on the screen.'
},
target: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Target',
array: true,
default: undefined,
description: 'The location of the target. The array should be the [row, column] of the target.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: [['3','5','7','9']],
description: ' Each entry in this array is the key that should be pressed for that corresponding location in the grid.'
},
grid_square_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Grid square size',
default: 100,
description: 'The width and height in pixels of each square in the grid.'
},
target_color: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Target color',
default: "#999",
description: 'The color of the target square.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial ends when user makes a response.'
},
pre_target_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pre-target duration',
default: 0,
description: 'The number of milliseconds to display the grid before the target changes color.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
show_response_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show response feedback',
default: false,
description: 'If true, show feedback indicating where the user responded and whether it was correct.'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 200,
description: 'The length of time in milliseconds to show the feedback.'
},
fade_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fade duration',
default: null,
description: 'If a positive number, the target will progressively change color at the start of the trial, with the transition lasting this many milliseconds.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
no_function: false,
description: ' Any content here will be displayed below the stimulus.'
},
}
}
plugin.trial = function(display_element, trial) {
// create a flattened version of the choices array
var flat_choices = jsPsych.utils.flatten(trial.choices);
while(flat_choices.indexOf('') > -1){
flat_choices.splice(flat_choices.indexOf(''),1);
}
// display stimulus
var stimulus = this.stimulus(trial.grid, trial.grid_square_size);
display_element.innerHTML = stimulus;
if(trial.pre_target_duration <= 0){
showTarget();
} else {
jsPsych.pluginAPI.setTimeout(function(){
showTarget();
}, trial.pre_target_duration);
}
//show prompt if there is one
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
var keyboardListener = {};
var response = {
rt: null,
key: false,
correct: false
}
function showTarget(){
if(trial.fade_duration == null){
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
} else {
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.transition = "background-color "+trial.fade_duration;
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
}
keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: flat_choices,
allow_held_key: false
});
if(trial.trial_duration > null){
jsPsych.pluginAPI.setTimeout(showFeedback, trial.trial_duration);
}
}
function showFeedback() {
if(response.rt == null || trial.show_response_feedback == false){
endTrial();
} else {
var color = response.correct ? '#0f0' : '#f00';
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+response.responseLoc[0]+'-'+response.responseLoc[1]).style.transition = "";
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+response.responseLoc[0]+'-'+response.responseLoc[1]).style.backgroundColor = color;
jsPsych.pluginAPI.setTimeout(endTrial, trial.feedback_duration);
}
}
function endTrial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
if (typeof keyboardListener !== 'undefined') {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
}
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"key_press": response.key,
"correct": response.correct,
"grid": JSON.stringify(trial.grid),
"target": JSON.stringify(trial.target)
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
function after_response(info) {
// only record first response
response = response.rt == null ? info : response;
// check if the response is correct
var responseLoc = [];
for(var i=0; i<trial.choices.length; i++){
for(var j=0; j<trial.choices[i].length; j++){
var t = typeof trial.choices[i][j] == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.choices[i][j]) : trial.choices[i][j];
if(info.key == t){
responseLoc = [i,j];
break;
}
}
}
response.responseLoc = responseLoc;
response.correct = (JSON.stringify(responseLoc) == JSON.stringify(trial.target));
if (trial.response_ends_trial) {
if (trial.show_response_feedback){
showFeedback(response.correct);
} else {
endTrial();
}
}
};
};
plugin.stimulus = function(grid, square_size, target, target_color, labels) {
var stimulus = "<div id='jspsych-serial-reaction-time-stimulus' style='margin:auto; display: table; table-layout: fixed; border-spacing:"+square_size/4+"px'>";
for(var i=0; i<grid.length; i++){
stimulus += "<div class='jspsych-serial-reaction-time-stimulus-row' style='display:table-row;'>";
for(var j=0; j<grid[i].length; j++){
stimulus += "<div class='jspsych-serial-reaction-time-stimulus-cell' id='jspsych-serial-reaction-time-stimulus-cell-"+i+"-"+j+"' "+
"style='width:"+square_size+"px; height:"+square_size+"px; display:table-cell; vertical-align:middle; text-align: center; font-size:"+square_size/2+"px;";
if(grid[i][j] == 1){
stimulus += "border: 2px solid black;"
}
if(typeof target !== 'undefined' && target[0] == i && target[1] == j){
stimulus += "background-color: "+target_color+";"
}
stimulus += "'>";
if(typeof labels !=='undefined' && labels[i][j] !== false){
stimulus += labels[i][j]
}
stimulus += "</div>";
}
stimulus += "</div>";
}
stimulus += "</div>";
return stimulus
}
return plugin;
})();

View File

@@ -0,0 +1,144 @@
/**
* jspsych-survey-html-form
* a jspsych plugin for free html forms
*
* Jan Simson
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-html-form'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-html-form',
description: '',
parameters: {
html: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'HTML',
default: null,
description: 'HTML formatted string containing all the input elements to display. Every element has to have its own distinctive name attribute. The <form> tag must not be included and is generated by the plugin.'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to finish the trial.'
},
dataAsArray: {
type: jsPsych.plugins.parameterType.BOOLEAN,
pretty_name: 'Data As Array',
default: false,
description: 'Retrieve the data as an array e.g. [{name: "INPUT_NAME", value: "INPUT_VALUE"}, ...] instead of an object e.g. {INPUT_NAME: INPUT_VALUE, ...}.'
}
}
}
plugin.trial = function(display_element, trial) {
var html = '';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-html-form-preamble" class="jspsych-survey-html-form-preamble">'+trial.preamble+'</div>';
}
// start form
html += '<form id="jspsych-survey-html-form">'
// add form HTML / input elements
html += trial.html;
// add submit button
html += '<input type="submit" id="jspsych-survey-html-form-next" class="jspsych-btn jspsych-survey-html-form" value="'+trial.button_label+'"></input>';
html += '</form>'
display_element.innerHTML = html;
display_element.querySelector('#jspsych-survey-html-form').addEventListener('submit', function(event) {
// don't submit form
event.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
var question_data = serializeArray(this);
if (!trial.dataAsArray) {
question_data = objectifyForm(question_data);
}
// save data
var trialdata = {
"rt": response_time,
"responses": JSON.stringify(question_data)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
});
var startTime = performance.now();
};
/*!
* Serialize all form data into an array
* (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {Node} form The form to serialize
* @return {String} The serialized form data
*/
var serializeArray = function (form) {
// Setup our serialized data
var serialized = [];
// Loop through each field in the form
for (var i = 0; i < form.elements.length; i++) {
var field = form.elements[i];
// Don't serialize fields without a name, submits, buttons, file and reset inputs, and disabled fields
if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue;
// If a multi-select, get all selections
if (field.type === 'select-multiple') {
for (var n = 0; n < field.options.length; n++) {
if (!field.options[n].selected) continue;
serialized.push({
name: field.name,
value: field.options[n].value
});
}
}
// Convert field data to a query string
else if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
serialized.push({
name: field.name,
value: field.value
});
}
}
return serialized;
};
// from https://stackoverflow.com/questions/1184624/convert-form-data-to-javascript-object-with-jquery
function objectifyForm(formArray) {//serialize data function
var returnArray = {};
for (var i = 0; i < formArray.length; i++){
returnArray[formArray[i]['name']] = formArray[i]['value'];
}
return returnArray;
}
return plugin;
})();

View File

@@ -0,0 +1,184 @@
/**
* jspsych-survey-likert
* a jspsych plugin for measuring items on a likert scale
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-likert'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-likert',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'Questions that are associated with the slider.'
},
labels: {
type: jsPsych.plugins.parameterType.STRING,
array: true,
pretty_name: 'Labels',
default: undefined,
description: 'Labels to display for individual question.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Makes answering the question required.'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
randomize_question_order: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Randomize Question Order',
default: false,
description: 'If true, the order of the questions will be randomized'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'String to display at top of the page.'
},
scale_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Scale width',
default: null,
description: 'Width of the likert scales in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label of the button.'
}
}
}
plugin.trial = function(display_element, trial) {
if(trial.scale_width !== null){
var w = trial.scale_width + 'px';
} else {
var w = '100%';
}
var html = "";
// inject CSS for trial
html += '<style id="jspsych-survey-likert-css">';
html += ".jspsych-survey-likert-statement { display:block; font-size: 16px; padding-top: 40px; margin-bottom:10px; }"+
".jspsych-survey-likert-opts { list-style:none; width:"+w+"; margin:auto; padding:0 0 35px; display:block; font-size: 14px; line-height:1.1em; }"+
".jspsych-survey-likert-opt-label { line-height: 1.1em; color: #444; }"+
".jspsych-survey-likert-opts:before { content: ''; position:relative; top:11px; /*left:9.5%;*/ display:block; background-color:#efefef; height:4px; width:100%; }"+
".jspsych-survey-likert-opts:last-of-type { border-bottom: 0; }"+
".jspsych-survey-likert-opts li { display:inline-block; /*width:19%;*/ text-align:center; vertical-align: top; }"+
".jspsych-survey-likert-opts li input[type=radio] { display:block; position:relative; top:0; left:50%; margin-left:-6px; }"
html += '</style>';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-likert-preamble" class="jspsych-survey-likert-preamble">'+trial.preamble+'</div>';
}
html += '<form id="jspsych-survey-likert-form">';
// add likert scale questions ///
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
for (var i = 0; i < trial.questions.length; i++) {
var question = trial.questions[question_order[i]];
// add question
html += '<label class="jspsych-survey-likert-statement">' + question.prompt + '</label>';
// add options
var width = 100 / question.labels.length;
var options_string = '<ul class="jspsych-survey-likert-opts" data-name="'+question.name+'" data-radio-group="Q' + question_order[i] + '">';
for (var j = 0; j < question.labels.length; j++) {
options_string += '<li style="width:' + width + '%"><input type="radio" name="Q' + question_order[i] + '" value="' + j + '"';
if(question.required){
options_string += ' required';
}
options_string += '><label class="jspsych-survey-likert-opt-label">' + question.labels[j] + '</label></li>';
}
options_string += '</ul>';
html += options_string;
}
// add submit button
html += '<input type="submit" id="jspsych-survey-likert-next" class="jspsych-survey-likert jspsych-btn" value="'+trial.button_label+'"></input>';
html += '</form>'
display_element.innerHTML = html;
display_element.querySelector('#jspsych-survey-likert-form').addEventListener('submit', function(e){
e.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
var matches = display_element.querySelectorAll('#jspsych-survey-likert-form .jspsych-survey-likert-opts');
for(var index = 0; index < matches.length; index++){
var id = matches[index].dataset['radioGroup'];
var el = display_element.querySelector('input[name="' + id + '"]:checked');
if (el === null) {
var response = "";
} else {
var response = parseInt(el.value);
}
var obje = {};
if(matches[index].attributes['data-name'].value !== ''){
var name = matches[index].attributes['data-name'].value;
} else {
var name = id;
}
obje[name] = response;
Object.assign(question_data, obje);
}
// save data
var trial_data = {
"rt": response_time,
"responses": JSON.stringify(question_data),
"question_order": JSON.stringify(question_order)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,199 @@
/**
* jspsych-survey-multi-choice
* a jspsych plugin for multiple choice survey questions
*
* Shane Martin
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-multi-choice'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-multi-choice',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'The strings that will be associated with a group of options.'
},
options: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Options',
array: true,
default: undefined,
description: 'Displays options for an individual question.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Subject will be required to pick an option for each question.'
},
horizontal: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Horizontal',
default: false,
description: 'If true, then questions are centered and options are displayed horizontally.'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
randomize_question_order: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Randomize Question Order',
default: false,
description: 'If true, the order of the questions will be randomized'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label of the button.'
}
}
}
plugin.trial = function(display_element, trial) {
var plugin_id_name = "jspsych-survey-multi-choice";
var html = "";
// inject CSS for trial
html += '<style id="jspsych-survey-multi-choice-css">';
html += ".jspsych-survey-multi-choice-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }"+
".jspsych-survey-multi-choice-text span.required {color: darkred;}"+
".jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-text { text-align: center;}"+
".jspsych-survey-multi-choice-option { line-height: 2; }"+
".jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}"+
"label.jspsych-survey-multi-choice-text input[type='radio'] {margin-right: 1em;}";
html += '</style>';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-multi-choice-preamble" class="jspsych-survey-multi-choice-preamble">'+trial.preamble+'</div>';
}
// form element
html += '<form id="jspsych-survey-multi-choice-form">';
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
// add multiple-choice questions
for (var i = 0; i < trial.questions.length; i++) {
// get question based on question_order
var question = trial.questions[question_order[i]];
var question_id = question_order[i];
// create question container
var question_classes = ['jspsych-survey-multi-choice-question'];
if (question.horizontal) {
question_classes.push('jspsych-survey-multi-choice-horizontal');
}
html += '<div id="jspsych-survey-multi-choice-'+question_id+'" class="'+question_classes.join(' ')+'" data-name="'+question.name+'">';
// add question text
html += '<p class="jspsych-survey-multi-choice-text survey-multi-choice">' + question.prompt
if(question.required){
html += "<span class='required'>*</span>";
}
html += '</p>';
// create option radio buttons
for (var j = 0; j < question.options.length; j++) {
// add label and question text
var option_id_name = "jspsych-survey-multi-choice-option-"+question_id+"-"+j;
var input_name = 'jspsych-survey-multi-choice-response-'+question_id;
var input_id = 'jspsych-survey-multi-choice-response-'+question_id+'-'+j;
var required_attr = question.required ? 'required' : '';
// add radio button container
html += '<div id="'+option_id_name+'" class="jspsych-survey-multi-choice-option">';
html += '<label class="jspsych-survey-multi-choice-text" for="'+input_id+'">'+question.options[j]+'</label>';
html += '<input type="radio" name="'+input_name+'" id="'+input_id+'" value="'+question.options[j]+'" '+required_attr+'></input>';
html += '</div>';
}
html += '</div>';
}
// add submit button
html += '<input type="submit" id="'+plugin_id_name+'-next" class="'+plugin_id_name+' jspsych-btn"' + (trial.button_label ? ' value="'+trial.button_label + '"': '') + '></input>';
html += '</form>';
// render
display_element.innerHTML = html;
document.querySelector('form').addEventListener('submit', function(event) {
event.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
for(var i=0; i<trial.questions.length; i++){
var match = display_element.querySelector('#jspsych-survey-multi-choice-'+i);
var id = "Q" + i;
if(match.querySelector("input[type=radio]:checked") !== null){
var val = match.querySelector("input[type=radio]:checked").value;
} else {
var val = "";
}
var obje = {};
var name = id;
if(match.attributes['data-name'].value !== ''){
name = match.attributes['data-name'].value;
}
obje[name] = val;
Object.assign(question_data, obje);
}
// save data
var trial_data = {
"rt": response_time,
"responses": JSON.stringify(question_data),
"question_order": JSON.stringify(question_order)//,
//"reloads": reloads
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,223 @@
/**
* jspsych-survey-multi-select
* a jspsych plugin for multiple choice survey questions
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-multi-select'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-multi-select',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'The strings that will be associated with a group of options.'
},
options: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Options',
array: true,
default: undefined,
description: 'Displays options for an individual question.'
},
horizontal: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Horizontal',
default: false,
description: 'If true, then questions are centered and options are displayed horizontally.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Subject will be required to pick at least one option for this question.'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
randomize_question_order: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Randomize Question Order',
default: false,
description: 'If true, the order of the questions will be randomized'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label of the button.'
},
required_message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Required message',
default: 'You must choose at least one response for this question',
description: 'Message that will be displayed if required question is not answered.'
}
}
}
plugin.trial = function(display_element, trial) {
var plugin_id_name = "jspsych-survey-multi-select";
var plugin_id_selector = '#' + plugin_id_name;
var _join = function( /*args*/ ) {
var arr = Array.prototype.slice.call(arguments, _join.length);
return arr.join(separator = '-');
}
// inject CSS for trial
var cssstr = ".jspsych-survey-multi-select-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }"+
".jspsych-survey-multi-select-text span.required {color: darkred;}"+
".jspsych-survey-multi-select-horizontal .jspsych-survey-multi-select-text { text-align: center;}"+
".jspsych-survey-multi-select-option { line-height: 2; }"+
".jspsych-survey-multi-select-horizontal .jspsych-survey-multi-select-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}"+
"label.jspsych-survey-multi-select-text input[type='checkbox'] {margin-right: 1em;}"
display_element.innerHTML = '<style id="jspsych-survey-multi-select-css">' + cssstr + '</style>';
// form element
var trial_form_id = _join(plugin_id_name, "form");
display_element.innerHTML += '<form id="'+trial_form_id+'"></form>';
var trial_form = display_element.querySelector("#" + trial_form_id);
// show preamble text
var preamble_id_name = _join(plugin_id_name, 'preamble');
if(trial.preamble !== null){
trial_form.innerHTML += '<div id="'+preamble_id_name+'" class="'+preamble_id_name+'">'+trial.preamble+'</div>';
}
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
// add multiple-select questions
for (var i = 0; i < trial.questions.length; i++) {
var question = trial.questions[question_order[i]];
var question_id = question_order[i];
// create question container
var question_classes = [_join(plugin_id_name, 'question')];
if (question.horizontal) {
question_classes.push(_join(plugin_id_name, 'horizontal'));
}
trial_form.innerHTML += '<div id="'+_join(plugin_id_name, question_id)+'" data-name="'+question.name+'" class="'+question_classes.join(' ')+'"></div>';
var question_selector = _join(plugin_id_selector, question_id);
// add question text
display_element.querySelector(question_selector).innerHTML += '<p id="survey-question" class="' + plugin_id_name + '-text survey-multi-select">' + question.prompt + '</p>';
// create option check boxes
for (var j = 0; j < question.options.length; j++) {
var option_id_name = _join(plugin_id_name, "option", question_id, j);
// add check box container
display_element.querySelector(question_selector).innerHTML += '<div id="'+option_id_name+'" class="'+_join(plugin_id_name, 'option')+'"></div>';
// add label and question text
var form = document.getElementById(option_id_name)
var input_name = _join(plugin_id_name, 'response', question_id);
var input_id = _join(plugin_id_name, 'response', question_id, j);
var label = document.createElement('label');
label.setAttribute('class', plugin_id_name+'-text');
label.innerHTML = question.options[j];
label.setAttribute('for', input_id)
// create checkboxes
var input = document.createElement('input');
input.setAttribute('type', "checkbox");
input.setAttribute('name', input_name);
input.setAttribute('id', input_id);
input.setAttribute('value', question.options[j])
form.appendChild(label)
form.insertBefore(input, label)
}
}
// add submit button
trial_form.innerHTML += '<div class="fail-message"></div>'
trial_form.innerHTML += '<button id="'+plugin_id_name+'-next" class="'+plugin_id_name+' jspsych-btn">'+trial.button_label+'</button>';
// validation check on the data first for custom validation handling
// then submit the form
display_element.querySelector('#jspsych-survey-multi-select-next').addEventListener('click', function(){
for(var i=0; i<trial.questions.length; i++){
if(trial.questions[i].required){
if(display_element.querySelector('#jspsych-survey-multi-select-'+i+' input:checked') == null){
display_element.querySelector('#jspsych-survey-multi-select-'+i+' input').setCustomValidity(trial.required_message);
} else {
display_element.querySelector('#jspsych-survey-multi-select-'+i+' input').setCustomValidity('');
}
}
}
trial_form.reportValidity();
})
trial_form.addEventListener('submit', function(event) {
event.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
var has_response = [];
for(var index=0; index<trial.questions.length; index++){
var match = display_element.querySelector('#jspsych-survey-multi-select-'+index);
var val = [];
var inputboxes = match.querySelectorAll("input[type=checkbox]:checked")
for(var j=0; j<inputboxes.length; j++){
currentChecked = inputboxes[j];
val.push(currentChecked.value)
}
var id = 'Q' + index
var obje = {};
var name = id;
if(match.attributes['data-name'].value !== ''){
name = match.attributes['data-name'].value;
}
obje[name] = val;
Object.assign(question_data, obje);
if(val.length == 0){ has_response.push(false); } else { has_response.push(true); }
}
// save data
var trial_data = {
"rt": response_time,
"responses": JSON.stringify(question_data),
"question_order": JSON.stringify(question_order)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,176 @@
/**
* jspsych-survey-text
* a jspsych plugin for free response survey questions
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-text'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-text',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
default: undefined,
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'Prompt for the subject to response'
},
placeholder: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Value',
default: "",
description: 'Placeholder text in the textfield.'
},
rows: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Rows',
default: 1,
description: 'The number of rows for the response text box.'
},
columns: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Columns',
default: 40,
description: 'The number of columns for the response text box.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Require a response'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to finish the trial.'
}
}
}
plugin.trial = function(display_element, trial) {
for (var i = 0; i < trial.questions.length; i++) {
if (typeof trial.questions[i].rows == 'undefined') {
trial.questions[i].rows = 1;
}
}
for (var i = 0; i < trial.questions.length; i++) {
if (typeof trial.questions[i].columns == 'undefined') {
trial.questions[i].columns = 40;
}
}
for (var i = 0; i < trial.questions.length; i++) {
if (typeof trial.questions[i].value == 'undefined') {
trial.questions[i].value = "";
}
}
var html = '';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-text-preamble" class="jspsych-survey-text-preamble">'+trial.preamble+'</div>';
}
// start form
html += '<form id="jspsych-survey-text-form">'
// generate question order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
// add questions
for (var i = 0; i < trial.questions.length; i++) {
var question = trial.questions[question_order[i]];
var question_index = question_order[i];
html += '<div id="jspsych-survey-text-'+question_index+'" class="jspsych-survey-text-question" style="margin: 2em 0em;">';
html += '<p class="jspsych-survey-text">' + question.prompt + '</p>';
var autofocus = i == 0 ? "autofocus" : "";
var req = question.required ? "required" : "";
if(question.rows == 1){
html += '<input type="text" id="input-'+question_index+'" name="#jspsych-survey-text-response-' + question_index + '" data-name="'+question.name+'" size="'+question.columns+'" '+autofocus+' '+req+' placeholder="'+question.placeholder+'"></input>';
} else {
html += '<textarea id="input-'+question_index+'" name="#jspsych-survey-text-response-' + question_index + '" data-name="'+question.name+'" cols="' + question.columns + '" rows="' + question.rows + '" '+autofocus+' '+req+' placeholder="'+question.placeholder+'"></textarea>';
}
html += '</div>';
}
// add submit button
html += '<input type="submit" id="jspsych-survey-text-next" class="jspsych-btn jspsych-survey-text" value="'+trial.button_label+'"></input>';
html += '</form>'
display_element.innerHTML = html;
// backup in case autofocus doesn't work
display_element.querySelector('#input-'+question_order[0]).focus();
display_element.querySelector('#jspsych-survey-text-form').addEventListener('submit', function(e) {
e.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
for(var index=0; index < trial.questions.length; index++){
var id = "Q" + index;
var q_element = document.querySelector('#jspsych-survey-text-'+index).querySelector('textarea, input');
var val = q_element.value;
var name = q_element.attributes['data-name'].value;
if(name == ''){
name = id;
}
var obje = {};
obje[name] = val;
Object.assign(question_data, obje);
}
// save data
var trialdata = {
"rt": response_time,
"responses": JSON.stringify(question_data)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,279 @@
/**
* jspsych-video-button-response
* Josh de Leeuw
*
* plugin for playing a video file and getting a button response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["video-button-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('video-button-response', 'stimulus', 'video');
plugin.info = {
name: 'video-button-response',
description: '',
parameters: {
sources: {
type: jsPsych.plugins.parameterType.VIDEO,
pretty_name: 'Video',
default: undefined,
description: 'The video file to play.'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The labels for the buttons.'
},
button_html: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'The html of the button. Can create own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the buttons.'
},
width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Width',
default: '',
description: 'The width of the video in pixels.'
},
height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Height',
default: '',
description: 'The height of the video display in pixels.'
},
autoplay: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Autoplay',
default: true,
description: 'If true, the video will begin playing as soon as it has loaded.'
},
controls: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Controls',
default: false,
description: 'If true, the subject will be able to pause the video or move the playback to any point in the video.'
},
start: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Start',
default: null,
description: 'Time to start the clip.'
},
stop: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stop',
default: null,
description: 'Time to stop the clip.'
},
rate: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Rate',
default: 1,
description: 'The playback rate of the video. 1 is normal, <1 is slower, >1 is faster.'
},
trial_ends_after_video: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'End trial after video finishes',
default: false,
description: 'If true, the trial will end immediately after the video finishes playing.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'The vertical margin of the button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'The horizontal margin of the button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when subject makes a response.'
}
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var video_html = '<div>'
video_html += '<video id="jspsych-video-button-response-stimulus"';
if(trial.width) {
video_html += ' width="'+trial.width+'"';
}
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
if(trial.autoplay){
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
if(!video_preload_blob) {
for(var i=0; i<trial.sources.length; i++){
var file_name = trial.sources[i];
if(file_name.indexOf('?') > -1){
file_name = file_name.substring(0, file_name.indexOf('?'));
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
video_html+='<source src="' + file_name + '" type="video/'+type+'">';
}
}
video_html += "</video>";
video_html += "</div>";
//display buttons
var buttons = [];
if (Array.isArray(trial.button_html)) {
if (trial.button_html.length == trial.choices.length) {
buttons = trial.button_html;
} else {
console.error('Error in video-button-response plugin. The length of the button_html array does not equal the length of the choices array');
}
} else {
for (var i = 0; i < trial.choices.length; i++) {
buttons.push(trial.button_html);
}
}
video_html += '<div id="jspsych-video-button-response-btngroup">';
for (var i = 0; i < trial.choices.length; i++) {
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
video_html += '<div class="jspsych-video-button-response-button" style="display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-video-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
video_html += '</div>';
// add prompt if there is one
if (trial.prompt !== null) {
video_html += trial.prompt;
}
display_element.innerHTML = video_html;
var start_time = performance.now();
if(video_preload_blob){
display_element.querySelector('#jspsych-video-button-response-stimulus').src = video_preload_blob;
}
display_element.querySelector('#jspsych-video-button-response-stimulus').onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
}
if(trial.start !== null){
display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime = trial.start;
}
if(trial.stop !== null){
display_element.querySelector('#jspsych-video-button-response-stimulus').addEventListener('timeupdate', function(e){
var currenttime = display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime;
if(currenttime >= trial.stop){
display_element.querySelector('#jspsych-video-button-response-stimulus').pause();
}
})
}
display_element.querySelector('#jspsych-video-button-response-stimulus').playbackRate = trial.rate;
// add event listeners to buttons
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-video-button-response-button-' + i).addEventListener('click', function(e){
var choice = e.currentTarget.getAttribute('data-choice'); // don't use dataset for jsdom compatibility
after_response(choice);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-video-button-response-stimulus').className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-video-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,236 @@
/**
* jspsych-video-keyboard-response
* Josh de Leeuw
*
* plugin for playing a video file and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["video-keyboard-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('video-keyboard-response', 'stimulus', 'video');
plugin.info = {
name: 'video-keyboard-response',
description: '',
parameters: {
sources: {
type: jsPsych.plugins.parameterType.VIDEO,
pretty_name: 'Video',
default: undefined,
description: 'The video file to play.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Width',
default: '',
description: 'The width of the video in pixels.'
},
height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Height',
default: '',
description: 'The height of the video display in pixels.'
},
autoplay: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Autoplay',
default: true,
description: 'If true, the video will begin playing as soon as it has loaded.'
},
controls: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Controls',
default: false,
description: 'If true, the subject will be able to pause the video or move the playback to any point in the video.'
},
start: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Start',
default: null,
description: 'Time to start the clip.'
},
stop: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stop',
default: null,
description: 'Time to stop the clip.'
},
rate: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Rate',
default: 1,
description: 'The playback rate of the video. 1 is normal, <1 is slower, >1 is faster.'
},
trial_ends_after_video: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'End trial after video finishes',
default: false,
description: 'If true, the trial will end immediately after the video finishes playing.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when subject makes a response.'
}
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var video_html = '<div>'
video_html += '<video id="jspsych-video-keyboard-response-stimulus"';
if(trial.width) {
video_html += ' width="'+trial.width+'"';
}
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
if(trial.autoplay){
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
if(!video_preload_blob) {
for(var i=0; i<trial.sources.length; i++){
var file_name = trial.sources[i];
if(file_name.indexOf('?') > -1){
file_name = file_name.substring(0, file_name.indexOf('?'));
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
video_html+='<source src="' + file_name + '" type="video/'+type+'">';
}
}
video_html += "</video>";
video_html += "</div>";
// add prompt if there is one
if (trial.prompt !== null) {
video_html += trial.prompt;
}
display_element.innerHTML = video_html;
if(video_preload_blob){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').src = video_preload_blob;
}
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
}
if(trial.start !== null){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime = trial.start;
}
if(trial.stop !== null){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').addEventListener('timeupdate', function(e){
var currenttime = display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime;
if(currenttime >= trial.stop){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').pause();
}
})
}
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').playbackRate = trial.rate;
// store response
var response = {
rt: null,
key: null
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
jsPsych.pluginAPI.cancelAllKeyboardResponses();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"key_press": response.key
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
var after_response = function(info) {
// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').className += ' responded';
// only record the first response
if (response.key == null) {
response = info;
}
if (trial.response_ends_trial) {
end_trial();
}
};
// start the response listener
if (trial.choices != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false,
});
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,291 @@
/**
* jspsych-video-slider-response
* Josh de Leeuw
*
* plugin for playing a video file and getting a slider response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["video-slider-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('video-slider-response', 'stimulus', 'video');
plugin.info = {
name: 'video-slider-response',
description: '',
parameters: {
sources: {
type: jsPsych.plugins.parameterType.VIDEO,
pretty_name: 'Video',
default: undefined,
description: 'The video file to play.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Width',
default: '',
description: 'The width of the video in pixels.'
},
height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Height',
default: '',
description: 'The height of the video display in pixels.'
},
autoplay: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Autoplay',
default: true,
description: 'If true, the video will begin playing as soon as it has loaded.'
},
controls: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Controls',
default: false,
description: 'If true, the subject will be able to pause the video or move the playback to any point in the video.'
},
start: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Start',
default: null,
description: 'Time to start the clip.'
},
stop: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stop',
default: null,
description: 'Time to stop the clip.'
},
rate: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Rate',
default: 1,
description: 'The playback rate of the video. 1 is normal, <1 is slower, >1 is faster.'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
slider_start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
trial_ends_after_video: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'End trial after video finishes',
default: false,
description: 'If true, the trial will end immediately after the video finishes playing.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when subject makes a response.'
}
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var video_html = '<video id="jspsych-video-slider-response-stimulus"';
if(trial.width) {
video_html += ' width="'+trial.width+'"';
}
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
if(trial.autoplay){
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
if(!video_preload_blob) {
for(var i=0; i<trial.sources.length; i++){
var file_name = trial.sources[i];
if(file_name.indexOf('?') > -1){
file_name = file_name.substring(0, file_name.indexOf('?'));
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
video_html+='<source src="' + file_name + '" type="video/'+type+'">';
}
}
video_html += "</video>";
var html = '<div id="jspsych-video-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div id="jspsych-video-slider-response-stimulus">' + video_html + '</div>';
html += '<div class="jspsych-video-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-video-slider-response-response"></input>';
html += '<div>'
for(var j=0; j < trial.labels.length; j++){
var width = 100/(trial.labels.length-1);
var left_offset = (j * (100 /(trial.labels.length - 1))) - (width/2);
html += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
// add prompt if there is one
if (trial.prompt !== null) {
html += '<div>'+trial.prompt+'</div>';
}
// add submit button
html += '<button id="jspsych-video-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
if(video_preload_blob){
display_element.querySelector('#jspsych-video-slider-response-stimulus').src = video_preload_blob;
}
display_element.querySelector('#jspsych-video-slider-response-stimulus').onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
}
if(trial.start !== null){
display_element.querySelector('#jspsych-video-slider-response-stimulus').currentTime = trial.start;
}
if(trial.stop !== null){
display_element.querySelector('#jspsych-video-slider-response-stimulus').addEventListener('timeupdate', function(e){
var currenttime = display_element.querySelector('#jspsych-video-slider-response-stimulus').currentTime;
if(currenttime >= trial.stop){
display_element.querySelector('#jspsych-video-slider-response-stimulus').pause();
}
})
}
display_element.querySelector('#jspsych-video-slider-response-stimulus').playbackRate = trial.rate;
if(trial.require_movement){
display_element.querySelector('#jspsych-video-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-video-slider-response-next').disabled = false;
})
}
var startTime = performance.now();
// store response
var response = {
rt: null,
response: null
};
display_element.querySelector('#jspsych-video-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
response.rt = endTime - startTime;
response.response = display_element.querySelector('#jspsych-video-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-video-slider-response-next').disabled = true;
}
});
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"response": response.response
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,259 @@
/**
*
* jspsych-visual-search-circle
* Josh de Leeuw
*
* display a set of objects, with or without a target, equidistant from fixation
* subject responds to whether or not the target is present
*
* based on code written for psychtoolbox by Ben Motz
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["visual-search-circle"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('visual-search-circle', 'target', 'image');
jsPsych.pluginAPI.registerPreload('visual-search-circle', 'foil', 'image');
jsPsych.pluginAPI.registerPreload('visual-search-circle', 'fixation_image', 'image');
plugin.info = {
name: 'visual-search-circle',
description: '',
parameters: {
target: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Target',
default: undefined,
description: 'The image to be displayed.'
},
foil: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Foil',
default: undefined,
description: 'Path to image file that is the foil/distractor.'
},
fixation_image: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Fixation image',
default: undefined,
description: 'Path to image file that is a fixation target.'
},
set_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Set size',
default: undefined,
description: 'How many items should be displayed?'
},
target_present: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Target present',
default: true,
description: 'Is the target present?'
},
target_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Target size',
array: true,
default: [50, 50],
description: 'Two element array indicating the height and width of the search array element images.'
},
fixation_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fixation size',
array: true,
default: [16, 16],
description: 'Two element array indicating the height and width of the fixation image.'
},
circle_diameter: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Circle diameter',
default: 250,
description: 'The diameter of the search array circle in pixels.'
},
target_present_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Target present key',
default: 'j',
description: 'The key to press if the target is present in the search array.'
},
target_absent_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Target absent key',
default: 'f',
description: 'The key to press if the target is not present in the search array.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'The maximum duration to wait for a response.'
},
fixation_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fixation duration',
default: 1000,
description: 'How long to show the fixation image for before the search array (in milliseconds).'
}
}
}
plugin.trial = function(display_element, trial) {
// circle params
var diam = trial.circle_diameter; // pixels
var radi = diam / 2;
var paper_size = diam + trial.target_size[0];
// stimuli width, height
var stimh = trial.target_size[0];
var stimw = trial.target_size[1];
var hstimh = stimh / 2;
var hstimw = stimw / 2;
// fixation location
var fix_loc = [Math.floor(paper_size / 2 - trial.fixation_size[0] / 2), Math.floor(paper_size / 2 - trial.fixation_size[1] / 2)];
// possible stimulus locations on the circle
var display_locs = [];
var possible_display_locs = trial.set_size;
var random_offset = Math.floor(Math.random() * 360);
for (var i = 0; i < possible_display_locs; i++) {
display_locs.push([
Math.floor(paper_size / 2 + (cosd(random_offset + (i * (360 / possible_display_locs))) * radi) - hstimw),
Math.floor(paper_size / 2 - (sind(random_offset + (i * (360 / possible_display_locs))) * radi) - hstimh)
]);
}
// get target to draw on
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");
// check distractors - array?
if(!Array.isArray(trial.foil)){
fa = [];
for(var i=0; i<trial.set_size; i++){
fa.push(trial.foil);
}
trial.foil = fa;
}
show_fixation();
function show_fixation() {
// show fixation
//var fixation = paper.image(trial.fixation_image, fix_loc[0], fix_loc[1], trial.fixation_size[0], trial.fixation_size[1]);
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>";
// wait
jsPsych.pluginAPI.setTimeout(function() {
// after wait is over
show_search_array();
}, trial.fixation_duration);
}
function show_search_array() {
var search_array_images = [];
var to_present = [];
if(trial.target_present){
to_present.push(trial.target);
}
to_present = to_present.concat(trial.foil);
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;
var after_response = function(info) {
trial_over = true;
var correct = false;
if (jsPsych.pluginAPI.compareKeys(info.key,trial.target_present_key) && trial.target_present ||
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];
key_listener = 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) {
jsPsych.pluginAPI.setTimeout(function() {
if (!trial_over) {
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
trial_over = true;
var rt = null;
var correct = 0;
var key_press = null;
clear_display();
end_trial(rt, correct, key_press);
}
}, trial.trial_duration);
}
function clear_display() {
display_element.innerHTML = '';
}
}
function end_trial(rt, correct, key_press) {
// data saving
var trial_data = {
correct: correct,
rt: rt,
key_press: key_press,
locations: JSON.stringify(display_locs),
target_present: trial.target_present,
set_size: trial.set_size
};
// go to next trial
jsPsych.finishTrial(trial_data);
}
};
// helper function for determining stimulus locations
function cosd(num) {
return Math.cos(num / 180 * Math.PI);
}
function sind(num) {
return Math.sin(num / 180 * Math.PI);
}
return plugin;
})();

View File

@@ -0,0 +1,196 @@
/**
* jsPsych plugin for showing animations that mimic the experiment described in
*
* Fiser, J., & Aslin, R. N. (2002). Statistical learning of higher-order
* temporal structure from visual shape sequences. Journal of Experimental
* Psychology: Learning, Memory, and Cognition, 28(3), 458.
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['vsl-animate-occlusion'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('vsl-animate-occlusion', 'stimuli', 'image');
plugin.info = {
name: 'vsl-animate-occlusion',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'A stimulus is a path to an image file.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: jsPsych.ALL_KEYS,
description: 'This array contains the keys that the subject is allowed to press in order to respond to the stimulus. '
},
canvas_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Canvas size',
array: true,
default: [400,400],
description: 'Array specifying the width and height of the area that the animation will display in.'
},
image_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image size',
array: true,
default: [100,100],
description: 'Array specifying the width and height of the images to show.'
},
initial_direction: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Initial direction',
choices: ['left','right'],
default: 'left',
description: 'Which direction the stimulus should move first.'
},
occlude_center: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Occlude center',
default: true,
description: 'If true, display a rectangle in the center of the screen that is just wide enough to occlude the image completely as it passes behind.'
},
cycle_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Cycle duration',
default: 1000,
description: 'How long it takes for a stimulus in the sequence to make a complete cycle.'
},
pre_movement_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pre movement duration',
default: 500,
description: 'How long to wait before the stimuli starts moving from behind the center rectangle.'
}
}
}
plugin.trial = function(display_element, trial) {
// variable to keep track of timing info and responses
var start_time = 0;
var responses = [];
var directions = [
[{
params: {
x: trial.canvas_size[0] - trial.image_size[0]
},
ms: trial.cycle_duration / 2
}, {
params: {
x: trial.canvas_size[0] / 2 - trial.image_size[0] / 2
},
ms: trial.cycle_duration / 2
}],
[{
params: {
x: 0
},
ms: trial.cycle_duration / 2
}, {
params: {
x: trial.canvas_size[0] / 2 - trial.image_size[0] / 2
},
ms: trial.cycle_duration / 2
}]
];
var which_image = 0;
var next_direction = (trial.initial_direction == "right") ? 0 : 1;
function next_step() {
if (trial.stimuli.length == which_image) {
endTrial();
} else {
var d = directions[next_direction];
next_direction === 0 ? next_direction = 1 : next_direction = 0;
var i = trial.stimuli[which_image];
which_image++;
c.animate(d[0].params, d[0].ms, mina.linear, function() {
c.animate(d[1].params, d[1].ms, mina.linear, function() {
next_step();
});
});
c.attr({
href: i
});
// start timer for this trial
start_time = performance.now();
}
}
display_element.innerHTML = "<svg id='jspsych-vsl-animate-occlusion-canvas' width=" + trial.canvas_size[0] + " height=" + trial.canvas_size[1] + "></svg>";
var paper = Snap("#jspsych-vsl-animate-occlusion-canvas");
var c = paper.image(trial.stimuli[which_image], trial.canvas_size[0] / 2 - trial.image_size[0] / 2, trial.canvas_size[1] / 2 - trial.image_size[1] / 2, trial.image_size[0], trial.image_size[1]).attr({
"id": 'jspsych-vsl-animate-occlusion-moving-image'
});
display_element.querySelector('#jspsych-vsl-animate-occlusion-moving-image').removeAttribute('preserveAspectRatio');
if (trial.occlude_center) {
paper.rect((trial.canvas_size[0] / 2) - (trial.image_size[0] / 2), 0, trial.image_size[0], trial.canvas_size[1]).attr({
fill: "#000"
});
}
// add key listener
var after_response = function(info) {
responses.push({
key: info.key,
stimulus: which_image - 1,
rt: info.rt
});
}
key_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: true,
allow_held_key: false
});
if (trial.pre_movement_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
next_step();
}, trial.pre_movement_duration);
} else {
next_step();
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
var trial_data = {
"stimuli": JSON.stringify(trial.stimuli),
"responses": JSON.stringify(responses)
};
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,103 @@
/**
* jsPsych plugin for showing scenes that mimic the experiments described in
*
* Fiser, J., & Aslin, R. N. (2001). Unsupervised statistical learning of
* higher-order spatial structures from visual scenes. Psychological science,
* 12(6), 499-504.
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['vsl-grid-scene'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('vsl-grid-scene', 'stimuli', 'image');
plugin.info = {
name: 'vsl-grid-scene',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
array: true,
default: undefined,
description: 'An array that defines a grid.'
},
image_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image size',
array: true,
default: [100,100],
description: 'Array specifying the width and height of the images to show.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: 2000,
description: 'How long to show the stimulus for in milliseconds.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = plugin.generate_stimulus(trial.stimuli, trial.image_size);
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.trial_duration);
function endTrial() {
display_element.innerHTML = '';
var trial_data = {
"stimulus": JSON.stringify(trial.stimuli)
};
jsPsych.finishTrial(trial_data);
}
};
plugin.generate_stimulus = function(pattern, image_size) {
var nrows = pattern.length;
var ncols = pattern[0].length;
// create blank element to hold code that we generate
var html = '<div id="jspsych-vsl-grid-scene-dummy" css="display: none;">';
// create table
html += '<table id="jspsych-vsl-grid-scene table" '+
'style="border-collapse: collapse; margin-left: auto; margin-right: auto;">';
for (var row = 0; row < nrows; row++) {
html += '<tr id="jspsych-vsl-grid-scene-table-row-'+row+'" css="height: '+image_size[1]+'px;">';
for (var col = 0; col < ncols; col++) {
html += '<td id="jspsych-vsl-grid-scene-table-' + row + '-' + col +'" '+
'style="padding: '+ (image_size[1] / 10) + 'px ' + (image_size[0] / 10) + 'px; border: 1px solid #555;">'+
'<div id="jspsych-vsl-grid-scene-table-cell-' + row + '-' + col + '" style="width: '+image_size[0]+'px; height: '+image_size[1]+'px;">';
if (pattern[row][col] !== 0) {
html += '<img '+
'src="'+pattern[row][col]+'" style="width: '+image_size[0]+'px; height: '+image_size[1]+'"></img>';
}
html += '</div>';
html += '</td>';
}
html += '</tr>';
}
html += '</table>';
html += '</div>';
return html;
};
return plugin;
})();

View File

@@ -0,0 +1,35 @@
/*
* Example plugin template
*/
jsPsych.plugins["PLUGIN-NAME"] = (function() {
var plugin = {};
plugin.info = {
name: "PLUGIN-NAME",
parameters: {
parameter_name: {
type: jsPsych.plugins.parameterType.INT, // BOOL, STRING, INT, FLOAT, FUNCTION, KEYCODE, SELECT, HTML_STRING, IMAGE, AUDIO, VIDEO, OBJECT, COMPLEX
default: undefined
},
parameter_name: {
type: jsPsych.plugins.parameterType.IMAGE,
default: undefined
}
}
}
plugin.trial = function(display_element, trial) {
// data saving
var trial_data = {
parameter_name: 'parameter value'
};
// end trial
jsPsych.finishTrial(trial_data);
};
return plugin;
})();

View File

@@ -0,0 +1,97 @@
// function randn_bm() {
// var u = 0, v = 0;
// while(u === 0) u = Math.random(); //Converting [0,1) to (0,1)
// while(v === 0) v = Math.random();
// return (Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v )); // still multiply with 10%
// }
var spareRandom = null;
function normalRandom()
{
var val, u, v, s, mul;
if(spareRandom !== null)
{
val = spareRandom;
spareRandom = null;
}
else
{
do
{
u = Math.random()*2-1;
v = Math.random()*2-1;
s = u*u+v*v;
} while(s === 0 || s >= 1);
mul = Math.sqrt(-2 * Math.log(s) / s);
val = u * mul;
spareRandom = v * mul;
}
return val / 14; // 7 standard deviations on either side
}
function normalRandomInRange(min, max)
{
var val;
do
{
val = normalRandom();
} while(val < min || val > max);
return val;
}
function promiseGenerator({id, timeout}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (jsPsych.data.getURLVariable('debug') === 'true') {
$('.debug').html(id + ' visible after ' + timeout + 'ms.');
};
if (id === '.test') {
$(id).css({'display': 'block'});
} else {
$(id).css({'visibility': 'visible'});
};
resolve();
}, timeout);
// console.log(id + " at " + timeout);
})
}
function promiseGenerator_display({id, timeout}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (jsPsych.data.getURLVariable('debug') === 'true') {
$('.debug').html(id + ' visible after ' + timeout + 'ms.');
};
$(id).css({'display': 'block'});
resolve();
}, timeout);
// console.log(id + " at " + timeout);
})
}
function estBetaParams(mu, sigma) {
// console.log("b_mu = "+mu+", b_sigma = "+sigma);
variance = Math.pow(sigma, 2);
alpha = ((1 - mu) / variance - 1 / mu) * Math.pow(mu,2);
beta = alpha * (1 / mu - 1);
return {
a: alpha,
b: beta
};
}
function betaRandomInRange(min, max) {
var betaparams = estBetaParams(0.9, 0.1);
var random_num = jStat.beta.sample(betaparams.a, betaparams.b);
// console.log("random_number -> "+random_num);
var out = min + random_num*(max-min);
// console.log("out -> "+Math.round(out)+" (min: "+min+", max: "+max+")");
return Math.round(out);
}

9
PCSurvey/PCstatic/js/sha256.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
<?php
$post_data = json_decode(file_get_contents('php://input'), true);
// the directory "data" must be writable by the server
$name = "/Data/".$post_data['filename'].".json";
//$name = "logsOutput/".$post_data['filename'].".json";
$data = $post_data['filedata'];
// write the file to disk
file_put_contents($name, $data);
?>

View File

@@ -0,0 +1,44 @@
============
== POTREE ==
============
http://potree.org
Copyright (c) 2011-2017, Markus Schütz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
=====================
== PLASIO / LASLAZ ==
=====================
http://plas.io/
The MIT License (MIT)
Copyright (c) 2014 Uday Verma, uday.karan@gmail.com

View File

@@ -0,0 +1,693 @@
/* CSS - Cascading Style Sheet */
/* Palette color codes */
/* Palette URL: http://paletton.com/#uid=13p0u0kex8W2uqu8af7lEqaulDE */
/* Feel free to copy&paste color codes to your application */
/* As hex codes */
.color-primary-0 { color: #19282C } /* Main Primary color */
.color-primary-1 { color: #7A8184 }
.color-primary-2 { color: #39474B }
.color-primary-3 { color: #2D6D82 }
.color-primary-4 { color: #108FB9 }
/* As RGBa codes */
.rgba-primary-0 { color: rgba( 25, 40, 44,1) } /* Main Primary color */
.rgba-primary-1 { color: rgba(122,129,132,1) }
.rgba-primary-2 { color: rgba( 57, 71, 75,1) }
.rgba-primary-3 { color: rgba( 45,109,130,1) }
.rgba-primary-4 { color: rgba( 16,143,185,1) }
/* Generated by Paletton.com © 2002-2014 */
/* http://paletton.com */
:root{
--color-0: rgba( 25, 40, 44, 1);
--color-1: rgba(122,129,132, 1);
--color-2: rgba( 57, 71, 75, 1);
--color-3: rgba( 45,109,130, 1);
--color-4: rgba( 16,143,185, 1);
--bg-color: var(--color-0);
--bg-color-2: rgb(60, 80, 85);
--bg-light-color: rgba( 48, 61, 65, 1);
--bg-dark-color: rgba( 24, 31, 33, 1);
--bg-hover-color: var(--color-2);
--font-color: #9AA1A4;
--font-color-2: #ddd;
--font-color: #cccccc;
--border-color: black;
--measurement-detail-node-bg-light: var(--color-1);
--measurement-detail-node-bg-dark: var(--color-2);
--measurement-detail-area-bg-color: #eee;
}
#sidebar_root{
color: var(--font-color);
background-color: var(--bg-color);
font-family: Arial,Helvetica,sans-serif;
font-size: 1em;
}
.potree_failpage{
width: 100%;
height: 100%;
background-color: white;
position: absolute;
margin: 15px;
}
.potree_failpage a{
color: initial !important;
text-decoration: underline !important;
}
.potree_info_text{
color: white;
font-weight: bold;
text-shadow: 1px 1px 1px black,
1px -1px 1px black,
-1px 1px 1px black,
-1px -1px 1px black;
}
.potree_message{
width: 500px;
background-color: var(--bg-color);
padding: 5px;
margin: 5px;
border-radius: 4px;
color: var(--font-color);
font-family: Arial;
opacity: 0.8;
border: 1px solid black;
display: flex;
overflow: auto;
}
.potree_message_error{
background-color: red;
}
#potree_description{
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 1000;
}
#potree_sidebar_container{
position: absolute;
z-index: 0;
width: 350px;
height: 100%;
overflow-y: auto;
font-size: 85%;
overflow: hidden;
border-right: 1px solid black;
}
.potree_sidebar_brand{
margin: 1px 20px;
line-height: 2em;
font-size: 100%;
font-weight: bold;
position: relative;
}
#potree_sidebar_container a{
color: #8Aa1c4;
}
.potree_menu_toggle{
position: absolute;
float: left;
margin: 8px 8px;
background: none;
width: 2.5em;
height: 2.5em;
z-index: 100;
cursor: pointer;
}
#potree_map_toggle{
position: absolute;
float: left;
margin: 8px 8px;
background: none;
width: 2.5em;
height: 2.5em;
z-index: 100;
top: calc(2.5em + 8px);
cursor: pointer;
}
#potree_render_area{
position: absolute;
/*background: linear-gradient(-90deg, red, yellow);*/
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
overflow: hidden;
z-index: 1;
-webkit-transition: left .35s;
transition: left .35s;
text-align: start;
}
.potree-panel {
border: 1px solid black;
border-radius: 0.4em;
padding: 0px;
background-color: var(--bg-light-color);
}
.potree-panel-heading{
background-color: var(--bg-dark-color);
}
a:hover, a:visited, a:link, a:active{
color: #ccccff;
text-decoration: none;
}
.annotation{
position: absolute;
padding: 10px;
opacity: 0.5;
transform: translate(-50%, -30px);
will-change: left, top;
}
.annotation-titlebar{
color: white;
background-color: black;
border-radius: 1.5em;
border: 1px solid rgba(255, 255, 255, 0.7);
font-size: 1em;
opacity: 1;
margin: auto;
display: table;
padding: 1px 8px;
cursor: pointer;
}
.annotation-expand{
color: white;
font-size: 0.6em;
opacity: 1;
}
.annotation-action-icon{
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
line-height: 1.5em;
text-align: center;
font-family: Arial;
font-weight: bold;
cursor: pointer;
}
.annotation-action-icon:hover{
filter: drop-shadow(0px 0px 1px white);
width: 24px;
height: 24px;
cursor: pointer;
}
.annotation-item {
color: white;
background-color: black;
opacity: 0.5;
border-radius: 1.5em;
font-size: 1em;
line-height: 1.5em;
padding: 1px 8px 0px 8px;
font-weight: bold;
display: flex;
cursor: default;
}
.annotation-item:hover {
opacity: 1.0;
box-shadow: 0 0 5px #ffffff;
}
.annotation-main{
display: flex;
flex-grow: 1;
}
.annotation-label{
display: inline-block;
height: 100%;
flex-grow: 1;
user-select: none;
-moz-user-select: none;
z-index: 100;
vertical-align: middle;
line-height: 1.5em;
font-family: Arial;
font-weight: bold;
cursor: pointer;
white-space: nowrap;
}
.annotation-description{
position: relative;
color: white;
background-color: black;
padding: 10px;
margin: 5px 0px 0px 0px;
border-radius: 4px;
display: none;
max-width: 500px;
width: 500px;
}
.annotation-description-close{
filter: invert(100%);
float: right;
opacity: 0.5;
margin: 0px 0px 8px 8px;
}
.annotation-description-content{
color: white;
}
.annotation-icon{
width: 20px;
height: 20px;
filter: invert(100%);
margin: 2px 2px;
opacity: 0.5;
}
canvas {
width: 100%;
height: 100%
}
body{
margin: 0;
padding: 0;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
.axis {
font: 10px sans-serif;
color: var(--font-color);
}
.axis path{
fill: rgba(255, 255, 255, 0.5);
stroke: var(--font-color);
shape-rendering: crispEdges;
opacity: 0.7;
}
.axis line {
fill: rgba(255, 255, 255, 0.5);
stroke: var(--font-color);
shape-rendering: crispEdges;
opacity: 0.1;
}
.tick text{
font-size: 12px;
}
.scene_header{
display:flex;
cursor: pointer;
padding: 2px;
}
.scene_content{
padding: 5px 0px 5px 0px;
/*background-color: rgba(0, 0, 0, 0.4);*/
}
.measurement_content{
padding: 5px 15px 5px 10px;
/*background-color: rgba(0, 0, 0, 0.4);*/
}
.propertypanel_content{
padding: 5px 15px 5px 10px;
/*background-color: rgba(0, 0, 0, 0.4);*/
}
.measurement_value_table{
width: 100%;
}
.coordinates_table_container table td {
width: 33%;
text-align: center;
}
#scene_object_properties{
margin: 0px;
}
.pv-panel-heading{
padding: 4px !important;
display: flex;
flex-direction: row
}
.pv-menu-list{
list-style-type: none;
padding: 0;
margin: 15px 0px;
overflow: hidden;
}
.pv-menu-list > *{
margin: 4px 20px;
}
.ui-slider {
margin-top: 5px;
margin-bottom: 10px;
background-color: var(--color-1) !important;
background: none;
border: 1px solid black;
}
.ui-selectmenu-button.ui-button{
width: 100% !important;
}
.pv-menu-list > li > .ui-slider{
background-color: var(--color-1) !important;
background: none;
border: 1px solid black;
}
.pv-menu-list .ui-slider{
background-color: var(--color-1) !important;
background: none;
border: 1px solid black !important;
}
.ui-slider-handle{
border: 1px solid black !important;
}
.ui-widget{
box-sizing:border-box
}
.panel-body > li > .ui-slider{
background-color: var(--color-1) !important;
background: none;
border: 1px solid black;
}
.panel-body > div > li > .ui-slider{
background-color: var(--color-1) !important;
background: none;
border: 1px solid black;
}
.pv-select-label{
margin: 1px;
font-size: 90%;
font-weight: 100;
}
.button-icon:hover{
/*background-color: #09181C;*/
filter: drop-shadow(0px 0px 4px white);
}
.ui-widget-content{
/*color: var(--font-color) !important;*/
}
.accordion > h3{
background-color: var(--bg-color-2) !important;
background: #f6f6f6 50% 50% repeat-x;
border: 1px solid black;
color: var(--font-color-2);
cursor: pointer;
margin: 2px 0 0 0;
padding: 4px 10px 4px 30px;
box-shadow: 0px 3px 3px #111;
text-shadow: 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
font-size: 1em;
}
.accordion > h3:hover{
filter: brightness(125%);
}
.accordion-content{
padding: 0px 0px !important;
border: none !important;
}
.icon-bar{
height: 4px !important;
border: 1px solid black;
background-color: white;
border-radius: 2px;
}
.canvas{
-webkit-transition: top .35s, left .35s, bottom .35s, right .35s, width .35s;
transition: top .35s, left .35s, bottom .35s, right .35s, width .35s;
text-align: start
}
#profile_window{
background-color: var(--bg-color);
}
#profile_titlebar{
background-color: var(--bg-color-2);
color: var(--font-color-2);
text-shadow: 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
font-size: 1em;
font-weight: bold;
}
#profile_window_title{
position: absolute;
margin: 5px;
}
.profile-container-button{
cursor: pointer;
}
.profile-button:hover{
background-color: #0000CC;
}
.unselectable{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.selectable{
-webkit-touch-callout: text;
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.divider {
display: block;
text-align: center;
overflow: hidden;
white-space: nowrap;
font-weight: bold;
font-size: 90%;
letter-spacing: 1px;
margin-left: 0px;
margin-right: 0px;
margin-top: 1px;
margin-bottom: 1px;
padding: 1px !important;
}
.divider > span {
position: relative;
display: inline-block;
}
.divider > span:before,
.divider > span:after {
content: "";
position: absolute;
top: 50%;
width: 9999px;
height: 1px;
background: #b2b2b2;
}
.divider > span:before {
right: 100%;
margin-right: 5px;
}
.divider > span:after {
left: 100%;
margin-left: 5px;
}
.ol-dragbox {
background-color: rgba(255,255,255,0.4);
border-color: rgba(100,150,0,1);
border: 1px solid red;
}
.text-icon{
opacity: 0.5;
height: 24px;
}
.text-icon:hover{
opacity: 1.0;
}
.input-grid-cell{
flex-grow: 1; margin: 0px 3px 0px 3px;
}
.input-grid-label{
flex-grow: 1;
margin: 0px 3px 0px 3px;
text-align:center;
font-weight: bold;
}
.input-grid-cell > input{
width: 100%
}
.invalid_value{
color: #e05e5e;
}
/**
* OVERRIDES
*/
.ui-spinner-input{
color: black;
}
.jstree-themeicon-custom{
background-size: 16px !important;
}
.jstree-default .jstree-clicked{
/*background-color: #ffffff !important;*/
background-color: #34494f !important;
}
.jstree-default .jstree-hovered{
background-color: #34494f !important;
}
.jstree-anchor{
width: 100% !important;
}
.ui-state-default{
background: #a6a9aa !important;
border: 1px solid black;
color: black;
}
.ui-state-active{
background: #c6c9ca !important;
color: black !important;
}
.cesium-viewer .cesium-viewer-cesiumWidgetContainer{
position: absolute;
height: 100%;
width: 100%;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
<div id="profile_window" style="
position: absolute;
width: 84%;
left: 15%; top: 55%; height: 44%;
margin: 5px;
border: 1px solid black;
display: none; box-sizing: border-box; z-index:10000">
<div id="profile_titlebar" class="pv-titlebar" style="display: flex; position: absolute; height: 30px; width: 100%; box-sizing: border-box;">
<span style="padding-right: 10px">
<span id="profile_window_title" data-i18n="profile.title"></span>
</span>
<span id="profileInfo" style="flex-grow: 1; flex-direction: row"> </span>
<!-- <span id="profile_toggle_size_button" class="ui-icon ui-icon-newwin profile-button"> </span> -->
<!--<span id="closeProfileContainer" class="ui-icon ui-icon-close profile-button"> </span>-->
<img id="closeProfileContainer" class="button-icon" style="width: 24px; height: 24px; margin: 4px"/>
</div>
<div style="position: absolute; top: 30px; width: 100%; height: calc(100% - 30px); box-sizing: border-box;" class="pw_content">
<span class="pv-main-color" style="height: 100%; width: 100%; padding: 5px; display:flex; flex-direction: column; box-sizing: border-box;">
<div style=" width: 100%; color: #9d9d9d; margin: 5px; display: flex; flex-direction: row; box-sizing: border-box;">
<span data-i18n="profile.nb_points"></span>:
<span id="profile_num_points">-</span>
<!--<span id="profile_threshold" style="width: 300px">
Threshold: <span id="potree_profile_threshold_label">123</span> <div id="potree_profile_threshold_slider"></div>
</span>-->
<span style="flex-grow: 1;"></span>
<span>
<a id="potree_download_profile_ortho_link" href="#" download="profile.csv">
<img id="potree_download_csv_icon" class="text-icon"/>
<!-- <input id="potree_download_profile_ortho_button" type="button" data-i18n="[value]profile.save_ortho"> -->
</a>
<a id="potree_download_profile_link" href="#" download="profile.las">
<img id="potree_download_las_icon" class="text-icon"/>
<!-- <input id="potree_download_profile_button" type="button" data-i18n="[value]profile.save_las"> -->
</a>
</span>
</div>
<div id="profile_draw_container" style="
width: 100%;
flex-grow: 1;
position: relative; height: 100%;
box-sizing: border-box; user-select: none">
<div style="
position: absolute;
left: 41px;
top: 0;
bottom: 20;
width: calc(100% - 41px);
height: calc(100% - 20px);
background-color: #000000;
"></div>
<svg id="profileSVG" style="
fill: #9d9d9d;
position: absolute;
left: 0; right: 0;
top: 0; bottom: 0;
width: 100%;
height: 100%;
"></svg>
<div id="profileCanvasContainer" style="
position: absolute;
left: 41px;
top: 0;
bottom: 20;
width: calc(100% - 41px);
height: calc(100% - 20px);
/*background-color: #000000;*/
"></div>
<div id="profileSelectionProperties" style="
position: absolute;
left: 50px;
top: 10px;
background-color: black;
color: white;
opacity: 0.7;
padding: 5px;
border: 1px solid white;
user-select: auto;
">
position: asdsadf asdf<br>
rgb: 123 423 123
</div>
</div>
</span>
</div>
</div>

View File

@@ -0,0 +1,15 @@
icons/map_icon.png
from sitn PotreeViewer
http://ne.ch/sitn
https://github.com/PotreeViewer/PotreeViewer
icons/navigation_cube.svg
icons/orthographic_camera.svg
icons/perspective_camera.svg
free for commerical use without attribution
http://www.freepik.com/free-icon/package-cube-box-for-delivery_720159.htm

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3797"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="annotation.svg"
inkscape:export-filename="D:\dev\workspaces\potree\develop\resources\icons\profile.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs3799">
<inkscape:path-effect
effect="spiro"
id="path-effect4170"
is_visible="true" />
<linearGradient
id="linearGradient3890">
<stop
id="stop3898"
offset="0"
style="stop-color:#ff0000;stop-opacity:1;" />
<stop
style="stop-color:#ffff00;stop-opacity:1;"
offset="0.25"
id="stop3904" />
<stop
style="stop-color:#00ff00;stop-opacity:1;"
offset="0.5"
id="stop3902" />
<stop
id="stop3906"
offset="0.75"
style="stop-color:#00ffff;stop-opacity:1;" />
<stop
style="stop-color:#0000ff;stop-opacity:1;"
offset="1"
id="stop3894" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3890-1"
id="linearGradient3896-3"
x1="17"
y1="5"
x2="17"
y2="26"
gradientUnits="userSpaceOnUse"
spreadMethod="pad"
gradientTransform="matrix(0.91304348,0,0,0.91304348,2.5217391,2.173913)" />
<linearGradient
id="linearGradient3890-1">
<stop
id="stop3898-6"
offset="0"
style="stop-color:#ff0000;stop-opacity:1;" />
<stop
style="stop-color:#ffff00;stop-opacity:1;"
offset="0.25"
id="stop3904-8" />
<stop
style="stop-color:#00ff00;stop-opacity:1;"
offset="0.5"
id="stop3902-8" />
<stop
id="stop3906-2"
offset="0.75"
style="stop-color:#00ffff;stop-opacity:1;" />
<stop
style="stop-color:#0000ff;stop-opacity:1;"
offset="1"
id="stop3894-7" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="12.960878"
inkscape:cy="6.4904832"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="true">
<inkscape:grid
type="xygrid"
id="grid3805" />
</sodipodi:namedview>
<metadata
id="metadata3802">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<circle
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.55555558"
id="path3000"
cx="59.629543"
cy="16"
r="5" />
<circle
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3770"
cx="59.629543"
cy="16"
r="14" />
<circle
id="path3772"
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
cx="59.629543"
cy="16"
r="9" />
<path
id="path4535"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 21.96875,22.09375 C 28,12 26,1 16,1 6,1 4,12 10.03125,22.09375 15.112504,30.59761 16,30 16,30 c 0,0 0.887496,0.59761 5.96875,-7.90625 z"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scscs" />
<circle
cy="8.498291"
cx="16"
id="circle4538"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.55430424"
r="4.9887381" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3797"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="area.svg"
inkscape:export-filename="D:\dev\workspaces\potree\develop\resources\icons\area.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs3799" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.836083"
inkscape:cx="-2.4229598"
inkscape:cy="11.830286"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3805" />
</sodipodi:namedview>
<metadata
id="metadata3802">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="fill:#e73100;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.0000003,25.75 H 25 l -5,-10 6,-9.9999996 H 7.0000003 Z"
id="path3790"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<circle
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-1"
cx="7"
cy="26"
r="2.7499998" />
<circle
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7"
cx="25"
cy="26"
r="2.7499998" />
<circle
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7-0"
cx="7"
cy="6.000001"
r="2.7499998" />
<circle
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7-9"
cx="26"
cy="6.000001"
r="2.7499998" />
<circle
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7-4"
cx="20"
cy="16"
r="2.7499998" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3797"
version="1.1"
inkscape:version="0.48.5 r10040"
sodipodi:docname="area.svg"
inkscape:export-filename="D:\dev\workspaces\potree\develop\resources\icons\area.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs3799" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.7994505"
inkscape:cx="-38.367796"
inkscape:cy="-28.878944"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1018"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3805" />
</sodipodi:namedview>
<metadata
id="metadata3802">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 7,25 25,25 20,15 26,5 7,5 z"
id="path3790"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
sodipodi:type="arc"
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:2.18181828999999980;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3807-0-1"
sodipodi:cx="11"
sodipodi:cy="22"
sodipodi:rx="6"
sodipodi:ry="6"
d="M 17,22 A 6,6 0 1 1 5,22 6,6 0 1 1 17,22 z"
transform="matrix(0.45833331,0,0,0.45833331,1.9583336,15.166667)" />
<path
sodipodi:type="arc"
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:2.18181828999999980;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3807-0-7"
sodipodi:cx="11"
sodipodi:cy="22"
sodipodi:rx="6"
sodipodi:ry="6"
d="M 17,22 A 6,6 0 1 1 5,22 6,6 0 1 1 17,22 z"
transform="matrix(0.45833331,0,0,0.45833331,19.958334,15.166667)" />
<path
sodipodi:type="arc"
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:2.18181828999999980;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3807-0-7-0"
sodipodi:cx="11"
sodipodi:cy="22"
sodipodi:rx="6"
sodipodi:ry="6"
d="M 17,22 A 6,6 0 1 1 5,22 6,6 0 1 1 17,22 z"
transform="matrix(0.45833331,0,0,0.45833331,1.9583336,-4.8333327)" />
<path
sodipodi:type="arc"
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:2.18181828999999980;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3807-0-7-9"
sodipodi:cx="11"
sodipodi:cy="22"
sodipodi:rx="6"
sodipodi:ry="6"
d="M 17,22 A 6,6 0 1 1 5,22 6,6 0 1 1 17,22 z"
transform="matrix(0.45833331,0,0,0.45833331,20.958334,-4.8333327)" />
<path
sodipodi:type="arc"
style="fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:2.18181828999999980;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3807-0-7-4"
sodipodi:cx="11"
sodipodi:cy="22"
sodipodi:rx="6"
sodipodi:ry="6"
d="M 17,22 A 6,6 0 1 1 5,22 6,6 0 1 1 17,22 z"
transform="matrix(0.45833331,0,0,0.45833331,14.958334,5.1666673)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3797"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="back.svg"
inkscape:export-filename="D:\dev\workspaces\potree\develop\resources\icons\area.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs3799" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.313709"
inkscape:cx="2.2740742"
inkscape:cy="12.023111"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3805" />
</sodipodi:namedview>
<metadata
id="metadata3802">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 12,5 v 15 l -6,7 6,-7 h 15"
id="path891"
inkscape:connector-curvature="0" />
<path
style="fill:#44a24a;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 11.970172,4.9850856 V 20.014914 H 27 V 4.9850856 Z"
id="path843"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:0.31372549;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 12,5 6,12 H 21 L 27,5 Z"
id="path879"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:0.31372549;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 27,5 -6,7 v 15 l 6,-7 z"
id="path881"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path840"
d="M 5.9850856,11.970171 V 27 H 21.014914 V 11.970171 Z"
style="fill:#ffffff;fill-opacity:0.31372549;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 6,27 V 12 l 6,-7 h 15 v 15 l -6,7 z"
id="path885"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 6,12 H 21 L 27,5"
id="path887"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 21,12 V 27"
id="path889"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3797"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="bottom.svg"
inkscape:export-filename="D:\dev\workspaces\potree\develop\resources\icons\area.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs3799">
<pattern
y="0"
x="0"
height="6"
width="6"
patternUnits="userSpaceOnUse"
id="EMFhbasepattern" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="6.2210905"
inkscape:cy="1.1094284"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3805" />
</sodipodi:namedview>
<metadata
id="metadata3802">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12,5 v 15 l -6,7 6,-7 h 15"
id="path891"
inkscape:connector-curvature="0" />
<path
style="fill:#44a24a;fill-opacity:0;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 11.970172,4.985086 V 20.014914 H 27 V 4.985086 Z"
id="path843"
inkscape:connector-curvature="0" />
<path
style="fill:#e73100;fill-opacity:0;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 12,5 -6,7 v 15 l 6,-7 z"
id="path835"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:0.3137255;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 12,5 6,12 H 21 L 27,5 Z"
id="path879"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:0.31372549;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 27,5 -6,7 v 15 l 6,-7 z"
id="path881"
inkscape:connector-curvature="0" />
<path
style="fill:#2669e7;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="m 12,20 -6,7 h 15 l 6,-7 z"
id="path900"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path840"
d="M 5.985086,11.970171 V 27 H 21.014914 V 11.970171 Z"
style="fill:#ffffff;fill-opacity:0.31372549;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 6,27 V 12 l 6,-7 h 15 v 15 l -6,7 z"
id="path885"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 6,12 H 21 L 27,5"
id="path887"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 21,12 V 27"
id="path889"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg13"
sodipodi:docname="clip-plane-x.svg"
inkscape:version="0.92.1 r15371">
<metadata
id="metadata19">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs17" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1001"
id="namedview15"
showgrid="false"
inkscape:zoom="23.805928"
inkscape:cx="8.4566285"
inkscape:cy="9.6500557"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg13" />
<style
type="text/css"
id="style3694">
.st0{fill:#DDDDDD;stroke:#DDDDDD;}
</style>
<path
class="st0"
d="M 11.95212,2.5364655 3.0470973,7.3937514 v 9.8206226 l 8.9050227,4.751235 8.905025,-4.751235 V 7.4463721 Z M 18.355643,7.9167192 11.956168,11.331392 5.5834101,7.8551936 11.948074,4.3830437 Z M 4.6661927,9.1990428 11.142574,12.731099 v 6.967776 L 4.6661927,16.243726 Z M 12.761668,19.698875 V 12.736766 L 19.23805,9.2808065 V 16.242917 Z M 6.2852879,11.92641 9.5234785,13.692842 v 3.307002 L 6.2852879,15.27227 Z"
id="path11"
inkscape:connector-curvature="0"
style="fill:#dddddd;stroke:#dddddd;stroke-width:0.8095476" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg35"
sodipodi:docname="clip-plane-y.svg"
inkscape:version="0.92.1 r15371">
<metadata
id="metadata41">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs39" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1001"
id="namedview37"
showgrid="false"
inkscape:zoom="27.812867"
inkscape:cx="15.739941"
inkscape:cy="13.312402"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg35" />
<style
type="text/css"
id="style3694">
.st0{fill:#DDDDDD;stroke:#DDDDDD;}
</style>
<path
class="st0"
d="M 11.983343,3.156765 3.4754522,7.797433 v 9.382656 l 8.5078908,4.539347 8.507891,-4.539347 V 7.847707 Z M 18.10129,8.297078 11.98721,11.559467 5.8986542,8.238296 11.979476,4.9209921 Z M 5.0223414,9.522214 11.209898,12.896753 v 6.657038 L 5.0223414,16.252729 Z m 7.7344466,10.031577 v -6.651624 l 6.187557,-3.301835 v 6.651624 z m 4.640667,-7.374021 v 3.144826 l -3.093778,1.65053 v -3.144825 z"
id="path33"
inkscape:connector-curvature="0"
style="fill:#dddddd;stroke:#dddddd;stroke-width:0.77344459" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg24"
sodipodi:docname="clip-plane-z.svg"
inkscape:version="0.92.1 r15371">
<metadata
id="metadata30">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs28" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1001"
id="namedview26"
showgrid="false"
inkscape:zoom="19.666667"
inkscape:cx="18.300037"
inkscape:cy="12.90824"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg24" />
<style
type="text/css"
id="style3694">
.st0{fill:#DDDDDD;stroke:#DDDDDD;}
</style>
<path
class="st0"
d="M 12.112502,2.9534908 3.4367263,7.685732 v 9.567802 l 8.6757757,4.628922 8.675775,-4.628922 V 7.7369979 Z M 18.351173,8.1952365 12.116446,11.522002 5.9077448,8.1352951 12.108559,4.7525312 Z M 5.0141399,9.4445478 11.323795,12.885676 v 6.7884 L 5.0141399,16.307875 Z M 12.901209,19.674076 V 12.891197 L 19.210863,9.5242072 V 16.307086 Z M 12.104615,6.5515715 15.043336,8.1723638 12.125121,9.7292713 9.2021733,8.1352951 Z"
id="path22"
inkscape:connector-curvature="0"
style="fill:#dddddd;stroke:#dddddd;stroke-width:0.78870684" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg3797"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="clip-polygon.svg"
inkscape:export-filename="D:\dev\workspaces\potree\develop\resources\icons\area.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs3799" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="31.672167"
inkscape:cx="10.156073"
inkscape:cy="15.68414"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="3840"
inkscape:window-height="2066"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3805" />
</sodipodi:namedview>
<metadata
id="metadata3802">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="fill:#ffffff;fill-opacity:0.39215687;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7,25 H 25 L 20,15 26,5.0000002 H 7 Z"
id="path3790"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<circle
r="2.7499998"
cy="25.25"
cx="7"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-1" />
<circle
r="2.7499998"
cy="25.25"
cx="25"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7" />
<circle
r="2.7499998"
cy="5.25"
cx="7"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7-0" />
<circle
r="2.7499998"
cy="5.25"
cx="26"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7-9" />
<circle
r="2.7499998"
cy="15.25"
cx="20"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3807-0-7-4" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Some files were not shown because too many files have changed in this diff Show More