-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpointing.html
More file actions
433 lines (374 loc) · 16.3 KB
/
Copy pathpointing.html
File metadata and controls
433 lines (374 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pointing Task</title>
<script src="/socket.io/socket.io.js"></script>
<style>
* { margin: 0px; padding: 0px; }
canvas { display:block; background-color: #aaaaaa; }
</style>
</head>
<body>
<canvas id='main'></canvas>
<button onClick="launchFullscreen(document.documentElement)" style="top: 0; left: 0; position: absolute; width:100%;">Full Screen(Press Esc to Exit Full Screen)</button>
<script>
// ---------Trial Variables--------------
var number_of_targets = 10;
var number_of_blocks = 4;
var center_diameters = [40, 60, 80]; // Diameter is % of Canvas Height, These are the Diameter from the center to the Targets
var target_diameters = [3, 6, 12]; // Diameter is % of Canvas Height, These are the Target diameters
var invisibleMode = true; // true will display only the start and destination targets
var half_invisible_half_visible = true; // This overrides the invisibleMode value, if true half the blocks will be invisible
var debugMode = false; // WARNING: In debug mode you will not have 120Hz Tracking, closer to ~50Hz tracking
var finishMessage = "You have finished the pointing task.\n\nPlease press OK to continue to the next survey. ";
var welcomeMessage = "Welcome to the Pointing Study.\n\nBEFORE starting please make sure to press the FULLSCREEN button at the top of the page!";
var breakMessage = "Feel free to take a break now.\nPress OK to continue.";
// ---------Trial Variables--------------
var canvas = document.getElementById('main');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/* Target Class
* (x,y) - Coordinates
* r(px) - Radius
* id - Identification Number
*/
function Target(x, y, r, id) {
this.x = x;
this.y = y;
this.r = r;
this.id = id;
this.color = "white";
}
Target.prototype.draw = function(){
if(start == this.id){ // Start target is purple
ctx.fillStyle = "purple";
}
else if(dest == this.id){ // Destination target is green
ctx.fillStyle = "green";
}
else{
ctx.fillStyle = this.color;
}
ctx.globalAlpha = 0.9;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
if(this.id == dest){
// Draws the purple cross on destination target
ctx.strokeStyle = "purple";
ctx.moveTo(this.x, this.y - this.r);
ctx.lineTo(this.x, this.y + this.r);
ctx.moveTo(this.x - this.r, this.y);
ctx.lineTo(this.x + this.r, this.y);
}
ctx.fill();
ctx.stroke();
if(debugMode){
// Writes the target id on each target
ctx.fillStyle = 'black';
ctx.font = "24px Georgia";
ctx.fillText(this.id, this.x, this.y);
}
}
/* Trial Class
* cen(px) - Diameter from center of screen to center of target
* tar(px) - Diameter of the
*/
function Trial(cen, tar){
this.cen = cen;
this.tar = tar;
}
window.addEventListener('load', onWindowLoad, false);
window.addEventListener('resize', onResized, false);
function onResized(){
// Get new canvas dimensions
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if(!doneStudy){
// Reload the trial
Targets = [];
trial_cen = canvas.height*(Trials[currentTrial].cen/200);
trial_tar = canvas.height*(Trials[currentTrial].tar/200);
for (var i = 0; i < number_of_targets; i++) {
var temp = new Target(canvas.width/2 + Math.cos(i*angleIncr)*trial_cen, canvas.height/2 + Math.sin(i*angleIncr)*trial_cen, trial_tar, i);
Targets.push(temp);
}
}
canvasDraw();
}
// Arrays to hold Targets and Trials
var Targets = [];
var Trials = [];
// Object to hold the mouse position
var mouse = {x: 0, y: 0};
var stopLog = true;
document.addEventListener('mousemove', function(e){
// Get mouse position, for all kinds of browsers
mouse.x = e.clientX || e.pageX || (e.clientX - canvas.offsetLeft);
mouse.y = e.clientY || e.pageY || (e.clientY - canvas.offsetTop);
if(debugMode){
// Draw a mouse trail
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, canvas.width*.0005, 0, 2*Math.PI);;
ctx.fill();
ctx.stroke();
}
if(!stopLog){
// Log mouse movement
socket.emit('moveLog', PID + "; " + trial_tar + "; " + trial_cen + "; " + Trials[currentTrial].tar + "; " + Trials[currentTrial].cen + "; " + number_of_targets + "; " + dest + "; " + mouse.x +
"; " + mouse.y + "; " + Date.now() + "; " + browserName + "; " + OSName + "; " + (invisibleMode ? 0 : 1) + "; " + (currentBlock + 1) + "\n");
}
}, false);
var mousedown = false,
mousedownTime = 0;
document.addEventListener('mousedown', function(e){
mousedown = true;
mousedownTime = Date.now();
}, false);
var oldStart,
errors = 0,
doneStudy = false,
skipFirst = true, // When the trial starts you don't want to log the first target click, you log the second time the target is clicked
currentBlock = 0;
document.addEventListener('mouseup', function(e){
if(mousedown){ // Check if mouse was previously down, this indicates a click
mousedown = false;
for (var i = 0; i < Targets.length; i++) {
if(Targets[i].id == dest){
// Check if distance between mouse click and center of target is less than the target radius
if((Math.sqrt((Math.pow(((e.x || (e.clientX - canvas.offsetLeft)) - Targets[i].x),2))+(Math.pow(((e.y || (e.clientY - canvas.offsetTop)) - Targets[i].y),2)))) <= trial_tar){
stopLog = false; // Enable logging
endTime = Date.now();
if(!skipFirst){
// Log mouse click data
socket.emit('clickLog', PID + "; " + trial_tar + "; " + trial_cen + "; " + Trials[currentTrial].tar + "; " + Trials[currentTrial].cen + "; " + number_of_targets + "; " + dest + "; " + startTime + "; " +
endTime + "; " + mousedownTime + "; " + errors + "; " + browserName + "; " + OSName + "; " + (invisibleMode ? 0 : 1) + "; " + (currentBlock + 1) + "\n");
}
else{
skipFirst = false;
}
errors = 0;
startTime = endTime;
oldStart = start;
start = dest;
if(dest == 0){ // Indicates end of a trial
skipFirst = true;
currentTrial++;
currentTrial %= (center_diameters.length * target_diameters.length);
if(currentTrial == 0){ // Indicates end of a block
currentBlock++;
if(currentBlock == number_of_blocks){
// Happens when the entire trial is over
doneStudy = true;
alert(finishMessage);
window.location.replace("https://hci-mturk.usask.ca/submitted");
}
else if(currentBlock == number_of_blocks/2 && half_invisible_half_visible){
// If half done the blocks and doing half invisible half visible
invisibleMode = !invisibleMode;
}
}
stopLog = true; // Trial is over, don't want to log inbetween trials
reloadTrial();
}
else{
// Iterate through the trial
dest = (oldStart == number_of_targets - 1) ? 0 : oldStart + 1;
canvasDraw();
}
break;
}
else{ // Missed the target
errors += 1;
}
}
}
}
}, false);
var socket = io(); // Socket connection to the server
var angleIncr; // Will hold angle between targets
var start = 0;
var dest = 0;
var startTime = Date.now();
var endTime = 0;
var currentTrial = 0;
var trial_cen = 0;
var trial_tar = 0;
var PID = 0;
var browserName = "";
var OSName = "Unknown OS";
function onWindowLoad() {
detectBrowserOS();
// Get PID number from external source
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == "PID" || pair[0] == "pid" || pair[0] == "Pid" ) {
PID = pair[1];
}
}
// PID = Date.now() % 1000;
// Tell server PID number
socket.emit("init", PID);
// Hide the information passed in the URL
window.history.replaceState("Hello", "World", "PointingTask");
alert(welcomeMessage);
ctx = canvas.getContext('2d');
// Determines invisibility based on parity of PID
if(half_invisible_half_visible){
if(PID%2 == 1){
invisibleMode = true;
}
else{
invisibleMode = false;
}
}
// Initialize Trials
for (var i = 0; i < target_diameters.length; i++) {
for (var j = 0; j < center_diameters.length; j++) {
Trials.push(new Trial(center_diameters[j], target_diameters[i]));
}
}
shuffleTrial();
angleIncr = 2*Math.PI/number_of_targets; // Angle between targets
trial_cen = canvas.height*(Trials[currentTrial].cen/200); // Radius from screen center to target center for the trial
trial_tar = canvas.height*(Trials[currentTrial].tar/200); // Radius of target in trial
dest = Math.ceil(number_of_targets/2);
// Initialize Targets for trial
for (var i = 0; i < number_of_targets; i++) {
var temp = new Target(canvas.width/2 + Math.cos(i*angleIncr)*trial_cen, canvas.height/2 + Math.sin(i*angleIncr)*trial_cen, trial_tar, i);
Targets.push(temp);
}
canvasDraw();
}
// Initializes the next trial
function reloadTrial(){
angleIncr = 2*Math.PI/number_of_targets;
trial_cen = canvas.height*(Trials[currentTrial].cen/200);
trial_tar = canvas.height*(Trials[currentTrial].tar/200);
start = 0;
dest = Math.ceil(number_of_targets/2);
Targets = []; // Empty the old targets
canvasDraw();
if(!doneStudy){
alert(breakMessage);
// Initialize new targets
for (var i = 0; i < number_of_targets; i++) {
var temp = new Target(canvas.width/2 + Math.cos(i*angleIncr)*trial_cen, canvas.height/2 + Math.sin(i*angleIncr)*trial_cen, trial_tar, i);
Targets.push(temp);
}
canvasDraw();
}
}
// Redraws the canvas
function canvasDraw(){
// Clear old canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < Targets.length; i++) {
if(invisibleMode){
// If trial is in invisible mode, only draw start and destination target
if(Targets[i].id == start || Targets[i].id == dest){
Targets[i].draw();
}
}
else{
Targets[i].draw();
}
}
if(debugMode){
// Write the debugging text
ctx.fillStyle = 'black';
ctx.font = "24px Georgia";
ctx.fillText("Canvas Height: " + canvas.height + " Canvas Width: " + canvas.width +" Current Trial: " + currentTrial +" Current Block:" + currentBlock + " PID:" + PID, 0 , canvas.height*.1);
}
}
// Shuffles Trials within each block by repeatedly picking random 2 trial positions to swap
function shuffleTrial(){
var shuffle_one = Math.floor(Math.random() * Trials.length);
var shuffle_two = Math.floor(Math.random() * Trials.length);
var temp = null;
for(var i = 0 ; i < 10; ++i){
temp = Trials[shuffle_one];
Trials[shuffle_one] = Trials[shuffle_two];
Trials[shuffle_two] = temp;
shuffle_one = Math.floor(Math.random() * Trials.length);
shuffle_two = Math.floor(Math.random() * Trials.length);
}
}
// Function to make the canvas fullscreen
// Code is from a JS Fullscreen API
function launchFullscreen(element) {
if(element.requestFullscreen) {
element.requestFullscreen();
}
else if(element.mozRequestFullScreen) {
element.mozRequestFullScreen();
}
else if(element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
}
else if(element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
// Function to detect browser information, and OS information
function detectBrowserOS(){
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var fullVersion = ''+parseFloat(navigator.appVersion);
var majorVersion = parseInt(navigator.appVersion,10);
var nameOffset,verOffset,ix;
if ((verOffset=nAgt.indexOf("Opera"))!=-1) {
browserName = "Opera";
fullVersion = nAgt.substring(verOffset+6);
if ((verOffset=nAgt.indexOf("Version"))!=-1){
fullVersion = nAgt.substring(verOffset+8);
}
}
else if ((verOffset=nAgt.indexOf("MSIE"))!=-1) {
browserName = "Microsoft Internet Explorer";
fullVersion = nAgt.substring(verOffset+5);
}
else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) {
browserName = "Chrome";
fullVersion = nAgt.substring(verOffset+7);
}
else if ((verOffset=nAgt.indexOf("Safari"))!=-1) {
browserName = "Safari";
fullVersion = nAgt.substring(verOffset+7);
if ((verOffset=nAgt.indexOf("Version"))!=-1){
fullVersion = nAgt.substring(verOffset+8);
}
}
else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) {
browserName = "Firefox";
fullVersion = nAgt.substring(verOffset+8);
}
else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < (verOffset=nAgt.lastIndexOf('/')) ) {
browserName = nAgt.substring(nameOffset,verOffset);
fullVersion = nAgt.substring(verOffset+1);
if (browserName.toLowerCase()==browserName.toUpperCase()) {
browserName = navigator.appName;
}
}
if ((ix=fullVersion.indexOf(";"))!=-1){
fullVersion=fullVersion.substring(0,ix);
}
if ((ix=fullVersion.indexOf(" "))!=-1){
fullVersion=fullVersion.substring(0,ix);
}
majorVersion = parseInt(''+fullVersion,10);
if (isNaN(majorVersion)) {
fullVersion = ''+parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion,10);
}
if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
}
</script>
</body>
</html>