diff --git a/.gitignore b/.gitignore index a029bd09..dd78b64f 100755 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ gfginteractive.egg-info/ venv/ config.ini gfg_interactive/config.py +.idea* diff --git a/.idea/gfginteractive.iml b/.idea/gfginteractive.iml deleted file mode 100755 index eebadf9b..00000000 --- a/.idea/gfginteractive.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100755 index 6530101f..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100755 index 3b312839..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100755 index 70858dae..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100755 index 502e7b24..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100755 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100755 index 51e1e60a..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,897 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - DEFINITION_ORDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - project - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1470765640243 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gfg_interactive/dashboard/static/img/alejandro.jpg b/gfg_interactive/dashboard/static/img/alejandro.jpg deleted file mode 100755 index 42a65fcb..00000000 Binary files a/gfg_interactive/dashboard/static/img/alejandro.jpg and /dev/null differ diff --git a/gfg_interactive/db_utils.py b/gfg_interactive/db_utils.py index 489fbb3b..ef38d80f 100755 --- a/gfg_interactive/db_utils.py +++ b/gfg_interactive/db_utils.py @@ -48,4 +48,4 @@ def get_age_matched_ids(userid, db_host, db_user, db_password, db_name): cur.execute("select id from FBApp_Users_Ids where age_range = %d" % int(age_range[0])) db.close() - return cur.fetchall() \ No newline at end of file + return cur.fetchall() diff --git a/gfg_interactive/exp/static/BART/css/BART.css b/gfg_interactive/exp/static/BART/css/BART.css new file mode 100644 index 00000000..fdc82c8f --- /dev/null +++ b/gfg_interactive/exp/static/BART/css/BART.css @@ -0,0 +1,142 @@ + +#task-container { + + height: 100%; + min-height: 100%; + display: block; + border: 2px solid black; + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; + margin-top: 20px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + overflow: hidden; + min-height: 100%; + height: 100%; +} + +html, body { + height: 90%; +} + +.fill { + min-height: 100%; + height: 100%; +} + +#balloon-image { + position: absolute; + top: 45%; + left: 50%; + -webkit-transform: translate(-45%, -50%); + -moz-transform: translate(-45%, -50%); + -ms-transform: translate(-45%, -50%); + -o-transform: translate(-45%, -50%); + transform: translate(-45%, -50%); + height: 10%; +} + +#result-text { + position: absolute; + left: 50%; + top: 30%; + transform: translate(-50%,-30%); + opacity: 0; + text-align: center; + width: 400px; + +} +.action-text { + position:absolute; + left: 50%; + text-align: center; + width: 400px; + +} + +body { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +#pump-text { + top: 60%; + transform: translate(-50%, -60%); + color: #c6c6c6; + opacity: 0; +} + +#cash-text { + top: 20%; + transform: translate(-50%, -20%); + color: white; +} + + +#instructions-box { + position: absolute; + height: 200px; + overflow: scroll; + left: 60%; + top: 23%; + width: 30%; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + background-color: whitesmoke; + border: 2px solid black; + padding: 10px; + opacity: 0; + text-align: center; + z-index: 10; +} + + +#chart { + position: absolute; + height:60%; + width:80%; + top: 20%; + left: 10%; + visibility: hidden; + opacity: 0; +} + +#chart-border { + position: absolute; + height:61%; + width:81%; + top: 20%; + left: 9.5%; + border-left: 5px solid black; + border-bottom: 5px solid black; + visibility: hidden; + opacity: 0; + +} + +.chart-bar { + background-color: #40b1b9; + position: absolute; + height: 10%; + width: 5%; + border: 1px solid black; + bottom: 0; +} + +.chart-action { + background-color: transparent; + position: absolute; + height: 100%; + width: 5%; + bottom: 0; +} + +#progress-container { + position: absolute; + height: 10%; + width: 20%; + top: 5%; + right:5%; +} + diff --git a/gfg_interactive/exp/static/BART/css/task_style.css b/gfg_interactive/exp/static/BART/css/task_style.css deleted file mode 100755 index cbe819aa..00000000 --- a/gfg_interactive/exp/static/BART/css/task_style.css +++ /dev/null @@ -1,93 +0,0 @@ - - -#container{ - position: absolute; - height:80%; - width:50%; - top:50%; - left:50%; - -webkit-transform: translate(-50%,-50%); - -moz-transform: translate(-50%,-50%); - -ms-transform: translate(-50%,-50%); - -o-transform: translate(-50%,-50%); - transform: translate(-50%,-50%); - border: 4px solid black; - border-radius: 15px; - overflow: hidden; -} - -img{ - position: absolute; - bottom:30%; - left: 50%; - -webkit-transform: translate(-50%, -5%); - -moz-transform: translate(-50%, -5%); - -ms-transform: translate(-50%, -5%); - -o-transform: translate(-50%, -5%); - transform: translate(-50%, -5%); -} - -#balloonIm{ - height: 20px; - width: 20px; -} -#poppedIm{ - height:0; - width:0; -} - -#pumpContainer{ - background-color: white; - height: 80%; - top: 0; -} - -#cashContainer{ - background-color: mediumspringgreen; - height: 20%; - bottom: 0; - border-top: 2px dashed black; - align-items: center; - display: flex; -} - -#cashText{ - font: 40px Helvetica; - color: white; - margin:auto; -} - -#pumpText { - font: 20px arial black; - left: 50%; - top: 2%; - position: absolute; - -webkit-transform: translate(-50%, -0%); - -moz-transform: translate(-50%, -0%); - -ms-transform: translate(-50%, -0%); - -o-transform: translate(-50%, -0%); - transform: translate(-50%, -0%); -} - -#tokenText{ - top: 55%; - left: -50%; - font: 40px arial black; - color: green; - position: absolute; - -webkit-transform: translate(-50%,-50%); - -moz-transform: translate(-50%,-50%); - -ms-transform: translate(-50%,-50%); - -o-transform: translate(-50%,-50%); - transform: translate(-50%,-50%); - white-space: nowrap; -} - -div{ --webkit-touch-callout: none; --webkit-user-select: none; --khtml-user-select: none; --moz-user-select: none; --ms-user-select: none; -user-select: none; -} \ No newline at end of file diff --git a/gfg_interactive/exp/static/BART/js/BART_task.js b/gfg_interactive/exp/static/BART/js/BART_task.js new file mode 100644 index 00000000..ff41a252 --- /dev/null +++ b/gfg_interactive/exp/static/BART/js/BART_task.js @@ -0,0 +1,514 @@ + +var datahandler = DataHandler(sessionid); + +function popAnimation() { + $("#balloon-image").css({opacity: '0'}); + $("#token-text").text('0 Tokens'); + $('#task-container') + .animate({backgroundColor: '#ff7272'}, 100); + $('#result-text').text('Popped!').css({color: '#72090C'}) + .animate({opacity: '1'}); + $('#task-container') + .animate({backgroundColor: 'white'}); + $('#cash-box').animate({backgroundColor: '#B3B3B3'}); +} + +function resetBalloon() { + $('#cash-box').animate({backgroundColor:'#7bb37e'}); + $('#cash-text').html('Cash In'); + $("#token-text").text( '0 Tokens'); + $("#balloon-image") + .css({ + top: '45%', + left: '50%', + height: '10%' + }) + .animate({opacity: '1'}); + $("#result-text") + .animate({opacity: '0'}); +} + +function cashDisplay() { + $("#balloon-image").css({opacity: '0'}); + $('#task-container') + .animate({backgroundColor: '#41B96B'}, 100); + $('#result-text').text('cashed in').css({color: 'green'}) + .animate({opacity: '1'}); + $('#task-container') + .animate({backgroundColor: 'white'}); + $('#cash-box').animate({backgroundColor:'#B3B3B3'}); +} + +BART_TUTORIAL = function() { + Bart_tutorial = (function () { + function BART_tutorial() { + this.lastClick = new Date().getTime(); + console.log(this.lastClick); + this.flashinterval = null; + this.status = null; + this.tokens = 0; + this.popPoint = 10; + this.active = true; + + this.popList = [4,40,12,20,32,48,52,1,60,24,64,28,8,56,36,44,16]; + this.autotrial = 0; + this.maxSize = 0; + this.isDown = false; + this.barValues = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + + BART_tutorial.prototype.onboarding = function () { + this.changeStatus('onboarding'); + $('#continue-instruction').show(); + $("#back-instruction").hide(); + this.displayInstruction( + ' Welcome to the BART ' + + '

Throughout this task, you will be presented with 15 balloons, one at a time.' + + '

You will be asked to inflate these balloons. Every time you choose to inflate the balloon,' + + ' it will grow slightly and you will receive one token. ' + ); + }; + + BART_tutorial.prototype.learntopump = function () { + resetBalloon(); + tutorial.active = true; + tutorial.tokens = 0; + this.changeStatus('learntopump'); + $('#back-instruction').show(); + $('#continue-instruction').hide(); + this.displayInstruction( + "To inflate the balloon click/tap anywhere in the white space of this container (or press the spacebar).

Go ahead and pump as many times as you'd like." + ); + $('#pump-text').delay(1000).animate({opacity: '1'}); + }; + + BART_tutorial.prototype.watchrangeIntro = function () { + this.changeStatus('onboardWatch'); + this.displayInstruction( + "We'd like you to now watch several balloons grow to their maximum sizes, to get a feel for what sorts" + + " of balloons you might encounter.

Click or tap on this box to view these balloons inflate." + ); + }; + + BART_tutorial.prototype.watchrange = function (required) { + var self = this; + var tokens = 0; + var popat = this.popList[required]; + window.setTimeout(function () { + resetBalloon(); + }, 1000); + + if (this.autotrial == required) { + window.setTimeout(function () { + var interv = setInterval(function () { + tokens++; + $("#balloon-image").animate({height: String( 10 + (tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(tokens) + ' Tokens'); + if (tokens == popat) { + $('#progress').css({width: (1+required) * 6.25.toString() + '%'}); + clearInterval(interv); + popAnimation(); + self.incrementAuto(); + $("#back-instruction").hide(); + if (required < self.popList.length - 1) { + self.watchrange(required + 1); + } else { + self.displayInstruction( + "Now that you've seen what sorts of balloons you might encounter in this task," + + " we'd like to ask you what you think the largest any balloon could grow to is." + ); + self.changeStatus('maxRating'); + } + } + }, 120) + }, 1500) + } + }; + + // Trial helper + BART_tutorial.prototype.incrementAuto = function () { + this.autotrial++; + }; + + BART_tutorial.prototype.changeStatus = function (status) { + this.status = status; + }; + + BART_tutorial.prototype.maxRating = function () { + $('#continue-instruction').hide(); + this.changeStatus('inputMax'); + this.displayInstruction('Before we move on, please tell us what you think the largest a balloon can grow to is (in number of pumps).'); + $('#instructions-box').delay(200).append(' '); + }; + + BART_tutorial.prototype.distribution = function () { + $('#balloon-image').hide(); + $('#token-text').hide(); + $('#cash-box').hide(); + $('#result-text').hide(); + $('#instructions-box').animate({opacity: '0'}).hide(); + $("#coin_distribution").show(); + Distribution_init(tutorial.maxSize); + tutorial.changeStatus('distributeGo'); + }; + + + BART_tutorial.prototype.submitMax = function () { + tutorial.changeStatus('Distribution'); + tutorial.maxSize = parseInt($('#maxSize')[0].value); + $('input').hide().remove(); + tutorial.distribution(); + }; + + BART_tutorial.prototype.removeChart = function () { + $('#coin_distribution').animate({opacity: '0'}, function () { + $('#coin_distribution').remove(); + addAndSubmitData(); + }); + resetBalloon(); + $('#balloon-image').show(); + $('#token-text').show(); + $('#cash-box').show(); + $('#result-text').show(); + $('#pump-box').show(); + $('#continue-instruction').hide(); + $('#instructions-box').animate({opacity: '0'}).show(); + tutorial.changeStatus('learntocash'); + tutorial.displayInstruction( + "To save your tokens before a balloon pops, you may choose to 'cash in'.

