Skip to content

Commit bf68f79

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 c50623b commit bf68f79

File tree

4 files changed

+196
-8
lines changed

4 files changed

+196
-8
lines changed

htdocs/js/GraphTool/graphtool.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@
243243
&.gt-quadrilateral-tool {
244244
background-image: url('images/Quadrilateral.svg');
245245
}
246+
247+
&.gt-vector-tool {
248+
background-image: url('images/Vector.svg');
249+
}
246250
}
247251
}
248252

Lines changed: 8 additions & 0 deletions
Loading

htdocs/js/GraphTool/vector.js

Lines changed: 96 additions & 0 deletions
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

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ =head1 DESCRIPTION
5353
5454
=head1 GRAPH OBJECTS
5555
56-
There are eleven types of graph objects that the students can graph. Points, lines, circles,
57-
parabolas, quadratics, cubics, intervals, sine waves, triangles, quadrilaterals and fills (or
58-
shading of a region). The syntax for each of these objects to pass to the GraphTool constructor
59-
is summarized as follows. Each object must be enclosed in braces. The first element in the
60-
braces must be the name of the object. The following elements in the braces depend on the type
61-
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, triangles, quadrilaterals, vectors, and
58+
fills (or shading of a region). The syntax for each of these objects to pass to the GraphTool
59+
constructor is summarized as follows. Each object must be enclosed in braces. The first element
60+
in the braces must be the name of the object. The following elements in the braces depend on the
61+
type of element.
6262
6363
For points the name "point" must be followed by the coordinates. For example:
6464
@@ -132,6 +132,12 @@ =head1 GRAPH OBJECTS
132132
133133
"{quadrilateral,solid,(0,0),(4,3),(2,3),(4,-3)}"
134134
135+
For vectors the name "vector" must be followed by the word "solid" or "dashed" to indicate if
136+
the vector is expected to be drawn solid or dashed. That is followed by the initial point and
137+
the terminal point. For example:
138+
139+
"{vector,solid,(0,0),(3,4)}"
140+
135141
The student answers that are returned by the JavaScript will be a list of the list objects
136142
discussed above and will be parsed by WeBWorK and passed to the checker as such. The default
137143
checker is designed to grade the graph based on appearance. This means that if a student graphs
@@ -311,8 +317,8 @@ =head1 OPTIONS
311317
The order the tools are listed here will also be the order the tools are presented in the graph
312318
tool button box. In addition to the tools listed in the default options above, there is a
313319
"PointTool", three point "QuadraticTool", four point "CubicTool", "IntervalTool",
314-
"IncludeExcludePointTool", "SineWaveTool", "TriangleTool", and "QuadrilateralTool". Note that
315-
the case of the tool names must match what is shown.
320+
"IncludeExcludePointTool", "SineWaveTool", "TriangleTool", "QuadrilateralTool", and
321+
"VectorTool". Note that the case of the tool names must match what is shown.
316322
317323
=item staticObjects (Default: C<< staticObjects => [] >>)
318324
@@ -454,6 +460,7 @@ sub _parserGraphTool_init {
454460
ADD_JS_FILE('js/GraphTool/sinewavetool.js', 0, { defer => undef });
455461
ADD_JS_FILE('js/GraphTool/triangle.js', 0, { defer => undef });
456462
ADD_JS_FILE('js/GraphTool/quadrilateral.js', 0, { defer => undef });
463+
ADD_JS_FILE('js/GraphTool/vector.js', 0, { defer => undef });
457464

458465
return;
459466
}
@@ -1349,6 +1356,77 @@ sub addTools {
13491356
}
13501357
);
13511358
}
1359+
},
1360+
vector => {
1361+
js => 'graphTool.vectorTool.Vector',
1362+
tikz => {
1363+
code => sub {
1364+
my $gt = shift;
1365+
1366+
my ($p1x, $p1y) = @{ $_->{data}[2]{data} };
1367+
my ($p2x, $p2y) = @{ $_->{data}[3]{data} };
1368+
1369+
if ($p1x == $p2x) {
1370+
# Vertical vector
1371+
return (
1372+
"\\draw[thick, blue, line width = 2.5pt, $_->{data}[1], *->] ($p1x, $p1y) -- ($p2x, $p2y);\n",
1373+
[
1374+
"($p1x,$gt->{bBox}[3]) -- ($p1x,$gt->{bBox}[1])"
1375+
. "-- ($gt->{bBox}[2],$gt->{bBox}[1]) -- ($gt->{bBox}[2],$gt->{bBox}[3]) -- cycle",
1376+
sub { return $_[0] - $p1x; }
1377+
]
1378+
);
1379+
} else {
1380+
# Non-vertical vector
1381+
my $m = ($p2y - $p1y) / ($p2x - $p1x);
1382+
my $y = sub { return $m * ($_[0] - $p1x) + $p1y; };
1383+
return (
1384+
"\\draw[thick, blue, line width = 2.5pt, $_->{data}[1], *->] ($p1x, $p1y) -- ($p2x, $p2y);\n",
1385+
[
1386+
"($gt->{bBox}[0],"
1387+
. &$y($gt->{bBox}[0]) . ') -- '
1388+
. "($gt->{bBox}[2],"
1389+
. &$y($gt->{bBox}[2]) . ')'
1390+
. "-- ($gt->{bBox}[2],$gt->{bBox}[1]) -- ($gt->{bBox}[0],$gt->{bBox}[1]) -- cycle",
1391+
sub { return $_[1] - &$y($_[0]); }
1392+
]
1393+
);
1394+
}
1395+
}
1396+
},
1397+
cmp => sub {
1398+
my ($vector) = @_;
1399+
1400+
my $solid_dashed = $vector->{data}[1];
1401+
my $initial_point = $vector->{data}[2];
1402+
my $terminal_point = $vector->{data}[3];
1403+
1404+
# These are the coefficients a, b, and c in ax + by + c = 0.
1405+
my @stdform = (
1406+
$vector->{data}[2]{data}[1] - $vector->{data}[3]{data}[1],
1407+
$vector->{data}[3]{data}[0] - $vector->{data}[2]{data}[0],
1408+
$vector->{data}[2]{data}[0] * $vector->{data}[3]{data}[1] -
1409+
$vector->{data}[3]{data}[0] * $vector->{data}[2]{data}[1]
1410+
);
1411+
1412+
my $vectorPointCmp = sub {
1413+
my $point = shift;
1414+
my ($x, $y) = $point->value;
1415+
return $stdform[0] * $x + $stdform[1] * $y + $stdform[2] <=> 0;
1416+
};
1417+
1418+
return (
1419+
$vectorPointCmp,
1420+
sub {
1421+
my ($other, $fuzzy) = @_;
1422+
return
1423+
$other->{data}[0] eq 'vector'
1424+
&& ($fuzzy || $other->{data}[1] eq $solid_dashed)
1425+
&& $initial_point == $other->{data}[2]
1426+
&& $terminal_point == $other->{data}[3];
1427+
}
1428+
);
1429+
}
13521430
}
13531431
);
13541432

@@ -1367,6 +1445,8 @@ sub addTools {
13671445
TriangleTool => 'graphTool.triangleTool.TriangleTool',
13681446
# A quadrilateral tool.
13691447
QuadrilateralTool => 'graphTool.quadrilateralTool.QuadrilateralTool',
1448+
# A vector tool.
1449+
VectorTool => 'graphTool.vectorTool.VectorTool',
13701450
# Include/Exclude point tool.
13711451
IncludeExcludePointTool => 'graphTool.includeExcludePointTool.IncludeExcludePointTool',
13721452
);

0 commit comments

Comments
 (0)