Skip to content

Commit b47cabc

Browse files
committed
Switch to a better extension system for the GraphTool.
For JavaScript instead of piecing together the parts of the class definition, use a function that returns the class definition directly. This has the advantage of being able to use `super` directly to access methods of the parent class, and is just much easier to work with. All of the graph objects and graph tools are now in separate files (except the SelectTool and SolidDashTool which are core functionality). For Perl, all graph objects are packages that derive from the GraphTool::GraphObject package instead of the previous method of defining cmp and tikz subroutines in a hash. A mapping from the string identifier to the package is maintained and used to construct the correct perl object for a graph tool object. There is a compatibility layer for objects added the old way.
1 parent eaa4e78 commit b47cabc

File tree

14 files changed

+4838
-5270
lines changed

14 files changed

+4838
-5270
lines changed

htdocs/js/GraphTool/circletool.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/* global graphTool, JXG */
2+
3+
'use strict';
4+
5+
(() => {
6+
if (graphTool && graphTool.circleTool) return;
7+
8+
graphTool.circleTool = {
9+
Circle(gt) {
10+
return class Circle extends gt.GraphObject {
11+
static strId = 'circle';
12+
13+
constructor(center, point, solid) {
14+
super(
15+
gt.board.create('circle', [center, point], {
16+
fixed: true,
17+
highlight: false,
18+
strokeColor: gt.color.curve,
19+
dash: solid ? 0 : 2
20+
})
21+
);
22+
this.definingPts.push(center, point);
23+
this.focusPoint = center;
24+
25+
// Redefine the circle's hasPoint method to return true if the center point has the given
26+
// coordinates, so that a pointer over the center point will give focus to the object with the
27+
// center point activated.
28+
const circleHasPoint = this.baseObj.hasPoint.bind(this.baseObj);
29+
this.baseObj.hasPoint = (x, y) => circleHasPoint(x, y) || center.hasPoint(x, y);
30+
}
31+
32+
stringify() {
33+
return [
34+
this.constructor.strId,
35+
this.baseObj.getAttribute('dash') == 0 ? 'solid' : 'dashed',
36+
...this.definingPts.map(
37+
(point) =>
38+
`(${gt.snapRound(point.X(), gt.snapSizeX)},${gt.snapRound(point.Y(), gt.snapSizeY)})`
39+
)
40+
].join(',');
41+
}
42+
43+
fillCmp(point) {
44+
return gt.sign(
45+
this.baseObj.stdform[3] * (point[1] * point[1] + point[2] * point[2]) +
46+
JXG.Math.innerProduct(point, this.baseObj.stdform)
47+
);
48+
}
49+
50+
static restore(string) {
51+
let pointData = gt.pointRegexp.exec(string);
52+
const points = [];
53+
while (pointData) {
54+
points.push(pointData.slice(1, 3));
55+
pointData = gt.pointRegexp.exec(string);
56+
}
57+
if (points.length < 2) return false;
58+
const center = gt.createPoint(parseFloat(points[0][0]), parseFloat(points[0][1]));
59+
const point = gt.createPoint(parseFloat(points[1][0]), parseFloat(points[1][1]), center);
60+
return new this(center, point, /solid/.test(string));
61+
}
62+
};
63+
},
64+
65+
CircleTool(gt) {
66+
return class CirlceTool extends gt.GenericTool {
67+
object = 'circle';
68+
useStandardActivation = true;
69+
activationHelpText = 'Plot the center of the circle.';
70+
useStandardDeactivation = true;
71+
constructionObjects = ['center'];
72+
73+
constructor(container, iconName, tooltip) {
74+
super(container, iconName ?? 'circle', tooltip ?? 'Circle Tool: Graph a circle.');
75+
this.supportsSolidDash = true;
76+
}
77+
78+
handleKeyEvent(e) {
79+
if (!this.hlObjs.hl_point || !gt.board.containerObj.contains(document.activeElement)) return;
80+
81+
if (e.key === 'Enter' || e.key === ' ') {
82+
e.preventDefault();
83+
e.stopPropagation();
84+
85+
if (this.center) this.phase2(this.hlObjs.hl_point.coords.usrCoords);
86+
else this.phase1(this.hlObjs.hl_point.coords.usrCoords);
87+
}
88+
}
89+
90+
updateHighlights(e) {
91+
this.hlObjs.hl_circle?.setAttribute({ dash: gt.drawSolid ? 0 : 2 });
92+
this.hlObjs.hl_point?.rendNode.focus();
93+
94+
let coords;
95+
if (e instanceof MouseEvent && e.type === 'pointermove') {
96+
coords = gt.getMouseCoords(e);
97+
this.hlObjs.hl_point?.setPosition(JXG.COORDS_BY_USER, [
98+
coords.usrCoords[1],
99+
coords.usrCoords[2]
100+
]);
101+
} else if (e instanceof KeyboardEvent && e.type === 'keydown') {
102+
coords = this.hlObjs.hl_point.coords;
103+
} else if (e instanceof JXG.Coords) {
104+
coords = e;
105+
this.hlObjs.hl_point?.setPosition(JXG.COORDS_BY_USER, [
106+
coords.usrCoords[1],
107+
coords.usrCoords[2]
108+
]);
109+
} else return false;
110+
111+
if (!this.hlObjs.hl_point) {
112+
this.hlObjs.hl_point = gt.board.create('point', [coords.usrCoords[1], coords.usrCoords[2]], {
113+
size: 2,
114+
color: gt.color.underConstruction,
115+
snapToGrid: true,
116+
highlight: false,
117+
snapSizeX: gt.snapSizeX,
118+
snapSizeY: gt.snapSizeY,
119+
withLabel: false
120+
});
121+
this.hlObjs.hl_point.rendNode.focus();
122+
}
123+
124+
// Make sure the highlight point is not moved off the board or on the center.
125+
if (e instanceof Event) gt.adjustDragPosition(e, this.hlObjs.hl_point, this.center);
126+
127+
if (this.center && !this.hlObjs.hl_circle) {
128+
this.hlObjs.hl_circle = gt.board.create('circle', [this.center, this.hlObjs.hl_point], {
129+
fixed: true,
130+
strokeColor: gt.color.underConstruction,
131+
highlight: false,
132+
dash: gt.drawSolid ? 0 : 2
133+
});
134+
}
135+
136+
gt.setTextCoords(this.hlObjs.hl_point.X(), this.hlObjs.hl_point.Y());
137+
gt.board.update();
138+
return true;
139+
}
140+
141+
// In phase1 the user has selected a point. If the point is on the board, then create the center of the
142+
// circle, and set up phase2.
143+
phase1(coords) {
144+
if (!gt.boardHasPoint(coords[1], coords[2])) return;
145+
146+
gt.board.off('up');
147+
148+
this.center = gt.board.create('point', [coords[1], coords[2]], {
149+
size: 2,
150+
withLabel: false,
151+
highlight: false,
152+
snapToGrid: true,
153+
snapSizeX: gt.snapSizeX,
154+
snapSizeY: gt.snapSizeY
155+
});
156+
this.center.setAttribute({ fixed: true });
157+
158+
// Get a new x coordinate that is to the right, unless that is off the board.
159+
// In that case go left instead.
160+
let newX = this.center.X() + gt.snapSizeX;
161+
if (newX > gt.board.getBoundingBox()[2]) newX = this.center.X() - gt.snapSizeX;
162+
163+
this.updateHighlights(new JXG.Coords(JXG.COORDS_BY_USER, [newX, this.center.Y()], gt.board));
164+
165+
this.helpText = 'Plot a point on the circle.';
166+
gt.updateHelp();
167+
168+
gt.board.on('up', (e) => this.phase2(gt.getMouseCoords(e).usrCoords));
169+
170+
gt.board.update();
171+
}
172+
173+
// In phase2 the user has selected a second point.
174+
// If that point is on the board, then finalize the circle.
175+
phase2(coords) {
176+
if (!gt.boardHasPoint(coords[1], coords[2])) return;
177+
178+
// If the current coordinates are the same those of the first point,
179+
// then use the highlight point coordinates instead.
180+
if (
181+
Math.abs(this.center.X() - gt.snapRound(coords[1], gt.snapSizeX)) < JXG.Math.eps &&
182+
Math.abs(this.center.Y() - gt.snapRound(coords[2], gt.snapSizeY)) < JXG.Math.eps
183+
)
184+
coords = this.hlObjs.hl_point.coords.usrCoords;
185+
186+
gt.board.off('up');
187+
188+
const center = this.center;
189+
delete this.center;
190+
191+
center.setAttribute(gt.definingPointAttributes);
192+
center.on('down', () => gt.onPointDown(center));
193+
center.on('up', () => gt.onPointUp(center));
194+
195+
const point = gt.createPoint(coords[1], coords[2], center);
196+
gt.selectedObj = new gt.graphObjectTypes[this.object](center, point, gt.drawSolid);
197+
gt.selectedObj.focusPoint = point;
198+
gt.graphedObjs.push(gt.selectedObj);
199+
200+
this.finish();
201+
}
202+
};
203+
}
204+
};
205+
})();

0 commit comments

Comments
 (0)