To cash in, click/tap anywhere " + + "inside of the green 'cash in' box at the bottom of this container (or press the return key).

Go ahead and pump this balloon" + + " to 10 tokens and cash in." + ); + tutorial.active = true; + tutorial.tokens = 0; + }; + + BART_tutorial.prototype.displayInstruction = function (message) { + var message = message; + $('#instructions-box').animate({opacity: '0'}, 750, function () { + $('#instructions-text').html(message); + }).animate({opacity: '1'}, {duration: 750}); + }; + + BART_tutorial.prototype.checkTime = function () { + clearInterval(this.flashinterval); + this.lastClick = new Date().getTime(); + var lastClick = this.lastClick; + this.flashinterval = window.setInterval(function () { + if (new Date().getTime() - lastClick > 7500) { + $('#pump-text') + .finish() + .animate({opacity: '1'}) + .delay(500) + .animate({opacity: '0'}); + } + }, 2000) + }; + return BART_tutorial; + })(); + + + BART_task = (function() { + function BART_task(){ + datahandler.finishInstructions(); + this.flashinterval = null; + this.proceed = false; + this.trial = 0; + this.tokens = 0; + this.active = false; + + } + + + + BART_task.prototype.newTrial = function() { + if (this.trial <= 14) { + this.trial++; + this.tokens = 0; + this.popPoint = Math.floor((Math.random() * 63) + 1); + resetBalloon(); + this.active = true; + this.proceed = false; + }else { + console.log('end'); + datahandler.saveData(); + datahandler.completeTask(); + window.setTimeout(function () { + datahandler.exitTask(); + }, 1500); + } + }; + + BART_task.prototype.checkTime = function () { + clearInterval(this.flashinterval); + this.lastClick = new Date().getTime(); + var lastClick = this.lastClick; + this.flashinterval = window.setInterval(function () { + if (new Date().getTime() - lastClick > 7500) { + $('#pump-text') + .finish() + .animate({opacity: '1'}) + .delay(500) + .animate({opacity: '0'}); + } + }, 2000) + }; + + return BART_task; + })(); + + + + + tutorial = new Bart_tutorial(); + tutorial.onboarding(); + Task = new BART_task(); + + + + $('#continue-instruction').click(function() { + if (tutorial.status === 'onboarding') { + tutorial.learntopump(); + } else if (tutorial.status === 'popped') { + tutorial.watchrangeIntro(); + } else if (tutorial.status === 'onboardWatch') { + $('#progress-container').css({visibility:'visible'}); + $('#instructions-box').animate({opacity:'0'}); + + tutorial.watchrange(0); + } else if (tutorial.status === 'maxRating'){ + $('#progress-container').css({visibility:'hidden'}); + tutorial.maxRating(); + } else if (tutorial.status === 'preTask') { + tutorial.changeStatus('done'); + $('#instructions-box').hide(); + Task.newTrial(); + } else if (tutorial.status === 'endLearnCash') { + tutorial.displayInstruction( + "At the end of this task we will compare your average tokens for cashed in balloons with other individuals who have also completed this task." + + "

Ready to start? Click/tap this box." + ); + tutorial.changeStatus('preTask'); + } + }); + $('#back-instruction').click(function() { + if (tutorial.status === 'learntopump') { + tutorial.onboarding(); + } else if (tutorial.status === 'popped' || tutorial.status == 'onboardWatch') { + tutorial.learntopump(); + } + }); + + + $("#task-container").mousedown(function() { + tutorial.isDown = true; + }).mouseup(function(){ + tutorial.isDown = false; + }); + + document.body.onkeyup = function(e){ + if(e.keyCode == 32){ + if (tutorial.status === 'learntopump') { + if (tutorial.active) { + console.log('yes'); + tutorial.tokens++; + $("#balloon-image").animate({height: String( 10 + (tutorial.tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(tutorial.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, tutorial.checkTime()); + + if (tutorial.tokens === 7) { + tutorial.changeStatus('popped'); + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + tutorial.active = false; + $('#continue-instruction').show(); + tutorial.displayInstruction( + ' Oh no! It looks like the balloon popped.

' + + 'Every balloon that you inflate will have a different maximum size that it can grow to.' + + ' Once the balloon gets to its max size, it will pop and you will lose your current tokens.' + ); + popAnimation(); + } + } + } else if (tutorial.status === 'learntocash' && tutorial.tokens < 10){ + console.log('hi'); + if (tutorial.active){ + tutorial.tokens++; + $("#balloon-image").animate({height: String( 10 + (tutorial.tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(tutorial.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, Task.checkTime()); + } + } else if (Task.active){ + datahandler.recordTrialData({ + 'balloon_num': Task.trial, + 'action': 1, + 'pumps': Task.tokens, + 'pop_point': Task.popPoint, + 'dist': '_' + }); + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + console.log(Task.tokens); + Task.tokens++; + $("#balloon-image").animate({height: String( 10 + (Task.tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(Task.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, Task.checkTime()); + if (Task.tokens === Task.popPoint) { + datahandler.recordTrialData({ + 'balloon_num': Task.trial, + 'action': 0, + 'pumps': Task.tokens, + 'pop_point': Task.popPoint, + 'dist': '_' + }); + Task.active = false; + popAnimation(); + $('#cash-text').html('Next Balloon'); + Task.proceed = true; + + } else if (Task.proceed) { + Task.newTrial(); + } + } + } + else if(e.keyCode == 13){ + if (tutorial.status === 'learntocash' && tutorial.tokens == 10) { + if (tutorial.active && tutorial.tokens != 0) { + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + tutorial.active = false; + cashDisplay(); + $('#continue-instruction').show(); + $('#back-instruction').show(); + tutorial.displayInstruction( + "And that's all there is to it!

In this task you will be presented with 15 balloons." + + " For each balloon you can choose to pump the balloon to whatever size you like before cashing in" + + " your tokens. But remember, if you pump the balloon too much, it will explode and you will lose your tokens." + ); + tutorial.changeStatus('endLearnCash'); + } + } else if (Task.active && Task.tokens != 0){ + datahandler.recordTrialData({ + 'balloon_num': Task.trial, + 'action': 2, + 'pumps': Task.tokens, + 'pop_point': Task.popPoint, + 'dist': '_' + }); + + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + Task.active = false; + cashDisplay(); + $('#cash-text').html('Next Balloon'); + Task.proceed = true; + } else if (Task.proceed) { + Task.newTrial(); + } + } + }; + + $("#pump-box").click(function() { + if (tutorial.status === 'learntopump') { + if (tutorial.active) { + tutorial.tokens++; + $("#balloon-image").animate({height: String( 10 + (tutorial.tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(tutorial.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, tutorial.checkTime()); + + if (tutorial.tokens === 7) { + tutorial.changeStatus('popped'); + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + tutorial.active = false; + $('#continue-instruction').show(); + tutorial.displayInstruction( + ' Oh no! It looks like the balloon popped.

' + + 'Every balloon that you inflate will have a different maximum size that it can grow to.' + + ' Once the balloon gets to its max size, it will pop and you will lose your current tokens.' + ); + popAnimation(); + } + } + } else if (tutorial.status === 'learntocash' && tutorial.tokens < 10){ + console.log('hi'); + if (tutorial.active){ + tutorial.tokens++; + $("#balloon-image").animate({height: String( 10 + (tutorial.tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(tutorial.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, Task.checkTime()); + } + } else if (Task.active){ + datahandler.recordTrialData({ + 'balloon_num': Task.trial, + 'action': 1, + 'pumps': Task.tokens, + 'pop_point': Task.popPoint, + 'dist': '_' + }); + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + console.log(Task.tokens); + Task.tokens++; + $("#balloon-image").animate({height: String( 10 + (Task.tokens /1.5)) + '%'}, 50); + $("#token-text").text(String(Task.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, Task.checkTime()); + if (Task.tokens === Task.popPoint) { + datahandler.recordTrialData({ + 'balloon_num': Task.trial, + 'action': 0, + 'pumps': Task.tokens, + 'pop_point': Task.popPoint, + 'dist': '_' + }); + Task.active = false; + popAnimation(); + $('#cash-text').html('Next Balloon'); + Task.proceed = true; + + } else if (Task.proceed) { + Task.newTrial(); + } + } + }); + + + $('#cash-box').click(function(){ + + if (tutorial.status === 'learntocash' && tutorial.tokens == 10) { + if (tutorial.active && tutorial.tokens != 0) { + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + tutorial.active = false; + cashDisplay(); + $('#continue-instruction').show(); + tutorial.displayInstruction( + "And that's all there is to it!

