Skip to content

Commit d2b477d

Browse files
committed
Add a vector tool to the graph tool.
This was requested by @somiaj in our last developer meeting after I told him that I already had this encoded. I have had it as a custom tool for a long time now. The usage of the new tool is documented in the parserGraphTool.pl macro. The "vector" graph object derives from the "line" graph object, and the "VectorTool" tool derives from the "LineTool" tool. That saves some code redundancy. Note that there will be some minor conflicts with #1191 that will need to be resolved.
1 parent 7f1ca8c commit d2b477d

File tree

4 files changed

+195
-7
lines changed

4 files changed

+195
-7
lines changed

htdocs/js/GraphTool/graphtool.scss

+4
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@
235235
&.gt-sine-wave-tool {
236236
background-image: url('images/SineWaveTool.svg');
237237
}
238+
239+
&.gt-vector-tool {
240+
background-image: url('images/Vector.svg');
241+
}
238242
}
239243
}
240244

htdocs/js/GraphTool/images/Vector.svg

+8
Loading

htdocs/js/GraphTool/vector.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* global graphTool, JXG */
2+
3+
(() => {
4+
if (graphTool && graphTool.vectorTool) return;
5+
6+
graphTool.vectorTool = {
7+
Vector: {
8+
parent: 'line',
9+
10+
postInit(_gt, point1, point2, _solid) {
11+
this.baseObj.setAttribute({ straightFirst: false, straightLast: false });
12+
this.baseObj.setArrow(false, { type: 1, size: 4 });
13+
},
14+
15+
stringify(gt) {
16+
return [
17+
this.baseObj.getAttribute('dash') === 0 ? 'solid' : 'dashed',
18+
...this.definingPts.map(
19+
(point) => `(${gt.snapRound(point.X(), gt.snapSizeX)},${gt.snapRound(point.Y(), gt.snapSizeY)})`
20+
)
21+
].join(',');
22+
},
23+
24+
restore(gt, string) {
25+
let pointData = gt.pointRegexp.exec(string);
26+
const points = [];
27+
while (pointData) {
28+
points.push(pointData.slice(1, 3));
29+
pointData = gt.pointRegexp.exec(string);
30+
}
31+
if (points.length < 2) return false;
32+
const point1 = gt.createPoint(parseFloat(points[0][0]), parseFloat(points[0][1]));
33+
const point2 = gt.createPoint(parseFloat(points[1][0]), parseFloat(points[1][1]), point1);
34+
return new gt.graphObjectTypes.vector(point1, point2, /solid/.test(string));
35+
}
36+
},
37+
38+
VectorTool: {
39+
iconName: 'vector',
40+
tooltip: 'Vector Tool: Graph a vector.',
41+
parent: 'LineTool',
42+
43+
initialize(gt) {
44+
this.phase1 = (coords) => {
45+
gt.toolTypes.LineTool.prototype.phase1.call(this, coords);
46+
this.helpText = 'Plot the terminal point of the vector.';
47+
gt.updateHelp();
48+
};
49+
50+
this.phase2 = (coords) => {
51+
if (!gt.boardHasPoint(coords[1], coords[2])) return;
52+
53+
// If the current coordinates are on top of the first,
54+
// then use the highlight point coordinates instead.
55+
if (
56+
Math.abs(this.point1.X() - gt.snapRound(coords[1], gt.snapSizeX)) < JXG.Math.eps &&
57+
Math.abs(this.point1.Y() - gt.snapRound(coords[2], gt.snapSizeY)) < JXG.Math.eps
58+
)
59+
coords = this.hlObjs.hl_point.coords.usrCoords;
60+
61+
gt.board.off('up');
62+
63+
const point1 = this.point1;
64+
delete this.point1;
65+
66+
point1.setAttribute(gt.definingPointAttributes);
67+
68+
point1.on('down', () => gt.onPointDown(point1));
69+
point1.on('up', () => gt.onPointUp(point1));
70+
71+
const point2 = gt.createPoint(coords[1], coords[2], point1);
72+
gt.selectedObj = new gt.graphObjectTypes.vector(point1, point2, gt.drawSolid);
73+
gt.selectedObj.focusPoint = point2;
74+
gt.graphedObjs.push(gt.selectedObj);
75+
76+
this.finish();
77+
};
78+
},
79+
80+
updateHighlights(gt, e) {
81+
const handled = gt.toolTypes.LineTool.prototype.updateHighlights.call(this, e);
82+
this.hlObjs.hl_line?.setAttribute({
83+
straightFirst: false,
84+
straightLast: false,
85+
lastArrow: { type: 1, size: 6 }
86+
});
87+
return handled;
88+
},
89+
90+
activate(gt) {
91+
this.helpText = 'Plot the initial point and then the terminal point of the vector.';
92+
gt.updateHelp();
93+
}
94+
}
95+
};
96+
})();

