Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterHi Nico,
The experiment at your link shows no error and works well; I see that the code contains two
Template
generating one trial each (the two CSV tables list 8 groups from A to H each, and a single trial per group) and the sequence does indeed contain the two trialsWere you able to fix the issue in the meantime?
Jeremy
Jeremy
KeymasterHi Kate,
The name of the file in Resources is RadioButtonsSentence.png but you reference RadioButtonSentence.png in your table
The Text element is in fact printed on the page after you click A, B or C, because of the
wait
command on the Scale element coming beforeprint
on the Text element. The trial finishes immediately, because there is no further command to run, so you don’t even have time to effectively see the text on the pageJeremy
Jeremy
KeymasterHi,
Use CSS to control the Scale element’s aesthetics
Another solution would be to inject some HTML in the labels:
newScale("score", "<span style='margin: 2em;'>A</span>", "<span style='margin: 2em;'>B</span>", "<span style='margin: 2em;'>C</span>" ) .css({"font-size": "20px", "font-weight": "normal"}) .radio() .labelsPosition("top") .print() .wait()
Jeremy
Jeremy
KeymasterHi,
The project at https://farm.pcibex.net/r/IqekEL/ does not define the function
pick
. Also, you forgot...
beforerepetitions
inSequence
Once I fix both issues, the experiment runs with a mix of target and filler trials, as intended
Jeremy
Jeremy
KeymasterHi,
The identifier and video file are already logged as extra columns in the code that you posted. As far as I can tell, there is a single response time in that code, which corresponds to how much time elapsed since the beginning of the timer and when the participant presses space. So you could add a Var element to log the response time as an extra column like this:
Template( "eCIDEx.csv" , row => newTrial("Experiment1", newText(row.identifier) .css("font-size", "2.5em") .css("text-align", "center") .settings.center() .print() .log() , newVideo(row.videofile) .size("60vw", "auto") .settings.center() .print() .play() .disable(0.01) .log() , newVar("RT").global().set( () => Date.now() ) , newTimer("7.2s", 7200).start() , newKey(" ").log("last").callback( getTimer("7.2s").stop() ) , getTimer("7.2s").wait() , getVar("RT").set( v => Date.now() - v ) , getVideo(row.videofile).remove() , newImage("Fix", "Focus_Point.jpg") .center() .print("middle at 50%", "middle at 50%") , newTimer("Timer1", 1000).start().wait() ) .log("id", getVar("subjID")) .log("identifier", row.identifier) .log("videofile", row.videofile) .log("ResponseTime", getVar("RT")) .log("qualID", getVar("qualID")) )
Then you can systematically look at a single line in the results file (eg. the Text element’s log line) and get all the information you want in it
Jeremy
Jeremy
KeymasterHi,
The code above dates from 2019, there have been new releases of PennController since then, and Scale elements are no longer rendered as HTML
table
elementsJudging from the project you shared, it seems like you have come up with a solution, as you have this in global_z.css:
.PennController-Scale .option label { margin: 0em 1em; font-size: 18px; }
You could add a frame like this:
.PennController-Scale .option label { margin: 0em 1em; font-size: 18px; border:1px solid gray; }
Jeremy
Jeremy
KeymasterHi Nianpo,
Just add a second
.wait
:newAudio("audio", variable.audio).center().print().wait(), newTimer("callback",1).callback( getAudio("audio").wait().wait().remove() ).start()
Jeremy
November 29, 2022 at 6:18 pm in reply to: Randomization with no more than three items of same category in a row #9745Jeremy
KeymasterHi Dimitra,
Assuming you are willing to merge together the four blocks of experimental trials (otherwise the items sharing the same picture would never appear in a row to start with, since you seem to use each picture only once per block) you can make it work as in this project (I have updated some commands to match the modern PennController syntax — you also seemed to use the same code for all your trials, practice and experimental, so I use a single
Template
command)Jeremy
Jeremy
KeymasterHi Karen,
You don’t need the
callback
here, since you already have await
that ensures that the following code will always be executed after the key has been pressedYou also seem to confuse
key.log
andvar.log
withnewTrial().log
newTrial( // ... newVar("RT1").global().set( () => Date.now() ) , newKey("flan_RespKey", "FJ") .log() .wait() , getVar("RT1").set( v => Date.now() - v ) // ... ) .log("flanRT",getVar("RT1"))
Jeremy
Jeremy
KeymasterHi,
There are multiple things at play here, but a crucial one is that
print
andunfold
will add content to a new line each time, so you won’t be able to get everything on a single line using those commandsTo really have the input box in the same stream as the surrounding text, ideally it needs to be contained in a simple
span
HTML element surrounded withtextNode
elementsBecause you won’t be able to use the PennController command
unfold
, you will need to come up with a custom function that does the job of dynamically revealing the content of aspan
element. This is one such function, which you can place at the top of your script for example:function unfold(element,duration) { class TextNode { constructor(original){ this.original = original; this.replacer = document.createElement("SPAN"); this.replacer.style.visibility='hidden'; this.replacer.textContent = this.original.textContent; this.original.replaceWith(this.replacer); } unfold(n) { if (n>=this.original.length) return this.replacer.replaceWith(this.original); [...this.replacer.childNodes].forEach(n=>n.remove()); this.replacer.style.visibility = 'visible'; const vis = document.createTextNode(this.original.textContent.substring(0,n)); const hid = document.createElement("SPAN"); hid.style.visibility = 'hidden'; hid.innerText = this.original.textContent.substring(n); this.replacer.append(vis); this.replacer.append(hid); } } let toUnfold = (function buildArray(el){ const ar = []; if (el.childNodes.length>0) { for (let i=0; i<el.childNodes.length; i++) ar.push(...buildArray(el.childNodes[i],duration,'hold')); } else if (el.nodeName=="#text") ar.push(new TextNode(el)); else if (el.style.visibility!="hidden") { el.style.visibility='hidden'; ar.push(el); } return ar; })(element); let totalChar = 0; toUnfold = toUnfold.map(e=>{ const n = e instanceof TextNode ? e.original.length : 1; totalChar += n; return {element: e, len: n, total: totalChar}; }); const perChar = duration / totalChar; let startTime; return new Promise(r=> { const updateUnfold = timestamp => { if (startTime===undefined) startTime = timestamp; const elapsed = timestamp-startTime, progress = elapsed/perChar; if (elapsed >= duration) { toUnfold.forEach( e=>e.element instanceof TextNode ? e.element.unfold(e.len+1) : e.element.style.visibility='visible' ); r(); } else { let total = 0; toUnfold.find(e=>{ const lastToReveal = progress < e.total; if (e.element instanceof TextNode) e.element.unfold(lastToReveal ? progress-total : progress); else e.element.style.visibility = 'visible'; total += e.len; return lastToReveal; }); window.requestAnimationFrame(updateUnfold); } }; window.requestAnimationFrame(updateUnfold); }); }
Then integrating it inside a trial is pretty easy:
newTextInput("filledInBlank") .size("6em", "1.5em") .lines(1) .css({ "outline": "none", "resize": "none", "border": "0", "padding": "0", "margin": "0", "margin-left": "1ex", "margin-right": "1ex", "vertical-align": "-.33em", "background-color": "white", "border-bottom": "2px solid black", "display": "inline" }) .cssContainer({"display": "inline", "font-size": "16px"}) .print() , newFunction( async ()=> { const answer = document.querySelector("textarea.PennController-filledInBlank"); const container = document.createElement("SPAN"); answer.replaceWith(container); const before = document.createTextNode( row.before_blank ); const after = document.createTextNode( row.after_blank ); container.append(before); container.append(answer); container.append(after); await unfold(container, parseInt(row.time_before)+parseInt(row.time_after) ); }).call() , newButton("moveOn", "Continue") .right() .print() .wait()
Jeremy
Jeremy
KeymasterHi,
Looking at your question and your tables, I think that you are talking about presenting several stimuli within a *single* trial, and that your subsequences
prime-target
correspond to single trials. As far as I understand, a single trial in your experiment has the following structure:- Show a prime word
- Show a target word
- Show a post-target word (filler trials only)
My suggestion is you add a column after
target
in your table for the third word, and simply leave it empty for the target trials. Then, in thenewTrial
command insideTemplate
, you include the third stimulus only for the filler items. The basic idea would be something like this:Table:
group,prime,target,post,pairtype,condition,correct A,prime1A,target1A,,target,noun.noun,f A,prime2A,target2A,,target,noun.noun,f A,prime3A,target3A,,target,noun.noun,f A,prime4A,target4A,,target,noun.noun,f A,prime5A,target5A,,target,noun.noun,f A,filler1A,filler1A,filler1A,filler,filler,f A,filler2A,filler2A,filler2A,filler,filler,f A,filler3A,filler3A,filler3A,filler,filler,f A,filler4A,filler4A,filler4A,filler,filler,f A,filler5A,filler5A,filler5A,filler,filler,f
Script:
Template( "mytable.csv" , row => newTrial( row.condition , newText( "prime", row.prime ).print(), newTimer(1000).start().wait(), getText( "prime" ).remove() , newText( "target", row.target ).print(), newTimer(1000).start().wait(), getText( "target" ).remove() , ...( row.condition=="filler" ? [ newText( "post", row.post ).print(), newTimer(1000).start().wait(), getText( "post" ).remove() ] : [] ) // ... ) .log("pairtype",row.pairtype) // ... )
Jeremy
Jeremy
KeymasterHi Nasim,
Not sure why the
@import
rule doesn’t work, but I don’t think usingurl("https://fonts.googleapis.com/css2?family=Raleway:wght@500&display=swap")
as thesrc
attribute in@font-face
is valid anywayHowever, if I visit that URL and copy-paste the raw CSS rules back in global_z.css in place of the
@font-face
rule you currently have, I do see the Google font when I open the experimentJeremy
Jeremy
KeymasterHi,
Indeed, there was an issue with the registration/activation process and two accounts were created with the same username and email address, and the two accounts owned different experiments. I have kept only the first user, and assigned all the experiments to that account. You should see all your projects listed once you log out and in again
Jeremy
Jeremy
KeymasterHello,
Yes, you can use the javascript ternary conditional operator:
...( row.audio13.length>0 ? [ newAudio("audio13", row.audio13) , newButton("(Re)play audio13") .callback( getAudio("audio13") .play() ) .print() ] : [] )
Jeremy
Jeremy
KeymasterHi,
This is because PennController elements are created immediately, but PennController commands are delayed:
getVar()
does not return a string, it returns an object that some PennController commands will use to retrieve its valueWhat you can do is generate a unique ID for each participant and use it both in the Audio elements’ names and in a
log
command on thenewTrial()
s so you can cross-reference things from the results file, for instance:uniqueID = [1,2,3,4].map(v=>Math.floor((1+Math.random())*0x10000).toString(16).substring(1)).join('-'); Template( "exp5.csv" , row => newTrial( "exp5Task" , // ... newMediaRecorder(uniqueID+"_"+getVar("ID"),"audio").record() // ... ) .log("uniqueID", uniqueID) )
Jeremy
-
AuthorPosts