In this task you will be presented with 15 balloons." + + " For each balloon you can choose to pump the balloon to whatever size you like before cashing in" + + " your tokens. But remember, if you pump the balloon too much, it will explode and you will lose your tokens." + ); + tutorial.changeStatus('endLearnCash'); + } + } else if (Task.active && Task.tokens != 0){ + datahandler.recordTrialData({ + 'balloon_num': Task.trial, + 'action': 2, + 'pumps': Task.tokens, + 'pop_point': Task.popPoint, + 'dist': '_' + }); + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + Task.active = false; + cashDisplay(); + $('#cash-text').html('Next Balloon'); + Task.proceed = true; + } else if (Task.proceed) { + Task.newTrial(); + } + }); +}(); + + + + diff --git a/gfg_interactive/exp/static/BART/js/TestingFiled.html b/gfg_interactive/exp/static/BART/js/TestingFiled.html new file mode 100644 index 00000000..710f97c9 --- /dev/null +++ b/gfg_interactive/exp/static/BART/js/TestingFiled.html @@ -0,0 +1,37 @@ + + + + + + + + + TestingFild + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/gfg_interactive/exp/static/BART/js/coin_distributor.js b/gfg_interactive/exp/static/BART/js/coin_distributor.js new file mode 100644 index 00000000..dcb8ab89 --- /dev/null +++ b/gfg_interactive/exp/static/BART/js/coin_distributor.js @@ -0,0 +1,276 @@ +var Canvas = document.getElementById('coin_distribution'), + Context = Canvas.getContext('2d'), + coins = [], + Bins = [], + Deleters = [], + values = [], + QuestionMark = {x: Canvas.clientWidth - (Canvas.clientWidth/1.005),y:Canvas.clientHeight,width:Canvas.clientHeight/25,height:Canvas.clientHeight/25}, + ContinueButton = {x:Canvas.clientWidth/1.13,y:QuestionMark.y,width:Canvas.clientWidth/10,height:QuestionMark.height}, + ContinueGo = null, + InstructionsOpen = false, + maxsize = null; + +function Coin(x,bottomed,binindex){ + this.x = x; + this.y = Canvas.clientHeight/20; + this.width = Canvas.clientWidth/10; + this.height = Canvas.clientHeight/60; + this.speed = 0.1; + this.gravity = 0.9; + this.gravitySpeed = 0; + this.bottomed = bottomed; + this.binindex = binindex; +} + +Coin.prototype.update = function() { + this.newPos(); + var grd=Context.createLinearGradient(this.x,this.y,this.x + this.width,this.y+this.height); + grd.addColorStop(0,"#7B7323"); + grd.addColorStop(0.3,"#f5de50"); + grd.addColorStop(1,"#7B7323"); + Context.fillStyle = grd; + Context.fillRect(this.x,this.y,this.width,this.height); + Context.rect(this.x,this.y,this.width,this.height); + Context.stroke(); +}; + +Coin.prototype.newPos = function(){ + this.gravitySpeed += this.gravity; + this.y += this.speed + this.gravitySpeed; + this.hitBottom(); +}; + +Coin.prototype.hitBottom = function() { + if (this.y > this.bottomed){ + this.y = this.bottomed; + } +}; + +function newCoin(bin){ + var coin = new Coin(bin.x,bin.bottomed,bin.binIndex); + coins.push(coin); +} + + + +function Bin(x,i){ + this.x= x; + this.y= Canvas.clientHeight/14; + this.width= Canvas.clientWidth/10; + this.height= Canvas.clientHeight - (Canvas.clientHeight/5) ; + this.bottomed = Canvas.clientHeight - (Canvas.clientHeight/10); + this.inBin = 0; + this.binIndex = i; +} + +Bin.prototype.update = function() { + Context.strokeStyle="#999999"; + Context.setLineDash([5, 10]); + Context.beginPath(); + Context.moveTo(this.x,Canvas.clientHeight/20); + Context.lineTo(this.x,Canvas.clientHeight - (Canvas.clientHeight/10)); + Context.stroke(); + Context.setLineDash([0,10]); +}; + + + +function Deleter(x){ + this.x = x; + this.y = Canvas.clientHeight - (Canvas.clientHeight/10); + this.width = Canvas.clientWidth/10; + this.height = Canvas.clientHeight/20; +} + +Deleter.prototype.update = function() { + Context.fillStyle = "#989898"; + Context.fillRect(this.x,this.y,this.width,this.height); + Context.rect(this.x,this.y,this.width,this.height); + Context.stroke(); + Context.fillStyle = "#0B2972"; + Context.font = String((Canvas.clientHeight/35)) + "px Arial"; + Context.fillText("REMOVE",this.x + (Canvas.clientWidth/45),this.y + (Canvas.clientHeight/35)); +}; + + + +function Distribution_init(max){ + $("#hider").show(); + InstructionsOpen = true; + maxsize = max; + fitToContainer(); + $('#coin_distribution').show(); + + for (i = 0; i < 11; i++){ + b = new Bin(i*(Canvas.clientWidth/10),i); + x = new Deleter(i*(Canvas.clientWidth/10)); + Bins.push(b); + Deleters.push(x); + } + updateAll(); +} + +function updateAll(){ + Context.clearRect(0,0,Canvas.clientWidth,Canvas.clientHeight); + fitToContainer(); + + Context.font = String((Canvas.clientWidth/75)) + "px Arial"; + Context.textAlign="left"; + Context.fillText("0",Canvas.clientWidth - (Canvas.clientWidth - 1),Canvas.clientHeight - (Canvas.clientHeight/100)); + Context.textAlign="center"; + Context.fillText("Balloon size",(Canvas.clientWidth / 2),Canvas.clientHeight - (Canvas.clientHeight/120)); + Context.textAlign="right"; + Context.fillText(String(maxsize),Canvas.clientWidth,Canvas.clientHeight - (Canvas.clientHeight/120)); + Context.textAlign="left"; + Context.fillStyle = "#000000"; + Context.font = String((Canvas.clientWidth/50)) + "px Arial"; + var results = Bins.map(function(a) {return a.inBin;}); + var sum = results.reduce(function(a, b) { return a + b; }, 0); + Context.fillText(String(sum) + ' BETS',(Canvas.clientWidth / 2) - 35, (Canvas.clientHeight/30)); + if (sum >= 50){ + Context.fillStyle = "#009108"; + Context.fillRect(Canvas.clientWidth/1.13,QuestionMark.y,Canvas.clientWidth/10,QuestionMark.height); + Context.stroke(); + Context.fillStyle = "#ffffff"; + Context.font = String((Canvas.clientWidth/50)) + "px Arial"; + Context.fillText('Continue',Canvas.clientWidth/1.12, Canvas.clientHeight/30); + ContinueGo = true; + } else{ + ContinueGo = false; + } + + coins.forEach(function(c){ + c.update(); + }); + Bins.forEach(function(b){ + b.update(); + }); + Deleters.forEach(function(d){ + d.update(); + }); + + Context.setLineDash([0,0]); + Context.rect(QuestionMark.x,QuestionMark.y,QuestionMark.width,QuestionMark.height); + Context.stroke(); + Context.fillStyle = "#0B2972"; + Context.font = String((Canvas.clientWidth/50)) + "px Arial"; + Context.fillText('?',QuestionMark.x, Canvas.clientHeight/28); + + Context.fillStyle = "black"; + Context.beginPath(); + Context.moveTo(0,Canvas.clientHeight - (Canvas.clientHeight/10)); + Context.lineTo(Canvas.clientWidth,Canvas.clientHeight - (Canvas.clientHeight/10)); + Context.stroke(); + + requestAnimationFrame(updateAll); + +} + +Canvas.addEventListener('click', function(event) { + + var x = event.pageX - Canvas.offsetLeft, + y = event.pageY - Canvas.offsetTop; + var results = Bins.map(function(a) {return a.inBin;}); + var sum = results.reduce(function(a, b) { return a + b; }, 0); + if (!InstructionsOpen) { + Bins.forEach(function (bin, i) { + + if (y > bin.y && y < bin.y + bin.height && + x > bin.x && x < bin.x + bin.width && sum < 50) { + console.log('hi'); + if (bin.inBin < 50) { + bin.inBin++; + bin.bottomed -= (Canvas.clientHeight / 60); + newCoin(bin); + } + } + }); + + Deleters.forEach(function (d, i) { + if (y > d.y && y < d.y + d.height && + x > d.x && x < d.x + d.width) { + console.log('hiss'); + if (Bins[i].inBin > 0) { + Bins[i].inBin -= 1; + var results = coins.filter(function (coin) { + return coin.x == d.x; + }); + + var index = coins.findIndex(function (coin) { + return coin.x == results[results.length - 1].x + && coin.y == results[results.length - 1].y; + }); + Bins[i].bottomed += (Canvas.clientHeight / 60); + coins.splice(index, 1); + } + } + }); + if ( y > QuestionMark.y && y < QuestionMark.y + QuestionMark.height && + x > QuestionMark.x && x < QuestionMark.x + QuestionMark.width){ + + InstructionsOpen = true; + $("#hider").show(); + } + + } else { + if (( y > QuestionMark.y && y < QuestionMark.y + QuestionMark.height && + x > QuestionMark.x && x < QuestionMark.x + QuestionMark.width)){ + $("#hider").hide(); + InstructionsOpen = false; + + } + } + if (ContinueGo && y > ContinueButton.y && y < ContinueButton.y + ContinueButton.height && + x > ContinueButton.x && x < ContinueButton.x + ContinueButton.width) { + console.log('yes'); + tutorial.removeChart(); + } +}, false); + +function fitToContainer(){ + // Make it visually fill the positioned parent + Canvas.style.width ='60%'; + Canvas.style.height='65%'; + // ...then set the internal size to match + Canvas.width = Canvas.offsetWidth; + Canvas.height = Canvas.offsetHeight; + + Bins.forEach(function(b,i){ + Bins[i].width = Canvas.width/10; + Bins[i].x = i*(Canvas.clientWidth/10); + Deleters[i].width = Canvas.width/10; + Deleters[i].x = i*(Canvas.clientWidth/10); + }); + coins.forEach(function(c,i){ + coins[i].width = Canvas.width/10; + coins[i].x = Bins[coins[i].binindex].x; + }); + QuestionMark.x = Canvas.clientWidth - (Canvas.clientWidth/1.005); + QuestionMark.y =Canvas.clientHeight - (Canvas.clientHeight/1.005); + QuestionMark.width = Canvas.clientHeight/25; + QuestionMark.height = Canvas.clientHeight/25; + ContinueButton.x = Canvas.clientWidth/1.13; + ContinueButton.y = QuestionMark.y; + ContinueButton.width = Canvas.clientWidth/10; + ContinueButton.height = QuestionMark.height; +} + +function addAndSubmitData() { + var results = Bins.map(function(a) {return a.inBin;}); + results.push(tutorial.maxSize); + var result = results.join(); + datahandler.recordTrialData({ + 'balloon_num': 0, + 'action': 5, + 'pumps': 0, + 'pop_point': 0, + 'dist': result + }); + datahandler.saveData(); +} + +function removeInstruction(){ + console.log('h'); + $("#hider").hide(); + InstructionsOpen = false; +} diff --git a/gfg_interactive/exp/static/BART/js/task.js b/gfg_interactive/exp/static/BART/js/task.js deleted file mode 100755 index 9b4b2fa7..00000000 --- a/gfg_interactive/exp/static/BART/js/task.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Created by JMP on 8/11/16. - */ -/** - * Created by JMP on 8/4/16. - */ - -function BART_experiment() { - var trial = 0; - var pumps, popped, cashed, popPoint; - reset(); - BART_trial(); - - function BART_trial() { - $(document).ready(function(){ - $("#pumpContainer").click(function(){ - if(popped == false && cashed == false){ - $("#balloonIm").animate({height: '+=3.25px', width: '+=3px'}, 50); - pumps += 1; - dataHandler.recordTrialData({ - 'balloon_num': trial, - 'user_action': 1, - 'pumps': pumps, - 'pop_point': popPoint - }); - $("#pumpText").text(pumps + ' tokens'); - if(pumps >= popPoint){ - popped = true; - pumps = 0; - $("#pumpContainer").css("pointerEvents","none"); - dataHandler.recordTrialData({ - 'balloon_num': trial, - 'user_action': 0, - 'pumps': pumps, - 'pop_point': popPoint - }); - } - } - if(popped){ - $("#tokenText").css({top: "-150%", left: "50%", color: "red"}); - $("#poppedIm").css({ - height: $("#balloonIm").css('height').toString(), - width: $("#balloonIm").css('width').toString(), - opacity: "1" - }); - $("#poppedIm").animate({ - height:"+=25px", - width: "+=25px" - },{duration: 50, easing: "linear"}).animate({ - opacity: "0" - },{duration: 200, easing: "linear",queue:false}); - $("#pumpContainer").delay(200).animate({backgroundColor: "#FFB7B7"},{duration: 500, easing: "linear", queue:false}); - $('#pumpText').delay(200).animate({top: "-50%"}, {duration: 100, easing: 'linear', queue:false}); - $("#balloonIm").delay(200).animate({bottom: "-150%"},{duration: 100, easing: "linear",queue:false}); - $("#tokenText").delay(200).animate({top: "50%"},100, "linear").text('POPPED!'); - $("#cashText").text("Reset"); - $("#cashContainer").delay(150).animate({backgroundColor: "#BAB5B0"},{duration: 200, easing: "linear", queue:false}); - } - } - ); - - $("#cashContainer").click(function() { - if(popped || cashed){ - reset(); - } - else if(popped == false && cashed == false){ - $("#tokenText").css({left: "-150%", color: "green"}); - $("#pumpContainer").animate({backgroundColor: "#86FAC9"},{duration: 500, easing: "linear", queue:false}); - $('#pumpText').animate({top: "-50%"}, {duration: 250, easing: 'linear', queue:false}); - $("#balloonIm").animate({left: "150%"},{duration: 200, easing: "linear",queue:false}); - $("#tokenText").delay(100).animate({left: "50%"},750, "easeOutElastic").text(pumps + ' tokens'); - $("#cashText").text("Reset"); - $("#cashContainer").animate({backgroundColor: "#BAB5B0"},{duration: 200, easing: "linear", queue:false}); - cashed = true; - dataHandler.recordTrialData({ - 'balloon_num': trial, - 'user_action': 2, - 'pumps': pumps, - 'pop_point': popPoint - }); - } - }); - }); - } - - function reset(){ - trial++; - pumps = 0; - popped = false; - cashed = false; - popPoint = Math.floor((Math.random() * 64) + 1); - $("#balloonIm").css({ - height: "20px", - width: "20px", - left: "-100%", - bottom: "30%" - }); - dataHandler.saveData(); - dataHandler.recordTrialData({ - 'balloon_num': trial, - 'user_action': 3, - 'pumps': pumps, - 'pop_point': popPoint - }); - $('#pumpContainer').animate({backgroundColor: "white"}, {duration: 500, easing:"linear", queue:false}); - $("#pumpText").text("0 tokens").animate({top: "2%"}, {duration: 250, easing: 'linear', queue:false}); - $("#tokenText").animate({left: "150%"},{duration: 200, easing: "linear",queue:false}); - $("#balloonIm").delay(100).animate({left: "50%"},1000, "easeOutElastic").text(pumps + ' tokens'); - $("#cashContainer").css("backgroundColor", "mediumspringgreen"); - $("#cashText").text("CASH IN"); - $("#pumpContainer").css("pointerEvents","auto"); - console.log(trial); - } -} -BART_experiment(); diff --git a/gfg_interactive/exp/static/js/dataHandler.js b/gfg_interactive/exp/static/js/dataHandler.js index 144fbc4b..7938426f 100755 --- a/gfg_interactive/exp/static/js/dataHandler.js +++ b/gfg_interactive/exp/static/js/dataHandler.js @@ -210,7 +210,7 @@ var DataHandler = function(sessionid) { type: "POST", data: {'sessionid': self.taskdata.id} }); - } + }; self.exitTask = function(){ $.ajax("/gfg/lib/interactive_survey_module_handler.php", { @@ -219,9 +219,9 @@ var DataHandler = function(sessionid) { action: "complete" } }); - opener.completeInteractiveSurvey(); + // opener.completeInteractiveSurvey(); window.location.replace('results?uniqueid=' + uniqueid + '&surveyid=' + surveyid) - } + }; // To be fleshed out with backbone views in the future. var replaceBody = function(x) { $('body').html(x); }; diff --git a/gfg_interactive/exp/static/keep_track/js/kt.js b/gfg_interactive/exp/static/keep_track/js/kt.js index 773fa2b9..13cbfae3 100755 --- a/gfg_interactive/exp/static/keep_track/js/kt.js +++ b/gfg_interactive/exp/static/keep_track/js/kt.js @@ -421,3 +421,4 @@ }; }).call(this); + diff --git a/gfg_interactive/exp/static/keep_track/js/task.js b/gfg_interactive/exp/static/keep_track/js/task.js index 443e5dbc..372f004c 100755 --- a/gfg_interactive/exp/static/keep_track/js/task.js +++ b/gfg_interactive/exp/static/keep_track/js/task.js @@ -16,3 +16,4 @@ currSession.start(); }).call(this); + diff --git a/gfg_interactive/exp/templates/BART/exp.html b/gfg_interactive/exp/templates/BART/exp.html old mode 100755 new mode 100644 index f93bcdae..ebf47528 --- a/gfg_interactive/exp/templates/BART/exp.html +++ b/gfg_interactive/exp/templates/BART/exp.html @@ -1,30 +1,96 @@ -{% extends "base.html" %} -{% block headscripts %} + + + + Genes for Good + - - - - -{% endblock %} - -{% block content %} -
-
-

