Physlets and JavaScript can be used to grade vector drawings. For example, you may duplicate and use questions 325457 and 327547 to grade drawings of electric field vectors and force vectors. An example from kinematics is question 242851.
Writing a new question using the vector grader is not a simple task. It requires defining WebAssign variables that can be randomize, setting up the picture or animation in the Physlet using the WebAssign variables, calculating the answer key (i.e. correct vector components), and setting the appropriate grading criteria and tolerances in the JavaScript.
The following example is similar to question 325457 and is taken from Matter & Interactions, Vol. II. Part (a) of the question will not be graded automatically here; however, you can check your answer to part (b) using the links below the Physlet.
Example 325457
A hollow ball with radius R = 2 cm has a charge of
-4 nC spread uniformly over its surface (Figure 13.47). The center of the ball is at P1 = <-1, 0, 0> cm. A point charge of 6 nC is located at P3 = <5, 0, 0> cm.
(a) What is the net electric field at location P2 = 0, 8, 0> cm?
[-3900],
[-351],
[0]> N/C
(b) At location P2 draw and label two arrows representing the electric field due to the ball and the electric field due to the point charge. The arrows should be in the correct direction; and the relative lengths of the arrows should be consistent with the relative magnitudes of the electric fields.
How to write your own question using the Physlet vector grader
The general procedure is as follows:
Define WebAssign variables that will be used to determine the correct vectors and the picture or animation in the Physlet.
Calculate the correct answer using the WebAssign variables.
Define the correct vector components using JavaScript.
Set the grading critera.
Modify the Physlet.
The code for the previous question is pasted below. I describe the areas that you will need to change in order to customize for your needs.
Here I define variables in WebAssign that can later be randomized. The values used for the Physlet will refer to these variables. The answer key will be calculated based on these variables as well. The variable $Escale will be used to scale the lengths of the vectors when they are drawn in the Physlet.
This is where you define grading criteria and the correct vectors. Make sure that after setting the values of the components of the correct vectors, you add them to the arrays.
<script language="JavaScript">
///////////////////////////////////////////////////////////////
/////////////There are two main areas to edit (besides the
////////////question and applet down in the body of the page
///////////below the javascript).
//////////
///////// (1) Edit the grading criteria immediately below.
//////// (2) Edit the startProb() function to include all
/////// of the physlet methods to create the animation;
////// be sure to add the line
///// appletLoaded=true;
//// to your startProb() funtion.
///
//////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////Bugs and features to be added:
////////////
///////////
//////////
///////// (1) Zero-magnitude vectors can cause a correct answer
//////// to be graded wrong.
/////// (2) Need to add a grading criteria of "vector magnitude ratios"
////// so that on free-body diagrams, for example, you can grade
///// whether one vector is twice the magnitude of another.
//// (3) Change vector drawing to draw a vector based on mouse clicks;
/// click for the tail of the vector, and click for the head of
// the vector.
//
//////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////Credits:
////////////
///////////
////////// The JavaScript code used here to grade vector drawings
///////// on Physlets was developed by Aaron Titus and is based upon
//////// work supported by the National Science Foundation under
/////// Grant No. DUE-9952323. Physlets were created by
////// Wolfgang Christian of Davidson College.
/////
//// Please include these credits and give any comments,
/// suggestions, or additional code to Aaron Titus at
// titus@mailaps.org. You may freely modify, use, and
// distribute this code.
//
//
//////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////
//////////// Developers
///////////
////////// Please add your name to this list if you've
///////// contributed to this code.
////////
/////// Aaron Titus
//////
//////////////////////////////////////////////////////////////
//grading criteria
gradeMagnitudes=false;
gradeAngles=true;
gradeComponents=false;
gradePositions=true; //evaluate the location where vectors are drawn from
gradeSum=false; //evaluate the sum of the vectors drawn
gradeSumAngle=true; //evaluate just the angle of the net vector
posTol=0.4; //absolute tolerance
compTol=0.1; //absolute tolerance
magTol=0; //absolute tolerance
angTol=10*Math.PI/180; //absolute tolerance in radians
sumTol=1; //absolute tolerance
sumAngTol=20*Math.PI/180; //absolute tolerance in radians
//
//display options
showMagnitude=false;
showAngle=false;
showLabel=true;
setDragable=true;
setResizable=true;
//
//components of correct vectors in the form of variables, x1 and y1, x2 and y2 etc.
//and positions from which vectors should be drawn
x1=<EQN $E1x*$Escale>;
y1=<EQN $E1y*$Escale>;
xPos1=<EQN $p2x>;
yPos1=<EQN $p2y>;
label1="E1";
x3=<EQN $E3x*$Escale>;
y3=<EQN $E3y*$Escale>;
xPos3=<EQN $p2x>;
yPos3=<EQN $p2y>;
label3="E3";
//
//use this for more than one vector, make sure all vector components are in the array
xVectorCompAnswers=new Array(x1,x3);
yVectorCompAnswers=new Array(y1,y3);
xVectorPosAnswers=new Array(xPos1,xPos3);
yVectorPosAnswers=new Array(yPos1,yPos3);
labelAnswers=new Array(label1,label3);
//
//positions at which new vectors are first drawn; the student can move the vectors from this position; this should be near the center of the applet
x=4;
y=4;
xTailPos=5;
yTailPos=10;
//
You won't need to modify any of these JavaScript functions.
//global variables initialized
dr=0;
appletLoaded=false;
//initialization of arrays
vectorID=0; //physlet object id number initialized
vectorArray=new Array(); //array of physlet object id numbers for the drawn vectors
xVectorComponents=new Array(); //array of x-components of drawn vectors
yVectorComponents=new Array();//array of y-components of drawn vectors
xVectorPositions=new Array(); //array of x-positions of tails of vectors
yVectorPositions=new Array();//array of y-positions of tails of vectors
vectorLabels=new Array(); //array of label values
////////////////////////////////////
function test_<EQN $numAnim>() {
var str=getResponse_<EQN $numAnim>();
// alert(str);
// setResponse_<EQN $numAnim>(str);
grade=isCorrect_<EQN $numAnim>(str);
alert(grade);
}
function setKey_<EQN $numAnim>() {
var str=computeAnswer_<EQN $numAnim>();
// alert(str);
setResponse_<EQN $numAnim>(str);
// grade=isCorrect_<EQN $numAnim>(str);
// alert(grade);
}
function reinitArrays_<EQN $numAnim>() {
//initialization of arrays
vectorID=0; //physlet object id number initialized
vectorArray=new Array(); //array of physlet object id numbers for the drawn vectors
xVectorComponents=new Array(); //array of x-components of drawn vectors
yVectorComponents=new Array();//array of y-components of drawn vectors
xVectorPositions=new Array(); //array of x-positions of tails of vectors
yVectorPositions=new Array();//array of y-positions of tails of vectors
vectorLabels=new Array(); //array of label values
}
function getResponse_<EQN $numAnim>() {
var i=0;
// alert(vectorArray.length);
do {
id=vectorArray[i];
if(id==undefined){break;} //check to make sure vector is indeed drawn
//since getW and getH will return an error
x=document.Animator_<EQN $numAnim>.getXPos(id);
y=document.Animator_<EQN $numAnim>.getYPos(id);
dx=document.Animator_<EQN $numAnim>.getW(id);
dy=document.Animator_<EQN $numAnim>.getH(id);
xVectorComponents[i]=dx;
yVectorComponents[i]=dy;
xVectorPositions[i]=x;
yVectorPositions[i]=y;
i++;
}
while(i<(vectorArray.length));
xPosStr=xVectorPositions.join();
yPosStr=yVectorPositions.join();
xCompStr=xVectorComponents.join();
yCompStr=yVectorComponents.join();
labelStr=vectorLabels.join();
response=xPosStr+"~"+yPosStr+"~"+xCompStr+"~"+yCompStr+"~"+labelStr;
// alert(response);
return response;
}
function setResponse_<EQN $numAnim>(response) {
startProb_<EQN $numAnim>();
// alert(response);
document.Animator_<EQN $numAnim>.pause();
document.Animator_<EQN $numAnim>.stepBack();
document.Animator_<EQN $numAnim>.setAutoRefresh(false);
delimiter="~";
responseArray=response.split(delimiter);
xPosStr=(responseArray[0]);
yPosStr=(responseArray[1]);
xCompStr=(responseArray[2]);
yCompStr=(responseArray[3]);
labelStr=(responseArray[4]);
xVectorPositions=xPosStr.split(",");
yVectorPositions=yPosStr.split(",");
xVectorComponents=xCompStr.split(",");
yVectorComponents=yCompStr.split(",");
vectorLabels=labelStr.split(",");
var i=0;
do {
// alert(i);
id=document.Animator_<EQN $numAnim>.addObject("arrow2","x=0,y=0,v=1,h=1,filled,thickness=5");
document.Animator_<EQN $numAnim>.setDragable(id,true);
document.Animator_<EQN $numAnim>.setResizable(id,true);
document.Animator_<EQN $numAnim>.setW(id,eval(xVectorComponents[i]));
document.Animator_<EQN $numAnim>.setH(id,eval(yVectorComponents[i]));
document.Animator_<EQN $numAnim>.setX(id,eval(xVectorPositions[i]));
document.Animator_<EQN $numAnim>.setY(id,eval(yVectorPositions[i]));
if(setDragable==true){
document.Animator_<EQN $numAnim>.setDragable(id,true); //false is default
}
if(setDragable==true){
document.Animator_<EQN $numAnim>.setResizable(id,true); //false is default
}
if(showMagnitude==true) {
magCalcID=document.Animator_<EQN $numAnim>.addObject("calculation","x=-2.7,y=0,text=Mag.=");
document.Animator_<EQN $numAnim>.makeDataConnection(id,magCalcID,2,"sqrt(w*w+h*h)","0");
document.Animator_<EQN $numAnim>.makeDataConnection(id,magCalcID,1,"x+w/2","y+h/2");
}
if(showAngle==true) {
angCalcID=document.Animator_<EQN $numAnim>.addObject("calculation","x=-2.7,y=0,text=Ang.=");
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,angCalcID,2,"atan(h/w)*180/pi","0");
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,angCalcID,1,"x+w/4","y+h/4");
}
if(showLabel==true) {
labelID=document.Animator_<EQN $numAnim>.addObject("text","x=0,y=0,text="+vectorLabels[i]);
document.Animator_<EQN $numAnim>.makeDataConnection(id,labelID,1,"x+w*0.8","y+h*0.8");
}
vectorArray[i]=id;
i++;
}
while(i<(xVectorComponents.length));
document.Animator_<EQN $numAnim>.updateDataConnections();
document.Animator_<EQN $numAnim>.setAutoRefresh(true);
}
function isCorrect_<EQN $numAnim>() {
response=getResponse_<EQN $numAnim>();
result=1;
delimiter="~";
responseArray=response.split(delimiter);
xPosStr=(responseArray[0]);
yPosStr=(responseArray[1]);
xCompStr=(responseArray[2]);
yCompStr=(responseArray[3]);
xVectorPositions=xPosStr.split(",");
yVectorPositions=yPosStr.split(",");
xVectorComponents=xCompStr.split(",");
yVectorComponents=yCompStr.split(",");
matchedIndex=new Array(); //store index when a correct vector is matched with the answer
var i=0;
do { //loop through all correct vectors to look for a match
xPosResp=eval(xVectorPositions[i]);
yPosResp=eval(yVectorPositions[i]);
xResp=eval(xVectorComponents[i]);
yResp=eval(yVectorComponents[i]);
magResp=Math.sqrt(xResp*xResp+yResp*yResp);
angRadResp=Math.atan2(yResp,xResp);
//for this correct vector, look for a drawn vector that matches
j=0;
do {
match=0;
matchComp=false;
matchMag=false;
matchAng=false;
matchPos=false;
// alert("i="+i+"\n\nj="+j);
//if student does not draw enough vectors or draws too many vectors,
//the answer is wrong; the code should trap for vectors of zero magnitude--this is a
//feature to be added
if (xVectorComponents.length != xVectorCompAnswers.length) {
match=0;
// alert("no match");
break;
}
matchedBefore=false;
//see if vector has been matched before
for (var index=0; index<matchedIndex.length; index++) {
if(matchedIndex[index]==j) {matchedBefore=true; break;}
}
if(matchedBefore==false) {
xPosAns=eval(xVectorPosAnswers[j]);
yPosAns=eval(yVectorPosAnswers[j]);
xAns=eval(xVectorCompAnswers[j]);
yAns=eval(yVectorCompAnswers[j]);
magAns=Math.sqrt(xAns*xAns+yAns*yAns);
angRadAns=Math.atan2(yAns,xAns);
if(gradeComponents==true) {
// alert(xResp+"\t"+xAns);
// alert(yResp+"\t"+yAns);
// alert(xPosResp+"\t"+xPosAns);
// alert(yPosResp+"\t"+yPosAns);
if(((xAns-compTol)<=xResp && xResp<=(xAns+compTol)) && ((yAns-compTol)<=yResp && yResp<=(yAns+compTol))) {
matchComp=true;
}
}
if(gradeMagnitudes==true) {
// alert(xResp+"\t"+xAns);
// alert(yResp+"\t"+yAns);
// alert(magResp+"\t"+magAns);
if(((magAns-magTol)<magResp && magResp<(magAns+magTol))) {
matchMag=true;
}
}
if(gradeAngles==true) {
// alert(xResp+"\t"+xAns);
// alert(yResp+"\t"+yAns);
// alert(angRadResp+"\t"+angRadAns);
if(((angRadAns-angTol)<=angRadResp && angRadResp<=(angRadAns+angTol))) {
matchAng=true;
}
}
if(gradePositions==true) {
if(((xPosAns-posTol)<=xPosResp && xPosResp<=(xPosAns+posTol)) && ((yPosAns-posTol)<=yPosResp && yPosResp<=(yPosAns+posTol))) {
matchPos=true;
}
}
if(matchComp==gradeComponents && matchMag==gradeMagnitudes && matchAng==gradeAngles && matchPos==gradePositions) {match=1;}
if(match==1){matchedIndex.push(j); break;}
}
j++;
}
while(j<xVectorCompAnswers.length)
if(match==0){result=0; break;} //break out of loop no correct match was found
i++;
}
while(i<(xVectorCompAnswers.length));
if(gradeSum==true) { //check whether net vector drawn equals the correct net vector
xCompSum=0;
yCompSum=0;
xCompSumAns=0;
yCompSumAns=0;
i=0;
do {
xCompSumAns=xCompSumAns+eval(xVectorCompAnswers[i]);
yCompSumAns=yCompSumAns+eval(yVectorCompAnswers[i]);
i++;
}
while(i<(xVectorCompAnswers.length))
j=0;
do {
xCompSum=xCompSum+eval(xVectorComponents[j]);
yCompSum=yCompSum+eval(yVectorComponents[j]);
j++;
}
while(j<(xVectorComponents.length))
if(!(((xCompSumAns-sumTol)<=xCompSum && xCompSum<=(xCompSumAns+sumTol)) && ((yCompSumAns-sumTol)<=yCompSum && yCompSum<=(yCompSumAns+sumTol)))) {
result=0;
}
//alert("xCompSum="+xCompSum+"\n yCompSum="+yCompSum+"\n xCompSumAns="+xCompSumAns+"\n yCompSumAns="+yCompSumAns);
}
if(gradeSumAngle==true) { //check whether angle of the net vector is correct
xCompSum=0;
yCompSum=0;
xCompSumAns=0;
yCompSumAns=0;
i=0;
do {
xCompSumAns=xCompSumAns+eval(xVectorCompAnswers[i]);
yCompSumAns=yCompSumAns+eval(yVectorCompAnswers[i]);
i++;
}
while(i<(xVectorCompAnswers.length))
j=0;
do {
xCompSum=xCompSum+eval(xVectorComponents[j]);
yCompSum=yCompSum+eval(yVectorComponents[j]);
j++;
}
while(j<(xVectorComponents.length))
angRadAns=Math.atan2(yCompSumAns,xCompSumAns);
angRad=Math.atan2(yCompSum,xCompSum);
if(!(((angRadAns-angTol)<=angRad && angRad<=(angRadAns+angTol)))) {
result=0;
}
}
return result;
}
function computeAnswer_<EQN $numAnim>() {
xPosStr=xVectorPosAnswers.join();
yPosStr=yVectorPosAnswers.join();
xCompStr=xVectorCompAnswers.join();
yCompStr=yVectorCompAnswers.join();
labelStr=labelAnswers.join();
answer=xPosStr+"~"+yPosStr+"~"+xCompStr+"~"+yCompStr+"~"+labelStr;
return answer;
}
function computeFeedback_<EQN $numAnim>(feedback) {
feedback="Please review the concepts";
return feedback;
}
function draw_<EQN $numAnim>() {
var myLabel=0;
if (appletLoaded==false) {startProb();}
document.Animator_<EQN $numAnim>.pause();
document.Animator_<EQN $numAnim>.setAutoRefresh(false);
vectorID=document.Animator_<EQN $numAnim>.addObject("arrow2","x="+xTailPos+",y="+yTailPos+",v="+x+",h="+y+",filled,thickness=5");
if(setDragable==true){document.Animator_<EQN $numAnim>.setDragable(vectorID,true);} //false is default
if(setDragable==true){document.Animator_<EQN $numAnim>.setResizable(vectorID,true);} //false is default
if(showMagnitude==true) {
magCalcID=document.Animator_<EQN $numAnim>.addObject("calculation","x=-2.7,y=0,text=Mag.=");
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,magCalcID,2,"sqrt(w*w+h*h)","0");
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,magCalcID,1,"x+w/2","y+h/2");
}
if(showAngle==true) {
angCalcID=document.Animator_<EQN $numAnim>.addObject("calculation","x=-2.7,y=0,text=Ang.=");
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,angCalcID,2,"atan(h/w)*180/pi","0");
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,angCalcID,1,"x+w/4","y+h/4");
}
if(showLabel==true) {
myLabel=prompt("Please type a label for the vector","");
labelID=document.Animator_<EQN $numAnim>.addObject("text","x=0,y=0,text="+myLabel);
document.Animator_<EQN $numAnim>.makeDataConnection(vectorID,labelID,1,"x+w*0.8","y+h*0.8");
}
lastIndex=vectorArray.length;
vectorArray[lastIndex]=vectorID;
xVectorComponents[lastIndex]="default";
yVectorComponents[lastIndex]="default";
xVectorPositions[lastIndex]="default";
yVectorPositions[lastIndex]="default";
if(myLabel!=0){vectorLabels[lastIndex]=myLabel;}
else{vectorLabels[lastIndex]="default";}
document.Animator_<EQN $numAnim>.updateDataConnections();
document.Animator_<EQN $numAnim>.setAutoRefresh(true);
}
function clearScreen_<EQN $numAnim>() {
document.Animator_<EQN $numAnim>.setDefault();
reinitArrays_<EQN $numAnim>();
startProb_<EQN $numAnim>();
document.Animator_<EQN $numAnim>.pause();
document.Animator_<EQN $numAnim>.stepBack();
}
function resetToZero_<EQN $numAnim>() {
var str=getResponse_<EQN $numAnim>();
// alert(str);
if(str!="~~~~"){
startProb_<EQN $numAnim>();
document.Animator_<EQN $numAnim>.pause();
document.Animator_<EQN $numAnim>.stepBack();
setResponse(str);
}
else{
startProb_<EQN $numAnim>();
document.Animator_<EQN $numAnim>.pause();
document.Animator_<EQN $numAnim>.stepBack();
}
}
This is the main Physlet function that draws the picture or create the animation. You will need to modify this function in order to create a new question.
The actual question is below. Of course, you would modify this part to create a new question.
<p> A hollow ball with radius R = 2 cm has a charge of
<eqn decform($q1*1e9,0)> nC spread uniformly over its surface (Figure 13.47). The center of
the ball is at P1 = <<eqn decform($p1x,0)>, <eqn decform($p1y,0)>, 0> cm.
A point charge of <eqn decform($q3*1e9,0)> nC is located at
P3 = <<eqn decform($p3x,0)>, <eqn decform($p3y,0)>, 0> cm.
<p>
(a) What is the net electric field at location
<I>P<SUB>2</sub></i> = <<eqn $p2x>, <eqn $p2y>, 0> cm?
<BR><<_>,<_>,<_>> N/C
<p>
<SECTION>(b) At location P2 draw and label two arrows representing the electric
field due to the ball and the electric field due to the point charge. The arrows should
be in the correct direction; and the relative lengths of the arrows should be consistent
with the relative magnitudes of the electric fields.</p>
<p>
</p>
<hr>
<table border=0>
<tr>
<td>
<table border=0><tr><td>
<MAP NAME="palette_sm">
<AREA SHAPE=RECT COORDS="3,1,69,67" HREF="javascript:onclick=draw_<EQN $numAnim>()" ALT="Draw New Vector">
<AREA SHAPE=RECT COORDS="5,69,69,133" HREF="javascript:onclick=clearScreen_<EQN $numAnim>()" ALT="Clear Drawing">
<AREA SHAPE=default NOHREF>
</map>
<IMG SRC="/userimages/titus@ncat/palette_sm.jpg" USEMAP="#palette_sm">
</td></tr>
<tr><td align="center">
</td></tr>
</table>
</td>
<td>
<applet width="400" height="400" code="animator4.Animator.class" codebase="/physlets/" archive="Animator4_.jar,STools4.jar" align="baseline" id="Animator_<EQN $numAnim>" name="Animator_<EQN $numAnim>">
<param name="FPS" value="10">
<param name="dt" value="0.1">
<param name="showControls" value="false">
</applet>
</td>
<tr>
<td> </td>
<td>
<center>
<p><a href="javascript:startProb_<EQN $numAnim>()">View Picture</a></p>
<input type="button" value="reset" onclick="resetToZero_<EQN $numAnim>()">
</center>
</td>
</tr>
</table>
There are a couple of JavaScript functions that are useful for testing things as you are writing the question. Uncomment the following lines (by removing the HTML comment beginning and ending tags: ) to access these functions. The test function returns a 0 for an incorrect answer and a 1 for a correct answer.