Dynamically create row of geometrical shapes for each trial #3208
-
Good evening, in my experiment I am using as stimuli geometrical figures which differ in size (big, small, medium), color (red, green, blue) and shape (triangle, circle or square). There is thus a total of 27 possible figures. I was thus trying to use the canvas the canvas in order to draw the stimuli. Given an initial list of stimuli in the form: The first problem that I have with this is that I cannot find a way to pass my 3 geometrical figures (as maybe subpart of a canvas) to the canvas keyboard response plugin. Thank you for taking the time to go through this |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
This is not going to be an easy one. We are going to have to break it up part from part. We start from defining some constants here, namely the size, color, and shape. const SIZE = [30, 60, 90]; // use radius to represent size
const COLOR = ['red', 'green', 'blue'];
const SHAPE = [
// triangle
(context, center_x, center_y, radius, color) => {
context.save();
context.fillStyle = color;
context.moveTo(center_x, center_y - radius);
context.beginPath();
context.lineTo(center_x + radius / 2 * Math.sqrt(3), center_y + radius / 2);
context.lineTo(center_x - radius / 2 * Math.sqrt(3), center_y + radius / 2);
context.lineTo(center_x, center_y - radius);
context.closePath();
context.fill();
context.restore();
},
// circle
(context, center_x, center_y, radius, color) => {
context.save();
context.fillStyle = color;
context.beginPath();
context.arc(center_x, center_y, radius, 0, Math.PI * 2);
context.closePath();
context.fill();
context.restore();
},
// square
(context, center_x, center_y, radius, color) => {
context.save();
context.fillStyle = color;
let len = radius * Math.sqrt(2);
context.fillRect(center_x - len / 2, center_y - len / 2, len, len);
context.restore();
}
];
const stimulus = [
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
]; The size and color is quite easy to understand. As for the shapes, we define them as functions that draw the corresponding shape on the canvas. The drawn shape can vary with the center, radius and color in the function arguments. We should then define the let trial_id = 0;
let record = []; Then comes the difficult part. I will put the code here first: let trial = {
type: jsPsychCanvasKeyboardResponse,
stimulus: (canvas) => {
let stim = stimulus[trial_id];
let context = canvas.getContext('2d');
let index = jspsych.randomization.shuffle([0, 1, 2]);
let record_entry = [];
// Draw the left part, which is the main stimulus
for (let i = 0; i < 3; i++) {
let size = SIZE[stim.stim[i][index[0]] - 1];
let color = COLOR[stim.stim[i][index[1]] - 1];
let draw_func = SHAPE[stim.stim[i][index[2]] - 1];
draw_func(context, i * 200 + 100, 300, size, color);
record_entry.push([size, color, draw_func]);
}
record.push(record_entry);
let image = new Image();
image.src = stim.stim[3];
// Image loading is asynchronous; you need to put it in an onload callback
image.onload = () => {
context.drawImage(image, 650, 250, 100, 100);
};
// Draw the right part
let start = Math.max(0, record.length - 5); // Show only, say, the last five
for (let i = start; i < record.length - 1; i++) {
let entry = record[i];
for (let j = 0; j < 3; j++) {
let s = entry[j];
let size = s[0] / 4;
let color = s[1];
let draw_func = s[2];
draw_func(context, j * 50 + 850, (i - start) * 50 + 75, size, color);
}
}
},
canvas_size: [600, 1000],
}; There is a lot to be explained here. First, what you want to do here can be done via another approach; that is to say, other than the one I have suggested here. As you have already a canvas, you can simply draw the stimulus on the left part of it and draw displayed shapes on the right side, so that you do not really need to mess up the DOM tree. let index = jspsych.randomization.shuffle([0, 1, 2]); Here, we are deciding which digit represents what. For example, if let record_entry = [];
for (let i = 0; i < 3; i++) {
let size = SIZE[stim.stim[i][index[0]] - 1];
let color = COLOR[stim.stim[i][index[1]] - 1];
let draw_func = SHAPE[stim.stim[i][index[2]] - 1];
draw_func(context, i * 200 + 100, 300, size, color);
record_entry.push([size, color, draw_func]);
}
record.push(record_entry); You can change the size and position as you wish, of course. Next, we need to draw the image. Image loading needs time, which is why it is async. Therefore, we need to wrap it up in an let image = new Image();
image.src = stim.stim[3];
// Image loading is asynchronous; you need to put it in an onload callback
image.onload = () => {
context.drawImage(image, 650, 250, 100, 100);
}; Then, you can draw the already displayed shapes on the right. As you can see above, we have stored the size, color and draw function, you can easily replicate whatever shapes the participant has already seen. The last step would be to wrap the thing up in a child timeline: jspsych.run([
{
timeline: [trial],
loop_function: () => {
trial_id++;
return trial_id < stimulus.length;
}
}
]); The complete code: let jspsych = initJsPsych();
const SIZE = [30, 60, 90]; // use radius to represent size
const COLOR = ['red', 'green', 'blue'];
const SHAPE = [
// triangle
(context, center_x, center_y, radius, color) => {
context.save();
context.fillStyle = color;
context.moveTo(center_x, center_y - radius);
context.beginPath();
context.lineTo(center_x + radius / 2 * Math.sqrt(3), center_y + radius / 2);
context.lineTo(center_x - radius / 2 * Math.sqrt(3), center_y + radius / 2);
context.lineTo(center_x, center_y - radius);
context.closePath();
context.fill();
context.restore();
},
// circle
(context, center_x, center_y, radius, color) => {
context.save();
context.fillStyle = color;
context.beginPath();
context.arc(center_x, center_y, radius, 0, Math.PI * 2);
context.closePath();
context.fill();
context.restore();
},
// square
(context, center_x, center_y, radius, color) => {
context.save();
context.fillStyle = color;
let len = radius * Math.sqrt(2);
context.fillRect(center_x - len / 2, center_y - len / 2, len, len);
context.restore();
}
];
const stimulus = [
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
{ stim: ['211', '132', '312', './temp.png'], correct: 0 },
{ stim: ['131', '322', '231', './temp.png'], correct: 1 },
]
let trial_id = 0;
let record = [];
let trial = {
type: jsPsychCanvasKeyboardResponse,
stimulus: (canvas) => {
let stim = stimulus[trial_id];
let context = canvas.getContext('2d');
let index = jspsych.randomization.shuffle([0, 1, 2]);
let record_entry = [];
// Draw the left part, which is the main stimulus
for (let i = 0; i < 3; i++) {
let size = SIZE[stim.stim[i][index[0]] - 1];
let color = COLOR[stim.stim[i][index[1]] - 1];
let draw_func = SHAPE[stim.stim[i][index[2]] - 1];
draw_func(context, i * 200 + 100, 300, size, color);
record_entry.push([size, color, draw_func]);
}
record.push(record_entry);
let image = new Image();
image.src = stim.stim[3];
// Image loading is asynchronous; you need to put it in an onload callback
image.onload = () => {
context.drawImage(image, 650, 250, 100, 100);
};
// Draw the right part
let start = Math.max(0, record.length - 5); // Show only, say, the last five
for (let i = start; i < record.length - 1; i++) {
let entry = record[i];
for (let j = 0; j < 3; j++) {
let s = entry[j];
let size = s[0] / 4;
let color = s[1];
let draw_func = s[2];
draw_func(context, j * 50 + 850, (i - start) * 50 + 75, size, color);
}
}
},
canvas_size: [600, 1000],
}
jspsych.run([
{
timeline: [trial],
loop_function: () => {
trial_id++;
return trial_id < stimulus.length;
}
}
]); |
Beta Was this translation helpful? Give feedback.
This is not going to be an easy one. We are going to have to break it up part from part.
We start from defining some constants here, namely the size, color, and shape.