0 tokens

-

HOLDER

- - - + // These fields are coming from the "/task" start_exp() in experiments.py + var uniqueid = "{{ uniqueid }}"; + var sessionid = "{{ sessionid }}"; + var experimentname = "{{ experimentname }}"; + var surveyid = "{{ surveyid }}"; + var debug = "{{ debug }}"; + + + + + + + + + + + + +
+ +
+ + +
+ + +
+
+

0 Tokens

+
+
+ +
+

Popped

+
+
+

Pump

-
-

CASH IN

+ +
+

Cash In

+
+ +
+
+ +
+

Welcome to the experiment!

+ + +
+
-{% endblock %} \ No newline at end of file + + + + + + + + + + + + \ No newline at end of file diff --git a/gfg_interactive/exp/templates/BART/results.html b/gfg_interactive/exp/templates/BART/results.html new file mode 100644 index 00000000..0dcde6b4 --- /dev/null +++ b/gfg_interactive/exp/templates/BART/results.html @@ -0,0 +1,202 @@ +{% extends "base.html" %} +{% block content %} + +

+
+
+ +

Your results!

+ + On Average you saved {{'%0.0f'| format(score|float)}} Tokens when you cashed in. Thanks for the hard work! + + {% if previousSessions is not none %} +
+

Result history

+

Here you will see the full history of all of your BART results.

+
+
+ {% endif %} + + {% if percentile is not none %} +

How you stack up.

+
+
+

On average, where you cashed in was greater than {{'%0.0f'| format(percentile|float)}}% of other Genes for Good participants who completed this task. This means that {{'%0.0f'| format(percentile|float)}}% of all participants, on average, cashed in below you, and that {{'%0.0f'|format( 100 - percentile|float)}}% of participants, on average, cashed in above you.

+
+ {% else %} +
+ We cannot tell you how your score compared to others yet, as not enough people have completed the test. Please try again later. + {% endif %} + +

+ +

What do my results mean?

+ +

+ This task is formally called the Balloon Analogue Risk Task, or BART (link to original article). With the BART we are able to elicit individuals' + appetite for risk. We do this by measuring how many pumps an individual takes in the face of an uncertain explosion point. + Individuals vary greatly in their willingness to pump the balloons in the BART, and this variation allows us to label them + as risk averse, seeking or neutral. An individual who shies away from risk (pumps very few times) would be labeled risk averse, whereas an individual + who is willing to take greater risk (pump more) would be labeled risk seeking. Those individuals who fall in the middle of these + preferences would be labeled risk neutral. +

+ + +

Why do we study risk with this task?

+ Risky behavior, as it is measured in the BART, is frequently associated with a range of health related risk behaviors such as alcohol use, drug use, + unprotected sex, diving without a seatbelt and smoking. With the BART we can identify individuals who are at risk of engaging + in these behaviors in a harmful way. While individuals who are risk averse are less likely to engage in these behaviors, it is also known that + these individuals are also likely to overspend on insurance and miss out on investment opportunities due to them being overly cautious. + Ultimately, understanding how individuals engage in risky behaviors is critical for improving methods for helping populations of individuals + who make too many or too few risky decisions in their daily lives. +