macros/graph/parserGraphTool.pl

+87-7
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ =head1 DESCRIPTION
5353
5454
=head1 GRAPH OBJECTS
5555
56-
There are nine types of graph objects that the students can graph. Points, lines, circles,
57-
parabolas, quadratics, cubics, intervals, sine waves, and fills (or shading of a region). The
58-
syntax for each of these objects to pass to the GraphTool constructor is summarized as follows.
59-
Each object must be enclosed in braces. The first element in the braces must be the name of the
60-
object. The following elements in the braces depend on the type of element.
56+
There are several types of graph objects that the students can graph. Points, lines, circles,
57+
parabolas, quadratics, cubics, intervals, sine waves, vectors, and fills (or shading of a
58+
region). The syntax for each of these objects to pass to the GraphTool constructor is summarized
59+
as follows. Each object must be enclosed in braces. The first element in the braces must be the
60+
name of the object. The following elements in the braces depend on the type of element.
6161
6262
For points the name "point" must be followed by the coordinates. For example:
6363
@@ -119,6 +119,12 @@ =head1 GRAPH OBJECTS
119119
120120
represents the function C<f(x) = 5 sin((2 pi / 3)(x - (-4))) + 2>.
121121
122+
For vectors the name "vector" must be followed by the word "solid" or "dashed" to indicate if
123+
the vector is expected to be drawn solid or dashed. That is followed by the initial point and
124+
the terminal point. For example:
125+
126+
"{vector,solid,(0,0),(3,4)}"
127+
122128
The student answers that are returned by the JavaScript will be a list of the list objects
123129
discussed above and will be parsed by WeBWorK and passed to the checker as such. The default
124130
checker is designed to grade the graph based on appearance. This means that if a student graphs
@@ -298,8 +304,8 @@ =head1 OPTIONS
298304
The order the tools are listed here will also be the order the tools are presented in the graph
299305
tool button box. In addition to the tools listed in the default options above, there is a
300306
"PointTool", three point "QuadraticTool", four point "CubicTool", "IntervalTool",
301-
"IncludeExcludePointTool", and "SineWaveTool". Note that the case of the tool names must match
302-
what is shown.
307+
"IncludeExcludePointTool", "SineWaveTool", and "VectorTool". Note that the case of the tool
308+
names must match what is shown.
303309
304310
=item staticObjects (Default: C<< staticObjects => [] >>)
305311
@@ -439,6 +445,7 @@ sub _parserGraphTool_init {
439445
ADD_JS_FILE('js/GraphTool/cubictool.js', 0, { defer => undef });
440446
ADD_JS_FILE('js/GraphTool/intervaltools.js', 0, { defer => undef });
441447
ADD_JS_FILE('js/GraphTool/sinewavetool.js', 0, { defer => undef });
448+
ADD_JS_FILE('js/GraphTool/vector.js', 0, { defer => undef });
442449

443450
return;
444451
}
@@ -1149,6 +1156,77 @@ sub addTools {
11491156
}
11501157
);
11511158
}
1159+
},
1160+
vector => {
1161+
js => 'graphTool.vectorTool.Vector',
1162+
tikz => {
1163+
code => sub {
1164+
my $gt = shift;
1165+
1166+
my ($p1x, $p1y) = @{ $_->{data}[2]{data} };
1167+
my ($p2x, $p2y) = @{ $_->{data}[3]{data} };
1168+
1169+
if ($p1x == $p2x) {
1170+
# Vertical vector
1171+
return (
1172+
"\\draw[thick, blue, line width = 2.5pt, $_->{data}[1], *->] ($p1x, $p1y) -- ($p2x, $p2y);\n",
1173+
[
1174+
"($p1x,$gt->{bBox}[3]) -- ($p1x,$gt->{bBox}[1])"
1175+
. "-- ($gt->{bBox}[2],$gt->{bBox}[1]) -- ($gt->{bBox}[2],$gt->{bBox}[3]) -- cycle",
1176+
sub { return $_[0] - $p1x; }
1177+
]
1178+
);
1179+
} else {
1180+
# Non-vertical vector
1181+
my $m = ($p2y - $p1y) / ($p2x - $p1x);
1182+
my $y = sub { return $m * ($_[0] - $p1x) + $p1y; };
1183+
return (
1184+
"\\draw[thick, blue, line width = 2.5pt, $_->{data}[1], *->] ($p1x, $p1y) -- ($p2x, $p2y);\n",
1185+
[
1186+
"($gt->{bBox}[0],"
1187+
. &$y($gt->{bBox}[0]) . ') -- '
1188+
. "($gt->{bBox}[2],"
1189+
. &$y($gt->{bBox}[2]) . ')'
1190+
. "-- ($gt->{bBox}[2],$gt->{bBox}[1]) -- ($gt->{bBox}[0],$gt->{bBox}[1]) -- cycle",
1191+
sub { return $_[1] - &$y($_[0]); }
1192+
]
1193+
);
1194+
}
1195+
}
1196+
},
1197+
cmp => sub {
1198+
my ($vector) = @_;
1199+
1200+
my $solid_dashed = $vector->{data}[1];
1201+
my $initial_point = $vector->{data}[2];
1202+
my $terminal_point = $vector->{data}[3];
1203+
1204+
# These are the coefficients a, b, and c in ax + by + c = 0.
1205+
my @stdform = (
1206+
$vector->{data}[2]{data}[1] - $vector->{data}[3]{data}[1],
1207+
$vector->{data}[3]{data}[0] - $vector->{data}[2]{data}[0],
1208+
$vector->{data}[2]{data}[0] * $vector->{data}[3]{data}[1] -
1209+
$vector->{data}[3]{data}[0] * $vector->{data}[2]{data}[1]
1210+
);
1211+
1212+
my $vectorPointCmp = sub {
1213+
my $point = shift;
1214+
my ($x, $y) = $point->value;
1215+
return $stdform[0] * $x + $stdform[1] * $y + $stdform[2] <=> 0;
1216+
};
1217+
1218+
return (
1219+
$vectorPointCmp,
1220+
sub {
1221+
my ($other, $fuzzy) = @_;
1222+
return
1223+
$other->{data}[0] eq 'vector'
1224+
&& ($fuzzy || $other->{data}[1] eq $solid_dashed)
1225+
&& $initial_point == $other->{data}[2]
1226+
&& $terminal_point == $other->{data}[3];
1227+
}
1228+
);
1229+
}
11521230
}
11531231
);
11541232

@@ -1163,6 +1241,8 @@ sub addTools {
11631241
IntervalTool => 'graphTool.intervalTool.IntervalTool',
11641242
# A sine wave tool.
11651243
SineWaveTool => 'graphTool.sineWaveTool.SineWaveTool',
1244+
# A vector tool.
1245+
VectorTool => 'graphTool.vectorTool.VectorTool',
11661246
# Include/Exclude point tool.
11671247
IncludeExcludePointTool => 'graphTool.includeExcludePointTool.IncludeExcludePointTool',
11681248
);

0 commit comments

Comments
 (0)