parserGraphTool.pl - Allow students to enter basic graphical answers via interactive JavaScript.
GraphTool objects let you provide an interactive graphing tool for students to enter graphical answers.
To create a GraphTool object pass a list of graph objects (discussed below) for the students to graph to GraphTool()
. For example:
$gt = GraphTool("{line,solid,(0,0),(1,1)}", "{circle,dashed,(2,2),(4,2)}");
or
$gt = GraphTool("{line,solid,(0,0),(1,1)}")->with(bBox => [-20, 20, 20, -20]);
Then, for standard PG use $gt->ans_rule()
to insert the JavaScript graph into the problem (or a print graph when a hard copy is generated), and $gt->cmp
to produce the answer checker. For example:
BEGIN_TEXT
Graph the line \(y = x\).
$PAR
\{$gt->ans_rule()\}
END_TEXT
ANS($gt->cmp);
For PGML you can just do
BEGIN_PGML
Graph the line [`y = x`].
[_]{$gt}
END_PGML
There are eight types of graph objects that the students can graph. Points, lines, circles, parabolas, quadratics, cubics, intervals, and fills (or shading of a region). The syntax for each of these objects to pass to the GraphTool constructor is summarized as follows. Each object must be enclosed in braces. The first element in the braces must be the name of the object. The following elements in the braces depend on the type of element.
For points the name "point" must be followed by the coordinates. For example:
"{point,(3,5)}"
For lines the name "line" must be followed by the word "solid" or "dashed" to indicate if the line is expected to be drawn solid or dashed. That is followed by two distinct points on the line. For example:
"{line,dashed,(1,5),(3,4)}"
For circles the name "circle" must be followed by the word "solid" or "dashed" to indicate if the circle is expected to be drawn solid or dashed. That is followed by the point that is to be the center of circle, and then by a point on the circle. For example:
"{circle,solid,(1,1),(4,5)}"
For parabolas the name "parabola" must be followed by the word "solid" or "dashed" to indicate if the parabola is expected to be drawn solid or dashed. The next element in the braces must be the word "vertical" for a parabola that opens up or down, or "horizontal" for a parabola that opens to the left or right. That is followed by the vertex and then another point on the parabola. For example:
"{parabola,solid,vertical,(1,0),(3,3)}"
For three point quadratics the name "quadratic" must be followed by the word "solid" or "dashed" to indicate if the quadratic is expected to be drawn solid or dashed. That is followed by the three points that define the quadratic. For example:
"{quadratic,solid,(-1,2),(1,0),(3,3)}"
For four point cubics the name "cubic" must be followed by the word "solid" or "dashed" to indicate if the cubic is expected to be drawn solid or dashed. That is followed by the four points that define the cubic. For example:
"{cubic,solid,(1,-3),(-1,2),(4,3),(3,2)}"
For fills the name "fill" must be followed by a point in the region that is to be filled. For example:
"{fill,(5,5)}"
For intervals the name "interval" must be followed by a single interval. Some examples are:
"{interval,[3,10)}"
"{interval,(-infinity,8]}"
"{interval,(2,infinity)}"
Note that for an infinite interval endpoint in a correct answer you may use "inf", or anything that is interpreted into a MathObject infinity. However, for static graph objects it must be "infinity". The JavaScript will always return "infinity" for student answers.
The student answers that are returned by the JavaScript will be a list of the list objects discussed above and will be parsed by WeBWorK and passed to the checker as such. The default checker is designed to grade the graph based on appearance. This means that if a student graphs duplicate objects, then the duplicates are ignored. Furthermore, if two objects are graphed whose only difference is that one is solid and the other is dashed (in this case the dashed object is covered by the solid object and only the solid object is really visible), then the dashed object is ignored.
A custom list_checker may be provided instead of using the default checker. This can either be passed as part of the cmpOptions
hash discussed below, or directly to the GraphTool object's cmp()
method. The variable $graphToolObjectCmps
can be used in a custom checker and contains a hash whose keys are the types of the objects described above, and whose values are methods that can be called passing a MathObject list constructed from one of the objects described above. When one of these methods is called it will return two methods. The first method when called passing a MathObject point will return 0 if the point satisfies the equation of the object, -1 if the equation evaluated at the point is negative, and 1 if the equation evaluated at the point is positive. The second method when called passing another MathObject list constructed from one of the objects described as above will return 1 if the two objects are exactly the same, and 0 otherwise. A second parameter may be passed and if that parameter is 1, then the method will return 1 if the two objects are the same ignoring if the two objects are solid or dashed, and 0 otherwise. In the following example, the $lineCmp
method is defined to be the second method (indexed by 1) that is returned by calling the 'line'
method on the first correct answer in the example.
$m = 2 * random(1, 4);
$gt = GraphTool("{line, solid, ($m / 2, 0), (0, -$m)}")->with(
bBox => [ -11, 11, 11, -11 ],
cmpOptions => {
list_checker => sub {
my ($correct, $student, $ans, $value) = @_;
return 0 if $ans->{isPreview};
my $score = 0;
my @errors;
my $lineCmp = ($graphToolObjectCmps->{line}->($correct->[0]))[1];
for (0 .. $#$student) {
if ($lineCmp->($student->[$_])) { ++$score; next; }
my $nth = Value::List->NameForNumber($_ + 1);
if ($student->[$_]->extract(1) ne 'line') {
push(@errors, "The $nth object graphed is not a line.");
next;
}
if ($student->[$_]->extract(2) ne 'solid') {
push(@errors, "The $nth object graphed should be a solid line.");
next;
}
push(@errors, "The $nth object graphed is incorrect.");
}
return ($score, @errors);
}
}
}
There are a number of options that you can supply to control the appearance and behavior of the JavaScript graph, listed below. These are set as parameters to the with()
method called on the GraphTool
object.
bBox => [-10, 10, 10, -10]
)This is an array of four numbers that represent the bounding box of the graph. The first two numbers in the array are the coordinates of the top left corner of the graph, and the last two numbers are the coordinates of the bottom right corner of the graph.
gridX => 1, gridY => 1
)These are the distances between successive grid lines in the x and y directions, respectively.
ticksDistanceX => 2, ticksDistanceY => 2
)These are the distances between successive major (labeled) ticks on the x and y axes, respectively.
minorTicksX => 1, minorTicksY => 1
)These are the number of minor (unlabeled) ticks between major ticks on the x and y axes, respectively.
xAxisLabel => 'x', yAxisLabel => 'y'
)Labels that will be added to the ends of the horizontal (x) and vertical (y) axes. Note that the values of these options will be used in MathJax online and in LaTeX math mode in print. These can also be set to the empty string '' to remove the labels.
ariaDescription => ''
)This will be added to a hidden div that will be referenced in an aria-describedby attribute of the jsxgraph board.
undef
)This is an advanced option that you usually do not want to use. It is usually constructed by the macro internally using the above options. If defined it should be a single string that is formatted in JavaScript object notation, and will override all of the above options. It will be passed to the JavaScript graphTool
method which will pass it on to the JSX graph board when it is initialized. It may consist of any of the valid attributes documented for JXG.JSXGraph.initBoard
at https://jsxgraph.org/docs/symbols/JXG.JSXGraph.html#.initBoard. For example the following value for JSXGraphOptions
will give the same result for the JavaScript graph as the default values for the options above:
JSXGraphOptions => JSON->new->encode({
boundingBox => [-10, 10, 10, -10],
defaultAxes => {
x => { ticks => { ticksDistance => 2, minorTicks => 1} },
y => { ticks => { ticksDistance => 2, minorTicks => 1} }
},
grid => { gridX => 1, gridY => 1 }
})
snapSizeX => 1, snapSizeY => 1
)These restrict the x coordinate and y coordinate of points that can be graphed to being multiples of the respective parameter. These values must be greater than zero.
showCoordinateHints => 1
)Set this to 0 to disable the display of the coordinates. These are in the lower right corner of the graph for the default 2 dimensional graphing mode, and in the top left corner of the graph for the 1 dimensional mode when numberLine is 1.
coordinateHintsType => 'decimal'
)This changes the way coordinate hints are shown. By default the coordinates are displayed as decimal numbers accurate to five decimal places. If this is set to 'fraction', then those decimals will be converted and displayed as fractions. If this is set to 'mixed', then those decimals will be converted and displayed as mixed numbers. For example, if the snapSizeX is set to 1/3, then what would be displayed as 4.66667 with the default 'decimal' setting, would be instead be displayed as 14/3 with the 'fraction' setting, and '4 2/3' with the 'mixed' setting. Note that these fractions are typeset by MathJax.
Make sure that the snap size is given with decent accuracy. For example, if the snap size to 0.33333, then instead of 1/3 being displayed, 33333/1000000 will be displayed. It is recommended to actually give an actual fraction for the snap size (like 1/3), and let perl and javascript compute that to get the best result.
coordinateHintsTypeX => undef
)This does the same as the coordinateHintsType option, but only for the x-coordinate. If this is undefined then the coordinateHintsType option is used for the x-coordinate.
coordinateHintsTypeY => undef
)This does the same as the coordinateHintsType option, but only for the y-coordinate. If this is undefined then the coordinateHintsType option is used for the y-coordinate.
availableTools => [ "LineTool", "CircleTool", "VerticalParabolaTool", "HorizontalParabolaTool", "FillTool", "SolidDashTool" ]
)This is an array of tools that will be made available for students to use in the graph tool. The order the tools are listed here will also be the order the tools are presented in the graph tool button box. All of the tools that may be included are listed in the default options above, except for the "PointTool", the three point "QuadraticTool", and the four point "CubicTool". Note that the case of the tool names must match what is shown.
staticObjects => []
)This is an array of fixed objects that will be displayed on the graph. These objects will not be able to be moved around. The format for these objects is the same as those that are passed to the GraphTool constructor as the correct answers.
undef
)If the JSXGraphOptions option is set directly, then you will also need to provide a function that will generate the corresponding hard copy graph. Otherwise the hard copy graph will still be generated using the above options, and will not look the same as the JavaScript graph.
cmpOptions => {}
)This is a hash of options that will be passed to the cmp()
method. These options can also be passed as parameters directly to the GraphTool object's cmp()
method.
texSize => 400
)This is the size of the graph that will be output when a hard copy of the problem is generated.
In "static" output forms (TeX, PTX) you may not want to print the graph if it is just taking space. In that case, set this to 0.
numberLine => 0
)If set to 0, then the graph will show both the horizontal and vertical axes. This is the default. If set to 1, then only the horizontal axis will be shown, and the graph can be interpreted as a number line. In this case the graph will also be displayed with a smaller height.
Note that if this option is set to 1, then some of the options listed above have different default values. The options with different default values and their corresponding default values are:
bBox => [ -10, 0.4, 10, -0.4 ],
xAxisLabel => '',
availableTools => [ 'IntervalTool', 'IncludeExcludePointTool' ],
In addition, bBox
may be provided as an array reference with only two entries which will be interpreted as a horizontal range. For example,
bBox => [ -12, 12 ]
will give a graph with horizontal extremes -12
and 12
.
Note that the horizontal extremes of the number line are interpreted as points at infinity. So in the above example, a point graphed at -12 will be interpreted to be a point at -infinity, and a point graphed at 12 will be interpreted to be a point at infinity.
The only graph objects that will work well with this graphing mode are the "point" and "interval" objects, which are created by the "PointTool" and "IntervalTool" respectively. Usually the "IncludeExcludePointTool" will be desired to control when interval end points are included or excluded from an interval. Of course "interval"s and the "IntervalTool" will not work well if this graph mode is not used.
useBracketEnds => 0
)If set to 1, then parentheses and brackets will be used for interval end point delimiters instead of open and closed dots. This option only has effect when numberLine
is 1, and the IntervalTool
is used.
This method may be called for a GraphTool object to output a static version of the graph into the problem. The typical place where this might be desired is in the solution for the problem. For example
BEGIN_PGML_SOLUTION
The correct graph is
[@ $gt->generateAnswerGraph(ariaDescription => 'a better description than the default') @]*
END_PGML_SOLUTION
The following options may be passed to this method.
showCorrect
Whether to show correct answers in the graph. This is 1 by default.
cssClass
A css class that will be added to the containing div. The default value is 'graphtool-solution-container'. Note that this default class is provided in the graphtool.css file. A custom class may also be used, and injected into the header via HEADER_TEXT. It is recommended that this class be prefixed with the graph tool answer name to avoid possible conflict with other problems. This may be obtained with $gt->ANS_NAME
. This class must set the width and height of the div.graphtool-graph contained within, or the div.graphtool-number-line contained within if numberLine is set. Note that this option is only used in HTML output.
ariaDescription
An aria description that will be added to the graph. The default value is 'graph of solution'. Note that this option is only used in HTML output.
objects
Additional objects to display in the graph. The default value is the empty string.
width
and height
The width and height of the answer graph in HTML output. If neither of these are given, then the css class will be used instead. If only one of these is given, then the other will be computed from the given value.
texSize
This is the size of the image that will be output when a hard copy of the problem is generated. The default value is the value of the graph tool object texSize
option which defaults to 400.