+ +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/gfg_interactive/exp/templates/base.html b/gfg_interactive/exp/templates/base.html index 31072f5d..956a6b4f 100755 --- a/gfg_interactive/exp/templates/base.html +++ b/gfg_interactive/exp/templates/base.html @@ -1,49 +1,51 @@ - Genes for Good - - - - - - - - - - - - - - - -{% block headscripts %}{% endblock %} - + 2. Bootstrap.min.css + 3. Bootstrap-theme + 4. custom.css --> + + + + + + + + + + + + + + + + + + + + + {% block headscripts %}{% endblock %} - -
- -
- {% block content %}{% endblock %} - - + +
+ +
+ +{% block content %}{% endblock %} + diff --git a/gfg_interactive/exp/templates/other/BART_Task.js b/gfg_interactive/exp/templates/other/BART_Task.js new file mode 100644 index 00000000..387badbb --- /dev/null +++ b/gfg_interactive/exp/templates/other/BART_Task.js @@ -0,0 +1,3 @@ +/** + * Created by JMP on 12/20/16. + */ diff --git a/gfg_interactive/exp/templates/other/PurpleBalloon.png b/gfg_interactive/exp/templates/other/PurpleBalloon.png new file mode 100755 index 00000000..c39ae7a5 Binary files /dev/null and b/gfg_interactive/exp/templates/other/PurpleBalloon.png differ diff --git a/gfg_interactive/exp/templates/other/bart_instructs.js b/gfg_interactive/exp/templates/other/bart_instructs.js new file mode 100644 index 00000000..f9c1fa84 --- /dev/null +++ b/gfg_interactive/exp/templates/other/bart_instructs.js @@ -0,0 +1,438 @@ + +function popAnimation() { + $("#balloon-image").css({opacity: '0'}); + $("#token-text").text('0 Tokens'); + $('#task-container') + .animate({backgroundColor: '#ff7272'}, 100); + $('#result-text').text('Popped!').css({color: '#72090C'}) + .animate({opacity: '1'}); + $('#task-container') + .animate({backgroundColor: 'white'}); + $('#cash-box').animate({backgroundColor: '#B3B3B3'}); +} + +function resetBalloon() { + $('#cash-box').animate({backgroundColor:'#7bb37e'}); + $('#cash-text').html('Cash In'); + $("#token-text").text( '0 Tokens'); + $("#balloon-image") + .css({ + top: '65%', + left: '50%', + height: '100px', + width: '75px' + }) + .animate({opacity: '1'}); + $("#result-text") + .animate({opacity: '0'}); +} + +function cashDisplay() { + $("#balloon-image").css({opacity: '0'}); + $('#task-container') + .animate({backgroundColor: '#41B96B'}, 100); + $('#result-text').text('cashed in').css({color: 'green'}) + .animate({opacity: '1'}); + $('#task-container') + .animate({backgroundColor: 'white'}); + $('#cash-box').animate({backgroundColor:'#B3B3B3'}); +} + +BART_TUTORIAL = function() { + Bart_tutorial = (function () { + function BART_tutorial() { + this.lastClick = new Date().getTime(); + console.log(this.lastClick); + this.flashinterval = null; + this.status = null; + this.tokens = 0; + this.popPoint = 10; + this.active = true; + this.popList = [4, 40, 12, 20, 32, 48, 52, 1, 60, 24, 64, 28, 8, 56, 36, 44, 16]; + this.autotrial = 0; + this.maxSize = 0; + this.isDown = false; + this.barValues = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + + BART_tutorial.prototype.onboarding = function () { + this.changeStatus('onboarding'); + this.displayInstruction( + ' Welcome to the BART ' + + '

Throughout this task, you will be presented with 30 balloons, one at a time.' + + '

You will be asked to inflate these balloons. Every time you choose to inflate the balloon,' + + ' it will grow slightly and you will receive one token. ' + ); + }; + + BART_tutorial.prototype.learntopump = function () { + this.changeStatus('learntopump'); + $('#continue-instruction').hide(); + this.displayInstruction( + "To inflate the balloon click or tap anywhere in the upper white space of this container.

Go ahead and pump as many times as you'd like." + ); + $('#pump-text').delay(1000).animate({opacity: '1'}); + }; + + BART_tutorial.prototype.watchrangeIntro = function () { + this.changeStatus('onboardWatch'); + this.displayInstruction( + "We'd like you to now watch several balloons grow to their maximum sizes, to get a feel for what sorts" + + " of balloons you might encounter.

Click or tap on this box to view these balloons inflate." + ); + }; + + BART_tutorial.prototype.watchrange = function (required) { + + var self = this; + var tokens = 0; + var popat = this.popList[required]; + console.log(popat); + window.setTimeout(function () { + resetBalloon(); + }, 1000); + + if (this.autotrial == required) { + window.setTimeout(function () { + var interv = setInterval(function () { + tokens++; + $("#balloon-image").animate({height: '+=3.25px', width: '+=3px', top: '-=1px'}, 50); + $("#token-text").text(String(tokens) + ' Tokens'); + if (tokens == popat) { + $('#progress').css({width: (1+required) * 6.25.toString() + '%'}); + clearInterval(interv); + popAnimation(); + self.incrementAuto(); + if (required < self.popList.length - 1) { + self.watchrange(required + 1); + } else { + $('#continue-instruction').show(); + self.displayInstruction( + "now that you've seen what sorts of balloons you might encounter in this task," + + " we'd like to ask you to tell us where you think the balloon is most likely to pop." + ); + self.changeStatus('maxRating'); + } + } + }, 120) + }, 1500) + } + }; + + // Trial helper + BART_tutorial.prototype.incrementAuto = function () { + this.autotrial++; + }; + + BART_tutorial.prototype.changeStatus = function (status) { + this.status = status; + }; + + + + BART_tutorial.prototype.maxRating = function () { + this.changeStatus('inputMax'); + this.displayInstruction('Before we move on. Please tell us what you think the largest a balloon can grow to is.'); + $('#instructions-box').delay(200).append(' '); + }; + + + BART_tutorial.prototype.distribution = function () { + $('#balloon-image').hide(); + $('#token-text').hide(); + $('#cash-box').hide(); + $('#result-text').hide(); + $('#instructions-box').animate({opacity: '0'}).hide(); + $('#chart').css({visibility: 'visible'}).delay(200).animate({opacity: '1'}); + $('#chart-border').css({visibility: 'visible'}).delay(200).animate({opacity: '1'}); + + for (var i = 0; i < 20; i++) { + var barHTML = "
"; + var actionHTML = "
"; + $('#chart') + .append(barHTML) + .append(actionHTML); + $('#bar-' + i.toString()).css({left: (i * 5).toString() + '%'}); + $('#action-' + i.toString()).css({left: (i * 5).toString() + '%'}); + } + $('#chart').append( + "

" + this.maxSize + "

" + + "

0

" + + "

Size (in pumps)

" + + "

number of balloons

" + + "
Please distribute at least 500 balloons to where you believe the balloons in this task are most likely to pop.

You can click/tap and drag to draw your distribution. Once you have distributed 500 or more balloons you may continue.

" + + "

click this box to begin this task

" + + "
" + + "

0

" + + "" + ); + + }; + + BART_tutorial.prototype.startDistribute = function () { + var self = tutorial; + $('#chart-instruct').animate({opacity: '0'}, function () { + $('#chart-instruct').hide(); + self.changeStatus('distributeGo'); + }) + }; + + BART_tutorial.prototype.moveInChart = function (e) { + if (tutorial.status == 'distributeGo') { + if (tutorial.isDown) { + var selfID = e.id; + var chartBar = $('#bar-' + selfID.split('-')[1]); + var actionY = event.pageY - $('#chart').offset().top; + var barHeight = Math.floor(100 - ((actionY / parseInt($('#chart').css('height'), 10)) * 100)); + + chartBar.css({height: barHeight.toString() + '%'}); + tutorial.barValues[parseInt(selfID.split('-')[1])] = barHeight; + tutorial.updateChartText(); + } + } + }; + + BART_tutorial.prototype.clickInChart = function (e) { + if (tutorial.status == 'distributeGo') { + var selfID = e.id; + var chartBar = $('#bar-' + selfID.split('-')[1]); + var actionY = event.pageY - $('#chart').offset().top; + var barHeight = Math.floor(100 - ((actionY / parseInt($('#chart').css('height'), 10)) * 100)); + + chartBar.css({height: barHeight.toString() + '%'}); + tutorial.barValues[parseInt(selfID.split('-')[1])] = barHeight; + tutorial.updateChartText(); + } + }; + + BART_tutorial.prototype.updateChartText = function () { + var count = 0; + for (var i = 0; i < tutorial.barValues.length; i++) { + count += tutorial.barValues[i]; + } + console.log(count); + $('#distribution-counter').text(count.toString()); + if (count >= 500) { + $('#chart-next').css({visibility: 'visible'}) + } else { + $('#chart-next').css({visibility: 'hidden'}) + } + }; + + BART_tutorial.prototype.submitMax = function () { + tutorial.changeStatus('Distribution'); + tutorial.displayInstruction('Now you will make a distribution'); + tutorial.maxSize = parseInt($('#maxSize')[0].value); + $('input').hide().remove(); + }; + + BART_tutorial.prototype.removeChart = function () { + $('#chart').animate({opacity: '0'}, function () { + $('#chart').remove(); + }); + $('#chart-border').animate({opacity: '0'}, function () { + $("#chart-border").remove(); + }); + resetBalloon(); + $('#balloon-image').show(); + $('#token-text').show(); + $('#cash-box').show(); + $('#result-text').show(); + $('#pump-box').show(); + $('#instructions-box').animate({opacity: '0'}).show(); + tutorial.changeStatus('learntocash'); + tutorial.displayInstruction( + "To save your tokens before a balloon pops, you may choose to 'cash in'.

To cash in, touch anywhere " + + "inside of the green 'cash in' box at the bottom of this container.

Go ahead and pump this balloon" + + " to 10 tokens and cash in." + ); + tutorial.active = true; + tutorial.tokens = 0; + }; + + BART_tutorial.prototype.displayInstruction = function (message) { + var message = message; + $('#instructions-box').animate({opacity: '0'}, 750, function () { + $('#instructions-text').html(message); + }).animate({opacity: '1'}, {duration: 750}); + }; + + BART_tutorial.prototype.checkTime = function () { + clearInterval(this.flashinterval); + this.lastClick = new Date().getTime(); + var lastClick = this.lastClick; + this.flashinterval = window.setInterval(function () { + if (new Date().getTime() - lastClick > 7500) { + $('#pump-text') + .finish() + .animate({opacity: '1'}) + .delay(500) + .animate({opacity: '0'}); + } + }, 2000) + }; + + + + return BART_tutorial; + + })(); + + + BART_task = (function() { + function BART_task(){ + this.flashinterval = null; + this.proceed = false; + this.trial = 0; + this.tokens = 0; + this.active = false; + } + + BART_task.prototype.newTrial = function() { + if (this.trial <= 29) { + this.trial++; + this.tokens = 0; + this.popPoint = Math.floor((Math.random() * 63) + 1); + resetBalloon(); + this.active = true; + this.proceed = false; + } + }; + + + BART_task.prototype.checkTime = function () { + clearInterval(this.flashinterval); + this.lastClick = new Date().getTime(); + var lastClick = this.lastClick; + this.flashinterval = window.setInterval(function () { + if (new Date().getTime() - lastClick > 7500) { + $('#pump-text') + .finish() + .animate({opacity: '1'}) + .delay(500) + .animate({opacity: '0'}); + } + }, 2000) + }; + + + return BART_task; + })(); + + + + tutorial = new Bart_tutorial(); + tutorial.onboarding(); + Task = new BART_task(); + + $('#instructions-box').click(function() { + if (tutorial.status === 'onboarding') { + tutorial.learntopump(); + } else if (tutorial.status === 'popped') { + tutorial.watchrangeIntro(); + } else if (tutorial.status === 'onboardWatch') { + $('#progress-container').css({visibility:'visible'}); + $('#instructions-box').animate({opacity:'0'}); + tutorial.watchrange(0); + } else if (tutorial.status === 'maxRating'){ + $('#progress-container').css({visibility:'hidden'}); + tutorial.maxRating(); + }else if (tutorial.status === 'Distribution') { + tutorial.distribution(); + } else if (tutorial.status === 'preTask') { + tutorial.changeStatus('done'); + $('#instructions-box').hide(); + Task.newTrial(); + } + }); + + $("#task-container").mousedown(function() { + tutorial.isDown = true; + }).mouseup(function(){ + tutorial.isDown = false; + }); + + $("#pump-box").click(function() { + if (tutorial.status === 'learntopump') { + if (tutorial.active) { + tutorial.tokens++; + $("#balloon-image").animate({height: '+=3.25px', width: '+=3px', top: '-=1px'}, 50); + $("#token-text").text(String(tutorial.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, tutorial.checkTime()); + + if (tutorial.tokens === 7) { + tutorial.changeStatus('popped'); + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + tutorial.active = false; + $('#continue-instruction').show(); + tutorial.displayInstruction( + ' Oh no! It looks like the balloon popped.

' + + 'Every balloon that you inflate will have a different maximum size that it can grow to.' + + ' Once the balloon gets to its max size, it will pop and you will lose your current tokens.' + ); + popAnimation(); + } + } + } else if (tutorial.status === 'learntocash' && tutorial.tokens < 10){ + console.log('hi'); + if (tutorial.active){ + tutorial.tokens++; + $("#balloon-image").animate({height: '+=3.25px', width: '+=3px', top: '-=1px'}, 50); + $("#token-text").text(String(tutorial.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, Task.checkTime()); + } + } else if (Task.active){ + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + console.log(Task.tokens); + Task.tokens++; + $("#balloon-image").animate({height: '+=3.25px', width: '+=3px', top: '-=1px'}, 50); + $("#token-text").text(String(Task.tokens) + ' Tokens'); + $('#pump-text').animate({opacity: '0'}, Task.checkTime()); + if (Task.tokens === Task.popPoint) { + Task.active = false; + popAnimation(); + $('#cash-text').html('Next Balloon'); + Task.proceed = true; + } else if (Task.proceed) { + Task.newTrial(); + + } + } + }); + + $('#cash-box').click(function(){ + + if (tutorial.status === 'learntocash' && tutorial.tokens == 10) { + if (tutorial.active) { + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + tutorial.active = false; + cashDisplay(); + tutorial.displayInstruction( + "And that's all there is to it!

In this task you will be presented with 30 balloons." + + " For each balloon you can choose to pump the balloon to whatever size you like before cashing in" + + " your tokens. But remember, if you pump the balloon too much, it will explode and you will lose your tokens." + + "

If you are ready to start this task for real, click/tap this box and we will get started." + ); + tutorial.changeStatus('preTask'); + + } + } else if (Task.active){ + clearInterval(tutorial.flashinterval); + clearInterval(Task.flashinterval); + Task.active = false; + cashDisplay(); + $('#cash-text').html('Next Balloon'); + Task.proceed = true; + } else if (Task.proceed) { + Task.newTrial(); + } + }); + +}(); + + + + diff --git a/gfg_interactive/exp/templates/other/genesforgood.css b/gfg_interactive/exp/templates/other/genesforgood.css new file mode 100755 index 00000000..e1dc7f11 --- /dev/null +++ b/gfg_interactive/exp/templates/other/genesforgood.css @@ -0,0 +1,706 @@ +/* Site Header Styles */ + +header > nav > div.container-fluid { + padding-left: 0px; + padding-right: 0px; +} + +header > nav > div.container-fluid > div.row { + background: url("../images/logo_bg.png") no-repeat scroll 0 #00294F; +} + +header > nav > div.container-fluid > div.row .logo { + display: block; + margin: 12px 0px 12px 0px; +} + +nav ul.nav-pills { + margin-top: 1em; +} + +nav ul.nav-pills li a { + color: #FAFBFC; +} + +nav ul.nav-pills li a:hover { + color: #00294F; +} + +.nav-gfg-header { + margin-bottom: 0px; + background-color: #E7E7E7; + border: 0; + border-radius: 0; +} + +li.dropdown a h3 { + margin: 0; + padding-right: 1.2em; + color: #00294F; +} + +.navbar-gfg { + background-color: #00294F; + border: 0; +} + +.navbar-gfg-xs { + margin: 0; +} + +.navbar-gfg-xs > li > a { + line-height: 61px; + padding-top: 11px; + padding-bottom: 11px; + color: #FAFBFC; +} + +.navbar-gfg-xs > li > a:hover { + line-height: 61px; + color: #00294F; +} + +.navbar-gfg-xs > li a.navbar-logo:hover { + background-color: #00294F; +} + +.navbar-gfg-xs > li { + float: left; +} + +#site_notices div.alert { + margin-bottom: 0px; + text-align: center; + font-size: 110%; +} + +#messages div.alert { + margin-bottom: 0px; + text-align: center; +} + +/* Site Footer Styles */ + +#footer { + padding-top: 1.5em; + padding-bottom: 1.5em; + background-color: #00294F; + color: #FFFFFF; + font-size: 110%; + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 1rem; + background-color: #efefef; + text-align: center; +} + +#footer a { + color: #AEC4D9; +} + + +/* Section Headers */ + +h2.section_title { + padding-top: 0.5em; + padding-bottom: 0.3em; +} + + +/* Main Container/Row Styles */ + +#main_container { + background-color: #F2F2F2; +} + +#main_row { + background-color: #FFFFFF; + margin-left: 2em; + margin-right: 2em; + margin: 0 auto; + max-width: 1100px; +} + +/* Override default panel heading text color */ +.panel-default > .panel-heading { + color: #00294F; +} + +ul.welcome li { + margin-bottom: 1em; +} + +/* Checklist Styles */ +ul.checklist { + font-size: 1.2em; + padding-left: 1.2em; + list-style-type: none; +} +span.checklist-ok { + padding-right: 0.6em; + color: #528400; +} +span.checklist-not-ok { + padding-right: 0.6em; + color: #AF1500; +} +span.checklist-partial { + padding-right: 0.6em; + color: #E3B102; +} +ul.checklist div.extra-info { + padding-left: 2.2em; + font-size: 85%; +} +ul.checklist div.progress { + margin-top: 0.4em; +} + +/* News Styles */ +div.panel-latest-news-clipped { + max-height: 940px; + overflow: hidden; +} +div.latest-news-clipped-read-more { + position: absolute; + bottom: 1.5em; + left: 16px; + width: -moz-calc(100% - 32px); + width: -webkit-calc(100% - 32px); + width: calc(100% - 32px); + padding-top: 4em; + padding-bottom: 0.8em; + text-align: center; + border-radius: 2px; + background: -moz-linear-gradient(top, rgba(250,251,252,0) 0%, rgba(250,251,252,1) 55%, rgba(250,251,252,1) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(top, rgba(250,251,252,0) 0%,rgba(250,251,252,1) 55%,rgba(250,251,252,1) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, rgba(250,251,252,0) 0%,rgba(250,251,252,1) 55%,rgba(250,251,252,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=0 ); +} +div.blog_post h2 { + font-weight: 800; + font-size: 2.3em; + text-transform: none; + font-variant: small-caps; + margin-top: 0.5em; + margin-bottom: 0.5em; + padding-top: 0.1em; + padding-bottom: 0.1em; +} +div.blog_post h3 { + font-size: 1.4em; + text-transform: none; + font-variant: small-caps; + margin-top: 0.3em; + margin-bottom: 0.3em; + padding-top: 0.1em; + padding-bottom: 0.1em; + letter-spacing: 0px; + font-weight: 600; +} +div.blog_post img { + width: auto !important; + height: auto !important; + max-width: 650px; +} +div.blog_contents h5 { + font-size: 1.2em; +} +div.blog_contents h5 small { + vertical-align: bottom; +} + +/* Consent Form Styles */ + +#consent_text { + margin-left: 2em; + margin-right: 2em; + margin-bottom: 2em; +} + +#consent_text ul li { + margin-bottom: 1em; + list-style: square outside none; +} + +#consent_text h4 { + font-weight: 700; + color: #0B2972; +} + + +/* Left Nav Styles */ + +div#left_nav { + padding-top: 16px; +} + +ul.left_nav { + list-style: none; + padding-left: 0px; + margin-left: -3px; +} + +ul.left_nav li { + line-height: 40px; +} + +ul.left_nav li a { + display: inline-block; + background: url(../images/marker_1.gif) 0 2px no-repeat; + padding-left: 48px; + font-weight: normal; + text-decoration: none; +} + +ul.left_nav li a:hover { + background-position: 0 -38px; + color: #0055a2; +} + +ul.left_nav li a.locked{ + background: url(../images/marker_locked.gif) 0 2px no-repeat; +} + +ul.left_nav li a.unlocked { + background: url(../images/marker_unlocked.gif) 0 2px no-repeat; +} + + +/* Fancybox / Overlay Styles */ + +.fancybox-nav span { + visibility: visible; +} +.fancybox-title { + font: normal 18px/24px "Helvetica Neue",Helvetica,Arial,sans-serif; +} + + +/* Download Slide Styles */ + +div.download_slide { + padding: 20px 65px 20px 65px; + font-size: 120%; +} + +div.download_slide p { + padding-bottom: 0.5em; +} + +div.download_slide h1 { + margin-left: 0px; + padding-left: 0px; + padding-top: 5px; +} + +div.download_slide ol { + list-style-type: decimal; +} + +div.download_slide ul { + list-style-type: disc; +} + +div.download_slide ul li { + margin-left: 20px; + padding-bottom: 0.5em; +} + +div.download_slide ol li { + margin-left: 20px; + padding-bottom: 0.5em; +} + +div.download_slide span.emphasis { + color: #1B7FC6; + font-weight: bold; +} + +div.download_slide span.warning { + color: #FF0E0E; + font-weight: bold; +} + +div.download_slide div.box { + border: 1px solid #1B7FC6; + padding: 10px; +} + + +/* LEGACY STYLES + Several of these may no longer be in use (and if so should be deleted) + Any others that override framework styles should at least be reevaluated. +*/ + +/* Left & Right alignment */ +.left { + float:left +} +.right { + float:right +} +.wrapper { + width:100%; + overflow:hidden +} + +/* Global properties */ + +p { + padding-bottom: 1em; +} + +.pad1 { + padding:0 34px +} +.pad_left1 { + padding-left:5px +} +.pad_top2 { + padding-top: 7px; + padding-left: 5px; +} +.marg_right1 { + margin-right:21px; + margin-bottom: 15px; +} + +.marg_right2 { + margin-right:21px; + margin-bottom: 300px; +} +.marg_left1 { + margin-left:-5px +} + +.box2 { + background:#f2f2f2; + padding:17px 30px 20px; + border-radius:6px; + -moz-border-radius:6px; + -webkit-border-radius:6px; + position:relative; + margin-right:5px; + margin-bottom:25px +} +.button { + display:inline-block; + color:#fff; + background:url(../images/button1_bg.gif) 0 0 repeat-x; + line-height:40px; + text-decoration:none; + cursor:pointer +} +.button span { + display:block; + background:url(../images/button1_left.gif) 0 0 no-repeat +} +.button span span { + background:url(../images/button1_right.gif) top right no-repeat; + padding:0 25px; + height:42px +} +.button:hover { + background-position:bottom +} +.button:hover span { + background-position:bottom left +} +.button:hover span span { + background-position:bottom right +} +.list2 { + margin-top:-1px +} +.list2 li { + font:14px Tahoma, Geneva, sans-serif; + line-height:30px +} +.list3 li { + line-height:30px + border: 1px solid #F2F2F2; + padding: 5px,0; +} +.list3 li a { + color:#0055a2; + background:url(../images/marker_3.gif) 0 -17px no-repeat; + padding-left:10px; + text-decoration: none; +} +.list3 li a:hover { + color:#000; + background-position:0 6px +} +.date { + float:left; + width:72px; + height:50px; + background:url(../images/date.gif) 0 0 no-repeat; + margin-right:14px; + font-size:34px; + line-height:1.2em; + padding-top:22px; + text-align:center +} + +.quot { + padding-left:42px; + background:url(../images/quot.gif) 0 4px no-repeat; + overflow:hidden +} +.quot span { + display:block +} + +legend { + color: blue; +} + +#container { + min-width: 400px; + height: 350px; + margin: 0 auto; + border:0px solid #DDD; + padding: 20px 10px 20px 0; +} + +.loading { + display: none; + position: fixed; + z-index: 8060; + top: 0; + left: 0; + height: 100%; + width: 100%; + background: rgba( 255, 255, 255, .6) url('../images/ajax-loader1.gif') center 40% no-repeat; +} + +.pad_left3 { + padding-left: 10px; + +} + +.member_status { + float: left; + padding: 5px 5px 5px 6px; +} + +a.member_level { + text-decoration: none; + padding-left:-1px; + color: #363636; +} + +a.member_level:hover{ + color: #0055A2; +} + +.formbox { + background: none repeat scroll 0 0 #F2F2F2; + border-radius: 8px 8px 8px 8px; + padding: 20px; + position: relative; +} + +.back_link { + color: #444; + cursor: pointer; + display: inline-block; + line-height: 40px; + text-decoration: underline; + padding-right: 15px; + float: right; +} + +.survey_list { + border-top: 1px solid #F2F2F2; + padding: 15px 0; +} + +div.question_box { + padding:5px; + width: 98%; + font-size: 1.2em; +} + +div.congrats_message { + height: 350px; +} + +div.survey_list a{ + font-size: 14px; + color: #0055A2; + font-weight: bold; +} + +div span#survey_blurb a { + text-decoration:none; + font-size:0.8em; +} + +div.row label { + display: inline-block; + padding-right: 8px; +} + +div.row input[type=text] { + width: 250px; +} + +div.pad_top2 label { + display: inline-block; + width: 180px; + text-indent: 0px; +} + +div.pad_top2 input[type=text] { + width: 180px; +} + +div.textblurb { + line-height: 20px; + display:block; + padding: 10px 0; + +} + +.trackerPanel { + width:48%; + height:200px; + border:solid 1px #DDD; +} + +p.question_blurb { + padding: 0px 20px; + font-size: 1em; + +} + +p.question_blurb span { + font-size: 0.8em; +} + +p.question_response { + padding-left: 20px; +} + +span.view { + margin-top:0px; + font-size:0.6em; + padding-right:30px; +} + +ul.inline, ul.links.inline { + display: inline; + padding-left: 0; +} + +ul.inline li { + display: inline; + list-style-type: none; + padding: 0 0.5em; +} + +ul.inline li a{ + color: #ffffff; + text-decoration: none; +} + +input.readonly { + color: #777; +} + +input[type=text], select { + border: 1px solid #DEE2E7; + height: 30px; +} + +#dialog.ui-dialog-titlebar-close { + display: none; +} + +.tabs { + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; +} + +ul.primary { + border-bottom: 1px solid #BBBBBB; + border-collapse: collapse; + height: auto; + line-height: normal; + list-style: none outside none; + margin: 5px; + padding: 0 0 0 1em; + white-space: nowrap; +} + +.tabs ul.primary { + background: url("../images/tabs-border.png") repeat-x scroll left bottom transparent; + border: medium none; + margin: 0; + overflow: hidden; + padding: 0 0px; +} + +.tabs ul.primary li { + display: block; + float: left; + margin: 0 5px 0 0; + vertical-align: bottom; +} + +.tabs ul.primary li.active a { + background-color: #FFFFFF; + border-bottom: 1px solid #FFFFFF; +} + +.tabs ul.primary li a { + background-color: #EDEDED; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + color: #000000; + display: block; + float: left; + font-size: 0.929em; + height: 1.8em; + line-height: 1.9; + margin: 0; + padding: 0 10px 3px; + text-shadow: 0 1px 0 #FFFFFF; +} + +ul.primary li.active a { + -moz-border-bottom-colors: none; + -moz-border-left-colors: none; + -moz-border-right-colors: none; + -moz-border-top-colors: none; + background-color: #FFFFFF; + border-color: #BBBBBB #BBBBBB #FFFFFF; + border-image: none; + border-style: solid; + border-width: 1px; +} + +ul.primary li a { + background-color: #DDDDDD; + border-color: #BBBBBB; + border-style: solid solid none; + border-width: 1px; + height: auto; + margin-right: 0.5em; + padding: 0 1em; + text-decoration: none; +} + +div.error { + font-size: 90%; + color: #990000; +} + +div.success { + font-size: 90%; + color: #009900; +} \ No newline at end of file diff --git a/gfg_interactive/exp/templates/other/header_logo.png b/gfg_interactive/exp/templates/other/header_logo.png new file mode 100755 index 00000000..fd397b56 Binary files /dev/null and b/gfg_interactive/exp/templates/other/header_logo.png differ diff --git a/gfg_interactive/experiments.py b/gfg_interactive/experiments.py index 10930d10..d6dff770 100755 --- a/gfg_interactive/experiments.py +++ b/gfg_interactive/experiments.py @@ -1,7 +1,7 @@ from flask import Blueprint, render_template, request, jsonify, current_app, url_for, redirect from errors import ExperimentError from models import Session, Participant, CategorySwitch, EventData, KeepTrack, QuestionData, BART -from sqlalchemy import func +from sqlalchemy import func, asc, distinct from sqlalchemy.exc import SQLAlchemyError from database import db import db_utils @@ -34,14 +34,14 @@ def index(): @experiments.route('/task', methods=['GET']) @utils.nocache def start_exp(): - """ Serves up the experiment applet. - If experiment is ongoing or completed, will not serve. + """ Serves up the experiment applet. + If experiment is ongoing or completed, will not serve. Querystring args (required): uniqueid: External gfg_id surveyid: Which experiment to serve """ - + print 'make new' browser, platform = utils.check_browser_platform(request.user_agent) # Check query string @@ -73,14 +73,15 @@ def start_exp(): if disqualifying_sessions and current_app.config['EXP_DEBUG'] == False: raise ExperimentError('already_did_exp', session_id=disqualifying_sessions.session_id) - # Otherwise, allow participant to re-enter + + else: session = Session(gfg_id=gfg_id, browser=browser, platform=platform, status=1, exp_name=exp_name, begin_session=datetime.datetime.now()) db.session.add(session) db.session.commit() - return render_template(exp_name + "/exp.html", experimentname=exp_name, surveyid=survey_id, + return render_template(exp_name + "/exp.html", experimentname=exp_name, surveyid=survey_id, sessionid=session.session_id, debug=current_app.config['EXP_DEBUG'], uniqueid=urllib.quote(uniqueid)) @@ -106,14 +107,14 @@ def enterexp(): session_id = request.form['sessionid'] session = Session.query.filter_by(session_id=session_id).first() - + print 'enterexp' if session: session.status = 2 session.begin_experiment = datetime.datetime.now() db.session.commit() current_app.logger.info( - "User has finished the instructions in session id: %s, experiment name: %s", + "User has finished the instructions in session id: %s, experiment name: %s", session_id, session.exp_name) resp = {"status": "success"} else: @@ -170,7 +171,7 @@ def update(session_id=None): valid_json['sessionid'])) ## JAKE: This needs to be slightly customized to add your task - ## However, most of the work will be in models.py + ## However, most of the work will be in models.py # For each trial, pass to appropriate parser, if not in db for json_trial in valid_json['data']: if session.exp_name == "category_switch": @@ -179,6 +180,7 @@ def update(session_id=None): experiment_class = KeepTrack elif session.exp_name == 'BART': experiment_class = BART + print 'jake' db_trial, new = db_utils.get_or_create(db.session, experiment_class, gfg_id=session.gfg_id, session_id=session.session_id, @@ -194,10 +196,11 @@ def update(session_id=None): # For each event, pass to parser, if not in db for json_event in valid_json['eventdata']: db_event, new = db_utils.get_or_create(db.session, EventData, - gfg_id=session.gfg_id, session_id=session.session_id, exp_name=session.exp_name, + gfg_id=session.gfg_id, session_id=session.session_id, exp_name=session.exp_name, timestamp = utils.convert_timestamp(json_event['timestamp'])) if new: + print 'added'; db_event.add_json_data(json_event) db.session.commit() @@ -206,7 +209,7 @@ def update(session_id=None): # For the QuestionData, pass to parser, if not in db db_ques, new = db_utils.get_or_create(db.session, QuestionData, gfg_id=session.gfg_id, session_id=session.session_id, exp_name=session.exp_name) - db_ques.add_json_data(valid_json['questiondata']) + db_ques.add_json_data(valid_json['questiondata']) db.session.commit() if resp is None: @@ -229,7 +232,7 @@ def quitter(): session = Session.query.filter( Session.session_id == session_id).one() session.status = 6 - db.session.commit() + db.session.commit() resp = {"status": "marked as quitter"} except SQLAlchemyError: @@ -264,9 +267,7 @@ def worker_complete(): return jsonify(**resp) -## JAKE: This part might take a bit more work since there is custom code to score -## and percentile rank their results. dataHandler.js automatically redirects here -## once exitTask() is called +## As you know, this is where you get routed for results @experiments.route('/results', methods=['GET']) def results(): """Return results at the end.""" @@ -279,23 +280,31 @@ def results(): current_app.logger.info("Results: uniqueid is %s, exp_name is %s and survey id is %s" %(uniqueid, exp_name, survey_id)) ## Get last session with code 3 from user + gfg_id = utils.decrypt(str(current_app.config['SECRET_KEY']), str(uniqueid)) current_app.logger.info("GFG id after decrypt is -- %s" % (gfg_id)) + # I would leave all the stuff above alone, as its just getting all the variable it needs + + # Here we are "supposedly" querying the DB for the last session from that user. it is possible that this should be asc() not desc() try: session = Session.query.filter_by(gfg_id=gfg_id, status=3, exp_name=exp_name).order_by(Session.session_id.desc()).first() except SQLAlchemyError: raise ExperimentError('user_access_denied') + # As you can see, we start doing things different for each task here, but template is still just + # expecting two variables, score and percentile (last line). So if you want to do something + # totally different, then you need to stick an if statement that splits off the whole thing here + # if session.exp_name == "bart": {your stuff} else: if session is None : - current_app.logger.info("Session is null---%s" %(session)) - raise ExperimentError('user_access_denied') + current_app.logger.info("Session is null---%s" %(session)) + raise ExperimentError('user_access_denied') elif session.exp_name == "keep_track": - target_trials = KeepTrack.query.filter(KeepTrack.session_id==session.session_id, + target_trials = KeepTrack.query.filter(KeepTrack.session_id==session.session_id, KeepTrack.block.in_(["1", "2", "3", "4", "5", "6"])).all() - all_scored = [] + all_scored = [] for trial in target_trials: score = trial.simple_score() all_scored += score @@ -306,10 +315,10 @@ def results(): elif session.exp_name == "category_switch": single_trials_avg = db.session.query(func.avg(CategorySwitch.reaction_time).label('average')).filter( - CategorySwitch.session_id==session.session_id, CategorySwitch.block.in_(["sizeReal", "livingReal"]), + CategorySwitch.session_id==session.session_id, CategorySwitch.block.in_(["sizeReal", "livingReal"]), CategorySwitch.accuracy==1).all() mixed_trials_avg = db.session.query(func.avg(CategorySwitch.reaction_time).label('average')).filter( - CategorySwitch.session_id==session.session_id, CategorySwitch.block.in_(["mixedReal1", "mixedReal2"]), + CategorySwitch.session_id==session.session_id, CategorySwitch.block.in_(["mixedReal1", "mixedReal2"]), CategorySwitch.accuracy==1).all() ## This value also needs to be stored @@ -318,15 +327,84 @@ def results(): ## JAKE: You'd need to add another elif for your task. This is probably not the best way to code this but oh well ## Probably would be good to have the scoring functions all in one file and just call the right function for the git ## task, but lets leave that for later + elif session.exp_name == 'BART': + def date_handler(date): + return [date.year,date.month,date.day,date.hour,date.minute] + + + score = round(db.session.query(func.avg(BART.pumps).label('average')).filter(BART.session_id == session.session_id, + BART.user_action == 2).all()[0][0]) + session.results = score + db.session.commit() + + # data from subjects with completed sessions + completed_others = [_[0] for _ in db.session.query(distinct(Session.gfg_id)).filter( + # Session.gfg_id != gfg_id, + Session.exp_name == session.exp_name, + Session.status == 3).all()] + + # Individuals previous scores + previousSessions = [_[0] for _ in db.session.query(Session.results).filter(Session.gfg_id == gfg_id, + Session.exp_name == exp_name, + Session.status == 3).order_by( + asc(Session.session_id)).all()] + print previousSessions + dates = [date_handler(_[0]) for _ in db.session.query(Session.begin_session).filter(Session.gfg_id == gfg_id, + Session.exp_name == exp_name, + Session.status == 3).order_by( + asc(Session.session_id)).all()] + if len(previousSessions) == 0: + previousSessions = None + + # current score + print dates + + print 'Other participants' + print completed_others + if len(completed_others) > 0: + + mean_score = db.session.query(func.avg(Session.results).label('average')).filter( + Session.gfg_id.in_(completed_others), + Session.status == 3, + Session.exp_name == exp_name).group_by(Session.gfg_id).all() + + std_score = db.session.query(func.STD(Session.results).label('average')).filter( + Session.gfg_id.in_(completed_others), + Session.status == 3, + Session.exp_name == exp_name).group_by(Session.gfg_id).all() + + percentile = stats.z2p((score - mean_score[0][0]) / (std_score[0][0] + 0.0000001)) + otherResults = [_[0] for _ in db.session.query(Session.results).filter(Session.gfg_id.in_(completed_others), + Session.status == 3, + Session.exp_name == exp_name).all()] + + print otherResults + else: + percentile = None + otherResults = None + + return render_template(session.exp_name + "/results.html", + score=score, + others = otherResults, + previousSessions = previousSessions, + dates = dates, + percentile = int(percentile * 100) + ) + + # Do make sure to save the value that you computer for each subject here session.results = score db.session.commit() + ## I would also make sure to use the same method to get "comparion subjects" + ## to have consistency across GFG + ## Find other people in same age range. If more than 20, calculate percentile and display age_matched_ids = db_utils.get_age_matched_ids(gfg_id, current_app.config['RESEARCH_DB_HOST'], current_app.config['RESEARCH_DB_USER'], current_app.config['RESEARCH_DB_PASSWORD'], current_app.config['RESEARCH_DB_NAME']) - + # Importantly, I only show results if I have at least 20 control subjects, so when there is very little data + # I don't show anything because it can be misleading. if len(age_matched_ids) > 20: mean_score = db.session.query(func.avg(Session.results).label('average')).filter( Session.gfg_id.in_(age_matched_ids), Session.exp_name == session.exp_name, Session.status==3).all() @@ -334,13 +412,14 @@ def results(): std_score = db.session.query(func.STD(Session.results).label('average')).filter( Session.gfg_id.in_(age_matched_ids), Session.exp_name == session.exp_name, Session.status==3).all() - print mean_score percentile = stats.z2p((score - mean_score[0][0]) / (std_score[0][0] + 0.0000001)) else: percentile = None - return render_template(session.exp_name + "/results.html", + # If you're going to do something different, then you'd want this call to be in your own + # "if" branch, because presumable you'd want to pass different variables. + return render_template(session.exp_name + "/results.html", score=score, percentile=percentile) diff --git a/gfg_interactive/manage.py b/gfg_interactive/manage.py index adb23ab7..45e41745 100755 --- a/gfg_interactive/manage.py +++ b/gfg_interactive/manage.py @@ -1,5 +1,5 @@ -from flask.ext.script import Manager -from flask.ext.migrate import Migrate, MigrateCommand +from flask_script import Manager +from flask_migrate import Migrate, MigrateCommand import ConfigParser from app import app, db import os diff --git a/gfg_interactive/migrations/alembic.ini b/gfg_interactive/migrations/alembic.ini old mode 100755 new mode 100644 diff --git a/gfg_interactive/migrations/script.py.mako b/gfg_interactive/migrations/script.py.mako index 95702017..2c015630 100755 --- a/gfg_interactive/migrations/script.py.mako +++ b/gfg_interactive/migrations/script.py.mako @@ -1,18 +1,20 @@ """${message} Revision ID: ${up_revision} -Revises: ${down_revision} +Revises: ${down_revision | comma,n} Create Date: ${create_date} """ +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} # revision identifiers, used by Alembic. revision = ${repr(up_revision)} down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} def upgrade(): ${upgrades if upgrades else "pass"} diff --git a/gfg_interactive/migrations/versions/43c800fdf0c8_.py b/gfg_interactive/migrations/versions/23892fdee24d_.py old mode 100755 new mode 100644 similarity index 69% rename from gfg_interactive/migrations/versions/43c800fdf0c8_.py rename to gfg_interactive/migrations/versions/23892fdee24d_.py index a526c708..9dc19f07 --- a/gfg_interactive/migrations/versions/43c800fdf0c8_.py +++ b/gfg_interactive/migrations/versions/23892fdee24d_.py @@ -1,21 +1,23 @@ """empty message -Revision ID: 43c800fdf0c8 -Revises: None -Create Date: 2016-07-28 13:25:36.097195 +Revision ID: 23892fdee24d +Revises: +Create Date: 2017-04-20 11:47:15.423963 """ +from alembic import op +import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = '43c800fdf0c8' +revision = '23892fdee24d' down_revision = None - -from alembic import op -import sqlalchemy as sa +branch_labels = None +depends_on = None def upgrade(): - ### commands auto generated by Alembic - please adjust! ### + # ### commands auto generated by Alembic - please adjust! ### op.create_table('participant', sa.Column('id', sa.Integer(), nullable=False), sa.Column('gfg_id', sa.String(length=32), nullable=False), @@ -33,6 +35,20 @@ def upgrade(): sa.Column('results', sa.Float(precision=2), nullable=True), sa.PrimaryKeyConstraint('session_id') ) + op.create_table('BART', + sa.Column('action_id', sa.Integer(), nullable=False), + sa.Column('gfg_id', sa.String(length=32), nullable=False), + sa.Column('session_id', sa.Integer(), nullable=True), + sa.Column('trial_num', sa.Integer(), nullable=True), + sa.Column('trial_action_num', sa.Integer(), nullable=True), + sa.Column('user_action', sa.Integer(), nullable=True), + sa.Column('pumps', sa.Integer(), nullable=True), + sa.Column('pop_point', sa.Integer(), nullable=True), + sa.Column('timestamp', sa.DateTime(), nullable=True), + sa.Column('distribution', sa.String(length=300), nullable=True), + sa.ForeignKeyConstraint(['session_id'], ['session.session_id'], ), + sa.PrimaryKeyConstraint('action_id') + ) op.create_table('category_switch', sa.Column('cs_id', sa.Integer(), nullable=False), sa.Column('gfg_id', sa.String(length=32), nullable=False), @@ -41,10 +57,10 @@ def upgrade(): sa.Column('response', sa.String(length=2), nullable=True), sa.Column('reaction_time', sa.Float(), nullable=True), sa.Column('accuracy', sa.Integer(), nullable=True), - sa.Column('block', sa.Unicode(length=32), nullable=True), - sa.Column('question', sa.Unicode(length=32), nullable=True), - sa.Column('answer', sa.Unicode(length=32), nullable=True), - sa.Column('user_answer', sa.Unicode(length=32), nullable=True), + sa.Column('block', sa.String(length=200), nullable=True), + sa.Column('question', sa.String(length=100), nullable=True), + sa.Column('answer', sa.String(length=100), nullable=True), + sa.Column('user_answer', sa.String(length=100), nullable=True), sa.Column('timestamp', sa.DateTime(), nullable=True), sa.ForeignKeyConstraint(['session_id'], ['session.session_id'], ), sa.PrimaryKeyConstraint('cs_id') @@ -55,7 +71,7 @@ def upgrade(): sa.Column('session_id', sa.Integer(), nullable=True), sa.Column('exp_name', sa.String(length=32), nullable=False), sa.Column('event_type', sa.String(length=32), nullable=True), - sa.Column('value', sa.Unicode(length=32), nullable=True), + sa.Column('value', sa.String(length=100), nullable=True), sa.Column('interval', sa.Float(), nullable=True), sa.Column('timestamp', sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(['session_id'], ['session.session_id'], ), @@ -68,10 +84,10 @@ def upgrade(): sa.Column('trial_num', sa.Integer(), nullable=True), sa.Column('reaction_time', sa.Float(), nullable=True), sa.Column('accuracy', sa.String(length=32), nullable=True), - sa.Column('block', sa.Unicode(length=32), nullable=True), + sa.Column('block', sa.String(length=200), nullable=True), sa.Column('timestamp', sa.DateTime(), nullable=True), - sa.Column('target_words', sa.Unicode(length=32), nullable=True), - sa.Column('input_words', sa.Unicode(length=32), nullable=True), + sa.Column('target_words', sa.String(length=100), nullable=True), + sa.Column('input_words', sa.String(length=100), nullable=True), sa.ForeignKeyConstraint(['session_id'], ['session.session_id'], ), sa.PrimaryKeyConstraint('kt_id') ) @@ -87,15 +103,16 @@ def upgrade(): sa.ForeignKeyConstraint(['session_id'], ['session.session_id'], ), sa.PrimaryKeyConstraint('q_id') ) - ### end Alembic commands ### + # ### end Alembic commands ### def downgrade(): - ### commands auto generated by Alembic - please adjust! ### + # ### commands auto generated by Alembic - please adjust! ### op.drop_table('question_data') op.drop_table('keep_track') op.drop_table('event_data') op.drop_table('category_switch') + op.drop_table('BART') op.drop_table('session') op.drop_table('participant') - ### end Alembic commands ### + # ### end Alembic commands ### diff --git a/gfg_interactive/models.py b/gfg_interactive/models.py index 151823a8..852c4f39 100755 --- a/gfg_interactive/models.py +++ b/gfg_interactive/models.py @@ -148,29 +148,31 @@ def simple_score(self): class BART(db.Model): - bart_id = db.Column(db.Integer, primary_key=True) + action_id = db.Column(db.Integer, primary_key=True) gfg_id = db.Column(db.String(32), nullable=False) session_id = db.Column(db.Integer, db.ForeignKey('session.session_id')) trial_num = db.Column(db.Integer) - balloon_num = db.Column(db.Integer) + trial_action_num = db.Column(db.Integer) user_action = db.Column(db.Integer) # 1 = pump, 0 = pop, 2 = cash, 3 = reset pumps = db.Column(db.Integer) pop_point = db.Column(db.Integer) timestamp = db.Column(db.DateTime) - + distribution = db.Column(db.String(300)) # This is how the model prints itself def __repr__(self): - return "BART Values (%s, %s, %s, %s)" % (self.bart_id, self.gfg_id, - self.session_id, self.trial_num) + return "BART Values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.action_id, self.gfg_id, + self.session_id, self.trial_num, self.trial_action_num,self.user_action,self.pumps,self.pop_point,self.timestamp, self.distribution) def add_json_data(self, json_trial): - self.trial_num = json_trial['current_trial'] trial_data = json_trial['trialdata'] + self.trial_action_num = json_trial['current_trial'] + self.trial_num = trial_data['balloon_num'] self.user_action = trial_data['action'] self.pumps = trial_data['pumps'] self.pop_point = trial_data['pop_point'] - self.balloon_num = trial_data['balloon'] self.timestamp = convert_timestamp(json_trial['dateTime']) + self.distribution = trial_data['dist'] + class EventData(db.Model):