// layout.js // general presentation functions for keeping items on screen during a draw loop // background and animated elements // PJR May 2020 var layout = { screen: [560,320,1], screen_elements: [], instruction_elements: [], stimulus_list: [], default_font: 'Arial', default_size: 24, default_color: 'black', default_align: 'center', init: function(window_name,screen_size) { let canvas = document.getElementById(window_name); this.ctx = canvas.getContext('2d'); this.screen=screen_size; this.instructions=true; this.background_elements=[]; this.stimulus_list=[]; }, add_background: function(elem) { this.screen_elements.push(elem); }, add_stimulus: function(stim) { this.stimulus_list.push(stim); }, add_anim: function(elem,frames) { this.screen_elements.push(elem); elem.animated=true; elem.frames=frames; }, add_text: function(text_list,start_pos,color='',size=0,align='') { var y=start_pos[1]; let text_color = (color === '')?this.default_color:color; let text_size = (size === 0)?this.default_size:size; let text_align = (align === '')?this.default_align:align; for(let i=0;i=(this.pos[0]-this.size[0]/2) && x<=(this.pos[0]+this.size[0]/2) && y>=(this.pos[1]-this.size[1]/2) && y<=(this.pos[1]+this.size[1]/2)); } return(x>this.pos[0] && x<(this.pos[0]+this.size[0]) && y>this.pos[1] && y<(this.pos[1]+this.size[1])); } this.position=function(x,y) { var f=(x-this.pos[0])/this.size[0]; if (this.elem_type === 'slider') { return Math.floor(this.options*f); } return(f); } // draws all screen elements this.draw=function() { if(this.elem_type === 'text') { layout.ctx.font = this.size.toString() + "px "+this.font; layout.ctx.fillStyle = this.color; layout.ctx.textAlign = this.align; layout.ctx.fillText(this.text, this.pos[0], this.pos[1]); } else if(this.elem_type === 'image') { layout.ctx.drawImage(this.image, this.pos[0], this.pos[1], this.size[0], this.size[1]); } else if(this.elem_type === 'zoom_image') { layout.ctx.drawImage(this.image, this.clip_start[0], this.clip_start[1], this.clip_size[0], this.clip_size[1], this.pos[0], this.pos[1], this.size[0], this.size[1]); } else if(this.elem_type === 'rotate_image') { layout.ctx.setTransform(1,0,0,1,this.pos[0]+this.size[0]/2,this.pos[1]+this.size[1]/2); layout.ctx.rotate(-this.rotate*Math.PI/180); layout.ctx.drawImage(this.image, -this.size[0]/2, -this.size[1]/2, this.size[0], this.size[1]); layout.ctx.setTransform(1,0,0,1,0,0); } else if(this.elem_type === 'circle') { layout.ctx.beginPath(); layout.ctx.fillStyle=this.color; layout.ctx.arc(this.pos[0], this.pos[1], this.size, 0, Math.PI*2, true); layout.ctx.closePath(); layout.ctx.fill(); } else if(this.elem_type === 'ring') { layout.ctx.strokeStyle = this.color; layout.ctx.beginPath(); layout.ctx.lineWidth=this.thick; layout.ctx.arc(this.pos[0], this.pos[1], this.size, 0, Math.PI*2, true); layout.ctx.closePath(); layout.ctx.stroke(); } else if(this.elem_type === 'rectangle') { layout.ctx.fillStyle = this.color; layout.ctx.fillRect(this.pos[0]-this.size[0]/2, this.pos[1]-this.size[1]/2, this.size[0], this.size[1]); } else if(this.elem_type === 'line') { layout.ctx.strokeStyle = this.color; layout.ctx.lineWidth=this.thick; layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0],this.pos[1]); layout.ctx.lineTo(this.size[0],this.size[1]); layout.ctx.stroke(); } else if(this.elem_type === 'triangle') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0], this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0]+this.size[0]/2, this.pos[1] + this.size[1]/2); layout.ctx.lineTo(this.pos[0]-this.size[0]/2, this.pos[1] + this.size[1]/2); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'diamond') { layout.ctx.fillStyle = this.color; layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0], this.pos[1] - this.size[0] / 2); layout.ctx.lineTo(this.pos[0] + this.size[0] / 2, this.pos[1]); layout.ctx.lineTo(this.pos[0], this.pos[1] + this.size[1]/2); layout.ctx.lineTo(this.pos[0] - this.size[0] / 2, this.pos[1]); layout.ctx.closePath(); layout.ctx.fill(); } else if(this.elem_type === 'heart') { var topCurveHeight = this.size[1] * 0.3; layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0] / 2, (this.pos[1] - this.size[1] / 2) + topCurveHeight); // top left curve layout.ctx.bezierCurveTo(this.pos[0] - this.size[0] / 2, this.pos[1] - this.size[1] / 2, this.pos[0], this.pos[1] - this.size[1]/2, this.pos[0], (this.pos[1] - this.size[1]/2) + topCurveHeight); // top right curve layout.ctx.bezierCurveTo(this.pos[0], this.pos[1] - this.size[1]/2, this.pos[0]+this.size[0]/2, this.pos[1] - this.size[1]/2, this.pos[0]+this.size[0]/2, (this.pos[1] - this.size[1]/2) + topCurveHeight); // bottom right curve layout.ctx.bezierCurveTo(this.pos[0] + this.size[0]/2, this.pos[1]+topCurveHeight/2, this.pos[0], this.pos[1]+topCurveHeight/2, this.pos[0],this.pos[1]+this.size[1]/2); // bottom left curve layout.ctx.bezierCurveTo(this.pos[0], this.pos[1]+topCurveHeight/2, this.pos[0]- this.size[0]/2, this.pos[1]+topCurveHeight/2, this.pos[0] - this.size[0]/2, this.pos[1] - this.size[1]/2 + topCurveHeight); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'plus') { var x_points = [this.pos[0] - this.size[0] / 2, this.pos[0] - this.size[0] * .15, this.pos[0] + this.size[0] * .15, this.pos[0] + this.size[0] / 2]; var y_points = [this.pos[1] - this.size[1] / 2, this.pos[1] - this.size[1] * .15, this.pos[1] + this.size[1] * .15, this.pos[1] + this.size[1] / 2]; var shape = [[1, 0], [2, 0], [2, 1], [3, 1], [3, 2], [2, 2], [2, 3], [1, 3], [1, 2], [0, 2], [0, 1], [1, 1]]; layout.ctx.moveTo(x_points[shape[0][0]], y_points[shape[0][1]]); layout.ctx.beginPath(); for (let i = 0; i < shape.length; i++) layout.ctx.lineTo(x_points[shape[i][0]], y_points[shape[i][1]]); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'pentagon') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0],this.pos[1]-this.size[1] * 0.45); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.07); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.3,this.pos[1]+this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.3,this.pos[1]+this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.07); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'hexagon') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0],this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.45, this.pos[1] - this.size[1]/4); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.45, this.pos[1] + this.size[1]/4); layout.ctx.lineTo(this.pos[0], this.pos[1] + this.size[1] / 2); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.45, this.pos[1] + this.size[1]/4); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.45, this.pos[1] - this.size[1]/4); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'trapezoid') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.4); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.4); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.25,this.pos[1]+this.size[1] * 0.4); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.25,this.pos[1]+this.size[1] * 0.4); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'arrowhead_down') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]); layout.ctx.lineTo(this.pos[0],this.pos[1]+this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0],this.pos[1]); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.5); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'hourglass') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]+this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]+this.size[1] * 0.5); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'block_l') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]-this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0],this.pos[1]-this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0],this.pos[1]); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]); layout.ctx.lineTo(this.pos[0] + this.size[0] * 0.5,this.pos[1]+this.size[1] * 0.5); layout.ctx.lineTo(this.pos[0] - this.size[0] * 0.5,this.pos[1]+this.size[1] * 0.5); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'block_t') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0]/2,this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/6,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/6,this.pos[1]+this.size[1]/2); layout.ctx.lineTo(this.pos[0] - this.size[0]/6,this.pos[1]+this.size[1]/2); layout.ctx.lineTo(this.pos[0] - this.size[0]/6,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] - this.size[0]/2,this.pos[1]-this.size[1]/6); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'block_c') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0]/2,this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] - this.size[0]/6,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] - this.size[0]/6,this.pos[1]+this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]+this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]+this.size[1]/2); layout.ctx.lineTo(this.pos[0] - this.size[0]/2,this.pos[1]+this.size[1]/2); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'block_i') { layout.ctx.beginPath(); layout.ctx.moveTo(this.pos[0] - this.size[0]/2,this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]-this.size[1]/2); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/6,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/6,this.pos[1]+this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]+this.size[1]/6); layout.ctx.lineTo(this.pos[0] + this.size[0]/2,this.pos[1]+this.size[1]/2); layout.ctx.lineTo(this.pos[0] - this.size[0]/2,this.pos[1]+this.size[1]/2); layout.ctx.lineTo(this.pos[0] - this.size[0]/2,this.pos[1]+this.size[1]/6); layout.ctx.lineTo(this.pos[0] - this.size[0]/6,this.pos[1]+this.size[1]/6); layout.ctx.lineTo(this.pos[0] - this.size[0]/6,this.pos[1]-this.size[1]/6); layout.ctx.lineTo(this.pos[0] - this.size[0]/2,this.pos[1]-this.size[1]/6); layout.ctx.closePath(); layout.ctx.fillStyle = this.color; layout.ctx.fill(); } else if(this.elem_type === 'arrow') { layout.ctx.beginPath(); layout.ctx.strokeStyle = this.color; layout.ctx.lineWidth = this.thick; layout.ctx.moveTo(this.pos[0],this.pos[1]); var end_x = this.pos[0]+this.size*Math.cos(this.direction); var end_y = this.pos[1]-this.size*Math.sin(this.direction); layout.ctx.lineTo(end_x,end_y); layout.ctx.stroke(); // arrow head end_x = this.pos[0]+(this.size+this.thick)*Math.cos(this.direction); end_y = this.pos[1]-(this.size+this.thick)*Math.sin(this.direction); layout.ctx.beginPath(); layout.ctx.strokeStyle = this.color; layout.ctx.fillStyle = this.color; layout.ctx.moveTo(end_x,end_y); var arrow_angle=0.87; layout.ctx.lineTo(end_x+(this.size/3)*Math.cos(this.direction-Math.PI*arrow_angle), end_y-(this.size/3)*Math.sin(this.direction-Math.PI*arrow_angle)); layout.ctx.lineTo(end_x+(this.size/3)*Math.cos(this.direction+Math.PI*arrow_angle), end_y-(this.size/3)*Math.sin(this.direction+Math.PI*arrow_angle)); //layout.ctx.lineTo(end_x,end_y); layout.ctx.lineTo(end_x,end_y); layout.ctx.closePath(); layout.ctx.fill(); } else if(this.elem_type === 'slider') { // draw this.options circles, connect with lines var circle_size = this.size[0]/((2*this.options)-1); //radius var y=this.pos[1]+this.size[1]/2; var x=this.pos[0]+circle_size/2; layout.ctx.strokeStyle = this.color; layout.ctx.fillStyle = this.color; layout.ctx.lineWidth=2; //todo: scale with slider size for(var i=0;i0) { // labels[0] is center let label_y = this.pos[1]; layout.ctx.font = circle_size + "px Arial"; //layout.ctx.font = (this.size[0]/9).toFixed(0)+"px Arial"; layout.ctx.fillStyle = 'black'; layout.ctx.textAlign = 'center'; layout.ctx.fillText(this.labels[0], this.pos[0]+((this.options-0.5)*circle_size), label_y); if (this.labels.length>1) { // anchor labels layout.ctx.textAlign = 'center'; layout.ctx.fillText(this.labels[1], this.pos[0]+circle_size/2, label_y); layout.ctx.fillText(this.labels[2], this.pos[0]+((this.options*2-1.5)*circle_size), label_y); } } } } }; // Kaleidoscope creating and drawing functions var kaleido=function() { this.poly_list=[]; this.scale=1.0; this.size=[0,0]; this.start=0; this.init_square=[-100,-100,-100,100,100,100,100,-100]; this.init_pentagon=[0,-100,-95,-31,-56,81,56,81,95,-31]; this.init_hexagon=[0,-100,-87,-50,-87,50,0,100,87,50,87,-50]; this.init=function(shape,layers,deflects=4,size=[200,200],scale=.7) { var curr_scale=1.0; this.scale=scale; this.size=size; if (shape === 'square' || shape === '4') { var init_shape=this.init_square.slice(); this.start=4; } else if(shape === 'pentagon' || shape === '5') { init_shape=this.init_pentagon.slice(); this.start=5; } else if(shape === 'triangle' || shape === '3' || shape === 'hexagon' || shape === '6') { init_shape=this.init_hexagon.slice(); this.start=6; } for(var i=0;i0;i=i-2) { this.poly.splice(i,0,0); this.poly.splice(i,0,0); } // duplicate beginning at end for math algorithm this.poly.push(this.poly[0]); this.poly.push(this.poly[1]); this.nv=this.poly.length/2; for(i=1; i < 1000 && i < this.nv; i=i+2) { var mx=(this.poly[(i-1)*2]+this.poly[(i+1)*2])/2; var my=(this.poly[(i-1)*2+1]+this.poly[(i+1)*2+1])/2; var dx=this.poly[(i+1)*2]-this.poly[(i-1)*2]; var dy=this.poly[(i+1)*2+1]-this.poly[(i-1)*2+1]; var theta=Math.atan2(dy,dx); //this.poly[i*2]=Math.round((mx+angle*Math.sin(theta))*10)/10; //this.poly[i*2+1]=Math.round((my-angle*Math.cos(theta))*10)/10; this.poly[i*2]=Math.round(mx+angle*Math.sin(theta)); this.poly[i*2+1]=Math.round(my-angle*Math.cos(theta)); } // remove duplicate point from end this.poly.pop(); this.poly.pop(); this.deflects.push(angle); }; this.draw=function(loc=[0,0]) { ctx.fillStyle = this.color; //ctx.strokeStyle = 'black'; ctx.beginPath(); ctx.moveTo(this.poly[0]+loc[0], this.poly[1]+loc[1]); for(var i=2;ix[1]) x[1]=this.poly[i]; if(this.poly[i+1]y[1]) y[1]=this.poly[i+1]; } var fx=(size[0]/(x[1]-x[0]))*frac; var fy=(size[1]/(y[1]-y[0]))*frac; for(i=0;i0;len--) r+=chars.charAt(Math.floor(Math.random()*chars.length)); return r; } function time_string(hours) { if(hours<1) return((hours*60).toFixed(0)+" min"); if(hours>24) { var days=Math.floor(hours/24); hours=hours-days*24; return(days.toFixed(0)+" days "+hours.toFixed(0)+" hours"); } return(hours.toFixed(1)+" hours"); }