Forum Replies Created
-
AuthorPosts
-
JeremyKeymasterHi Nico,
The experiment at your link shows no error and works well; I see that the code contains two
Templategenerating 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
JeremyKeymasterHi 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
waitcommand on the Scale element coming beforeprinton 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
JeremyKeymasterHi,
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
JeremyKeymasterHi,
The project at https://farm.pcibex.net/r/IqekEL/ does not define the function
pick. Also, you forgot...beforerepetitionsinSequenceOnce I fix both issues, the experiment runs with a mix of target and filler trials, as intended
Jeremy
JeremyKeymasterHi,
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
JeremyKeymasterHi,
The code above dates from 2019, there have been new releases of PennController since then, and Scale elements are no longer rendered as HTML
tableelementsJudging 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
JeremyKeymasterHi 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 #9745
JeremyKeymasterHi 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
Templatecommand)Jeremy
JeremyKeymasterHi Karen,
You don’t need the
callbackhere, since you already have awaitthat ensures that the following code will always be executed after the key has been pressedYou also seem to confuse
key.logandvar.logwithnewTrial().lognewTrial( // ... newVar("RT1").global().set( () => Date.now() ) , newKey("flan_RespKey", "FJ") .log() .wait() , getVar("RT1").set( v => Date.now() - v ) // ... ) .log("flanRT",getVar("RT1"))Jeremy
JeremyKeymasterHi,
There are multiple things at play here, but a crucial one is that
printandunfoldwill 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
spanHTML element surrounded withtextNodeelementsBecause 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 aspanelement. 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
JeremyKeymasterHi,
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-targetcorrespond 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
targetin your table for the third word, and simply leave it empty for the target trials. Then, in thenewTrialcommand 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
JeremyKeymasterHi Nasim,
Not sure why the
@importrule doesn’t work, but I don’t think usingurl("https://fonts.googleapis.com/css2?family=Raleway:wght@500&display=swap")as thesrcattribute in@font-faceis valid anywayHowever, if I visit that URL and copy-paste the raw CSS rules back in global_z.css in place of the
@font-facerule you currently have, I do see the Google font when I open the experimentJeremy
JeremyKeymasterHi,
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
JeremyKeymasterHello,
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
JeremyKeymasterHi,
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
logcommand 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