@teachinglab/omd 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +138 -0
  2. package/canvas/core/canvasConfig.js +203 -0
  3. package/canvas/core/omdCanvas.js +475 -0
  4. package/canvas/drawing/segment.js +168 -0
  5. package/canvas/drawing/stroke.js +386 -0
  6. package/canvas/events/eventManager.js +435 -0
  7. package/canvas/events/pointerEventHandler.js +263 -0
  8. package/canvas/features/focusFrameManager.js +287 -0
  9. package/canvas/index.js +49 -0
  10. package/canvas/tools/eraserTool.js +322 -0
  11. package/canvas/tools/pencilTool.js +319 -0
  12. package/canvas/tools/selectTool.js +457 -0
  13. package/canvas/tools/tool.js +223 -0
  14. package/canvas/tools/toolManager.js +394 -0
  15. package/canvas/ui/cursor.js +438 -0
  16. package/canvas/ui/toolbar.js +304 -0
  17. package/canvas/utils/boundingBox.js +378 -0
  18. package/canvas/utils/mathUtils.js +259 -0
  19. package/docs/api/configuration-options.md +104 -0
  20. package/docs/api/eventManager.md +68 -0
  21. package/docs/api/focusFrameManager.md +150 -0
  22. package/docs/api/index.md +91 -0
  23. package/docs/api/main.md +58 -0
  24. package/docs/api/omdBinaryExpressionNode.md +227 -0
  25. package/docs/api/omdCanvas.md +142 -0
  26. package/docs/api/omdConfigManager.md +192 -0
  27. package/docs/api/omdConstantNode.md +117 -0
  28. package/docs/api/omdDisplay.md +121 -0
  29. package/docs/api/omdEquationNode.md +161 -0
  30. package/docs/api/omdEquationSequenceNode.md +301 -0
  31. package/docs/api/omdEquationStack.md +139 -0
  32. package/docs/api/omdFunctionNode.md +141 -0
  33. package/docs/api/omdGroupNode.md +182 -0
  34. package/docs/api/omdHelpers.md +96 -0
  35. package/docs/api/omdLeafNode.md +163 -0
  36. package/docs/api/omdNode.md +101 -0
  37. package/docs/api/omdOperationDisplayNode.md +139 -0
  38. package/docs/api/omdOperatorNode.md +127 -0
  39. package/docs/api/omdParenthesisNode.md +122 -0
  40. package/docs/api/omdPopup.md +117 -0
  41. package/docs/api/omdPowerNode.md +127 -0
  42. package/docs/api/omdRationalNode.md +128 -0
  43. package/docs/api/omdSequenceNode.md +128 -0
  44. package/docs/api/omdSimplification.md +110 -0
  45. package/docs/api/omdSqrtNode.md +79 -0
  46. package/docs/api/omdStepVisualizer.md +115 -0
  47. package/docs/api/omdStepVisualizerHighlighting.md +61 -0
  48. package/docs/api/omdStepVisualizerInteractiveSteps.md +129 -0
  49. package/docs/api/omdStepVisualizerLayout.md +60 -0
  50. package/docs/api/omdStepVisualizerNodeUtils.md +140 -0
  51. package/docs/api/omdStepVisualizerTextBoxes.md +68 -0
  52. package/docs/api/omdToolbar.md +102 -0
  53. package/docs/api/omdTranscriptionService.md +76 -0
  54. package/docs/api/omdTreeDiff.md +134 -0
  55. package/docs/api/omdUnaryExpressionNode.md +174 -0
  56. package/docs/api/omdUtilities.md +70 -0
  57. package/docs/api/omdVariableNode.md +148 -0
  58. package/docs/api/selectTool.md +74 -0
  59. package/docs/api/simplificationEngine.md +98 -0
  60. package/docs/api/simplificationRules.md +77 -0
  61. package/docs/api/simplificationUtils.md +64 -0
  62. package/docs/api/transcribe.md +43 -0
  63. package/docs/api-reference.md +85 -0
  64. package/docs/index.html +454 -0
  65. package/docs/user-guide.md +9 -0
  66. package/index.js +67 -0
  67. package/omd/config/omdConfigManager.js +267 -0
  68. package/omd/core/index.js +150 -0
  69. package/omd/core/omdEquationStack.js +347 -0
  70. package/omd/core/omdUtilities.js +115 -0
  71. package/omd/display/omdDisplay.js +443 -0
  72. package/omd/display/omdToolbar.js +502 -0
  73. package/omd/nodes/omdBinaryExpressionNode.js +460 -0
  74. package/omd/nodes/omdConstantNode.js +142 -0
  75. package/omd/nodes/omdEquationNode.js +1223 -0
  76. package/omd/nodes/omdEquationSequenceNode.js +1273 -0
  77. package/omd/nodes/omdFunctionNode.js +352 -0
  78. package/omd/nodes/omdGroupNode.js +68 -0
  79. package/omd/nodes/omdLeafNode.js +77 -0
  80. package/omd/nodes/omdNode.js +557 -0
  81. package/omd/nodes/omdOperationDisplayNode.js +322 -0
  82. package/omd/nodes/omdOperatorNode.js +109 -0
  83. package/omd/nodes/omdParenthesisNode.js +293 -0
  84. package/omd/nodes/omdPowerNode.js +236 -0
  85. package/omd/nodes/omdRationalNode.js +295 -0
  86. package/omd/nodes/omdSqrtNode.js +308 -0
  87. package/omd/nodes/omdUnaryExpressionNode.js +178 -0
  88. package/omd/nodes/omdVariableNode.js +123 -0
  89. package/omd/simplification/omdSimplification.js +171 -0
  90. package/omd/simplification/omdSimplificationEngine.js +886 -0
  91. package/omd/simplification/package.json +6 -0
  92. package/omd/simplification/rules/binaryRules.js +1037 -0
  93. package/omd/simplification/rules/functionRules.js +111 -0
  94. package/omd/simplification/rules/index.js +48 -0
  95. package/omd/simplification/rules/parenthesisRules.js +19 -0
  96. package/omd/simplification/rules/powerRules.js +143 -0
  97. package/omd/simplification/rules/rationalRules.js +475 -0
  98. package/omd/simplification/rules/sqrtRules.js +48 -0
  99. package/omd/simplification/rules/unaryRules.js +37 -0
  100. package/omd/simplification/simplificationRules.js +32 -0
  101. package/omd/simplification/simplificationUtils.js +1056 -0
  102. package/omd/step-visualizer/omdStepVisualizer.js +597 -0
  103. package/omd/step-visualizer/omdStepVisualizerHighlighting.js +206 -0
  104. package/omd/step-visualizer/omdStepVisualizerLayout.js +245 -0
  105. package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +163 -0
  106. package/omd/utils/omdNodeOverlay.js +638 -0
  107. package/omd/utils/omdPopup.js +1084 -0
  108. package/omd/utils/omdStepVisualizerInteractiveSteps.js +491 -0
  109. package/omd/utils/omdStepVisualizerNodeUtils.js +268 -0
  110. package/omd/utils/omdTranscriptionService.js +125 -0
  111. package/omd/utils/omdTreeDiff.js +734 -0
  112. package/package.json +46 -0
  113. package/src/index.js +62 -0
  114. package/src/json-schemas.md +109 -0
  115. package/src/omd-json-samples.js +115 -0
  116. package/src/omd.js +109 -0
  117. package/src/omdApp.js +391 -0
  118. package/src/omdAppCanvas.js +336 -0
  119. package/src/omdBalanceHanger.js +172 -0
  120. package/src/omdColor.js +13 -0
  121. package/src/omdCoordinatePlane.js +467 -0
  122. package/src/omdEquation.js +125 -0
  123. package/src/omdExpression.js +104 -0
  124. package/src/omdFunction.js +113 -0
  125. package/src/omdMetaExpression.js +287 -0
  126. package/src/omdNaturalExpression.js +564 -0
  127. package/src/omdNode.js +384 -0
  128. package/src/omdNumber.js +53 -0
  129. package/src/omdNumberLine.js +107 -0
  130. package/src/omdNumberTile.js +119 -0
  131. package/src/omdOperator.js +73 -0
  132. package/src/omdPowerExpression.js +92 -0
  133. package/src/omdProblem.js +55 -0
  134. package/src/omdRatioChart.js +232 -0
  135. package/src/omdRationalExpression.js +115 -0
  136. package/src/omdSampleData.js +215 -0
  137. package/src/omdShapes.js +476 -0
  138. package/src/omdSpinner.js +148 -0
  139. package/src/omdString.js +39 -0
  140. package/src/omdTable.js +369 -0
  141. package/src/omdTapeDiagram.js +245 -0
  142. package/src/omdTerm.js +92 -0
  143. package/src/omdTileEquation.js +349 -0
  144. package/src/omdVariable.js +51 -0
@@ -0,0 +1,467 @@
1
+ import { omdColor } from "./omdColor.js";
2
+ import { jsvgGroup, jsvgRect, jsvgClipMask, jsvgLine, jsvgEllipse, jsvgTextBox, jsvgPath } from "@teachinglab/jsvg";
3
+ import {
4
+ omdRightTriangle,
5
+ omdIsoscelesTriangle,
6
+ omdRectangle,
7
+ omdEllipse,
8
+ omdCircle,
9
+ omdRegularPolygon
10
+ } from "./omdShapes.js";
11
+
12
+ export class omdCoordinatePlane extends jsvgGroup {
13
+ constructor() {
14
+ super();
15
+ this.graphEquations = [];
16
+ this.lineSegments = [];
17
+ this.dotValues = [];
18
+ this.shapeSet = [];
19
+
20
+ this.xMin = -5;
21
+ this.xMax = 5;
22
+ this.yMin = -5;
23
+ this.yMax = 5;
24
+
25
+ this.xLabel = "";
26
+ this.yLabel = "";
27
+ this.axisLabelOffsetPx = 20; //offset from axes
28
+
29
+ this.size = "medium";
30
+
31
+ this.tickInterval = 1; //interval between ticks
32
+ this.forceAllTickLabels = true; // show all tick labels despite potential overlap
33
+ this.tickLabelOffsetPx = 5; //offset from axes
34
+ this.showTickLabels = true; // show numeric tick labels
35
+
36
+ this.calculatePadding();
37
+ this.updateLayout();
38
+ }
39
+
40
+
41
+
42
+ loadFromJSON(data) {
43
+ // Assume valid JSON shape
44
+ this.graphEquations = data.graphEquations || [];
45
+ this.lineSegments = data.lineSegments || [];
46
+ this.dotValues = data.dotValues || [];
47
+ this.shapeSet = data.shapeSet || [];
48
+
49
+ this.xMin = (data.xMin !== undefined) ? data.xMin : this.xMin;
50
+ this.xMax = (data.xMax !== undefined) ? data.xMax : this.xMax;
51
+ this.yMin = (data.yMin !== undefined) ? data.yMin : this.yMin;
52
+ this.yMax = (data.yMax !== undefined) ? data.yMax : this.yMax;
53
+
54
+ this.xLabel = (data.xLabel !== undefined) ? data.xLabel : this.xLabel;
55
+ this.yLabel = (data.yLabel !== undefined) ? data.yLabel : this.yLabel;
56
+ this.axisLabelOffsetPx = (data.axisLabelOffsetPx !== undefined) ? data.axisLabelOffsetPx : this.axisLabelOffsetPx;
57
+
58
+ this.size = (data.size !== undefined) ? data.size : this.size;
59
+
60
+ this.tickInterval = (data.tickInterval !== undefined) ? data.tickInterval : this.tickInterval;
61
+ this.tickLabelOffsetPx = (data.tickLabelOffsetPx !== undefined) ? data.tickLabelOffsetPx : this.tickLabelOffsetPx;
62
+ this.forceAllTickLabels = (data.forceAllTickLabels !== undefined) ? data.forceAllTickLabels : this.forceAllTickLabels;
63
+ this.showTickLabels = (data.showTickLabels !== undefined) ? data.showTickLabels : this.showTickLabels;
64
+
65
+ this.calculatePadding();
66
+ this.updateLayout();
67
+ }
68
+
69
+
70
+
71
+ updateLayout() {
72
+ this.removeAllChildren();
73
+ this.calculateGraphDimensions();
74
+
75
+ const outerWidth = this.width;
76
+ const outerHeight = this.height;
77
+
78
+ const clipMask = new jsvgClipMask(outerWidth, outerHeight, 15);
79
+ this.addChild(clipMask);
80
+
81
+ const whiteRect = new jsvgRect();
82
+ whiteRect.setWidthAndHeight(outerWidth, outerHeight);
83
+ whiteRect.setFillColor(omdColor.lightGray);
84
+ whiteRect.setStrokeWidth(0);
85
+
86
+ if (whiteRect.setCornerRadius) {
87
+ whiteRect.setCornerRadius(15);
88
+ }
89
+
90
+ clipMask.addChild(whiteRect);
91
+
92
+ const contentGroup = new jsvgGroup();
93
+ contentGroup.setPosition(0, 0);
94
+ clipMask.addChild(contentGroup);
95
+
96
+ this.setWidthAndHeight(outerWidth, outerHeight);
97
+ this.svgObject.setAttribute("viewBox", `0 0 ${outerWidth} ${outerHeight}`);
98
+
99
+ const gridHolder = new jsvgGroup();
100
+ contentGroup.addChild(gridHolder);
101
+ this.makeGrid(gridHolder);
102
+
103
+ const graphClipMask = new jsvgClipMask(this.graphWidth, this.graphHeight, 0);
104
+ graphClipMask.setPosition(this.paddingLeft, this.paddingTop);
105
+ contentGroup.addChild(graphClipMask);
106
+
107
+ // Traditional explicit calls instead of dynamic invocation
108
+ const functionsHolder = new jsvgGroup();
109
+ graphClipMask.addChild(functionsHolder);
110
+ this.graphMultipleFunctions(functionsHolder);
111
+
112
+ const segmentsHolder = new jsvgGroup();
113
+ graphClipMask.addChild(segmentsHolder);
114
+ this.drawLineSegments(segmentsHolder);
115
+
116
+ const dotsHolder = new jsvgGroup();
117
+ graphClipMask.addChild(dotsHolder);
118
+ this.plotDots(dotsHolder);
119
+
120
+ const shapesHolder = new jsvgGroup();
121
+ graphClipMask.addChild(shapesHolder);
122
+ this.addShapes(shapesHolder);
123
+ }
124
+
125
+ createAxisGrid(gridHolder, isXAxis) {
126
+ const interval = this.tickInterval || 1;
127
+ const min = isXAxis ? this.xMin : this.yMin;
128
+ const max = isXAxis ? this.xMax : this.yMax;
129
+
130
+ const firstTick = Math.ceil(min);
131
+ const lastTick = Math.floor(max);
132
+ const minLabelSpacing = 10;
133
+
134
+ let lastLabelPos = -Infinity;
135
+ let zeroLineDrawn = false;
136
+
137
+ for (let value = firstTick; value <= lastTick; value += interval) {
138
+ const pos = this.computeAxisPos(isXAxis, value);
139
+
140
+ const line = new jsvgLine();
141
+ this.setAxisLineEndpoints(isXAxis, line, pos);
142
+ line.setStrokeColor("black");
143
+ line.setOpacity(value === 0 ? 1.0 : 0.15);
144
+ gridHolder.addChild(line);
145
+
146
+ if (value === 0) {
147
+ zeroLineDrawn = true;
148
+ }
149
+
150
+ const isEdgeTick = value === firstTick || value === lastTick;
151
+ const shouldShowLabel = this.showTickLabels && (
152
+ this.forceAllTickLabels ||
153
+ isEdgeTick ||
154
+ Math.abs(pos - lastLabelPos) >= minLabelSpacing
155
+ );
156
+
157
+ if (shouldShowLabel) {
158
+ this.addTickLabel(gridHolder, isXAxis, pos, value);
159
+ lastLabelPos = pos;
160
+ }
161
+ }
162
+
163
+ if (0 >= min && 0 <= max && !zeroLineDrawn) {
164
+ const zeroPos = this.computeAxisPos(isXAxis, 0);
165
+ const zeroLine = new jsvgLine();
166
+ this.setAxisLineEndpoints(isXAxis, zeroLine, zeroPos);
167
+ zeroLine.setStrokeColor("black");
168
+ zeroLine.setOpacity(1.0);
169
+ gridHolder.addChild(zeroLine);
170
+ }
171
+ }
172
+
173
+ createAxisLabels(gridHolder) {
174
+ if (this.xLabel) {
175
+ const w = Math.max(60, this.xLabel.length * 10);
176
+ const h = 20;
177
+ const xAxisLabel = new jsvgTextBox();
178
+
179
+ xAxisLabel.setWidthAndHeight(w, h);
180
+ xAxisLabel.setText(this.xLabel);
181
+ xAxisLabel.setFontSize(14);
182
+ xAxisLabel.setFontColor("black");
183
+ xAxisLabel.setAlignment("center");
184
+ xAxisLabel.setVerticalCentering();
185
+ xAxisLabel.setFontWeight(600);
186
+
187
+ const idealX = this.paddingLeft + this.graphWidth / 2 - w / 2;
188
+ const lx = Math.max(5, Math.min(idealX, this.width - w - 5));
189
+
190
+ xAxisLabel.setPosition(lx, this.height - this.paddingBottom + this.axisLabelOffsetPx);
191
+ gridHolder.addChild(xAxisLabel);
192
+ }
193
+ if (this.yLabel) {
194
+ const fontSize = 14;
195
+ const finalX = Math.max(15, this.paddingLeft - (this.axisLabelOffsetPx + 10));
196
+ const finalY = this.paddingTop + this.graphHeight / 2;
197
+ const labelGroup = new jsvgGroup();
198
+ const yAxisLabel = new jsvgTextLine();
199
+
200
+ yAxisLabel.setText(this.yLabel);
201
+ yAxisLabel.setFontSize(fontSize);
202
+ yAxisLabel.setFontColor("black");
203
+ yAxisLabel.setPosition(0, 0);
204
+ yAxisLabel.svgObject.setAttribute("text-anchor", "middle");
205
+ yAxisLabel.svgObject.setAttribute("dominant-baseline", "middle");
206
+ yAxisLabel.setFontWeight(600);
207
+
208
+ labelGroup.addChild(yAxisLabel);
209
+ labelGroup.svgObject.setAttribute("transform", `translate(${finalX}, ${finalY}) rotate(-90)`);
210
+
211
+ gridHolder.addChild(labelGroup);
212
+ }
213
+ }
214
+
215
+ makeGrid(gridHolder) {
216
+ this.createAxisGrid(gridHolder, true);
217
+ this.createAxisGrid(gridHolder, false);
218
+ this.createAxisLabels(gridHolder);
219
+ }
220
+
221
+ // ===== Helper functions =====
222
+
223
+ calculatePadding() {
224
+ this.paddingLeft = this.yLabel ? 50 : 30;
225
+ this.paddingBottom = this.xLabel ? 50 : 30;
226
+ this.paddingTop = 25;
227
+ this.paddingRight = 25;
228
+ }
229
+
230
+ computeAxisPos(isXAxis, value) {
231
+ return isXAxis
232
+ ? this.paddingLeft + (value - this.xMin) * this.xSpacer
233
+ : this.height - this.paddingBottom - (value - this.yMin) * this.ySpacer;
234
+ }
235
+
236
+ setAxisLineEndpoints(isXAxis, line, pos) {
237
+ if (isXAxis) {
238
+ line.setEndpoints(pos, this.paddingTop, pos, this.height - this.paddingBottom);
239
+ } else {
240
+ line.setEndpoints(this.paddingLeft, pos, this.width - this.paddingRight, pos);
241
+ }
242
+ }
243
+
244
+ addTickLabel(gridHolder, isXAxis, pos, value) {
245
+ const label = this.createNumericLabel(value);
246
+ if (isXAxis) {
247
+ const lx = Math.max(10, Math.min(pos - 10, this.width - 30));
248
+ label.setPosition(lx, this.height - this.paddingBottom + this.tickLabelOffsetPx);
249
+ } else {
250
+ const lx = Math.max(5, this.paddingLeft - (this.tickLabelOffsetPx + 20));
251
+ const ly = Math.max(7.5, Math.min(pos - 7.5, this.height - 15));
252
+ label.setPosition(lx, ly);
253
+ }
254
+ gridHolder.addChild(label);
255
+ }
256
+
257
+ toGraphPixelX(x) {
258
+ return (x - this.xMin) * this.xSpacer;
259
+ }
260
+ toGraphPixelY(y) {
261
+ return this.graphHeight - (y - this.yMin) * this.ySpacer;
262
+ }
263
+
264
+ createNumericLabel(text) {
265
+ const label = new jsvgTextBox();
266
+ label.setWidthAndHeight(20, 15);
267
+ label.setText(String(text));
268
+ label.setFontSize(9);
269
+ label.setFontColor(this.getAllowedColor("black"));
270
+ label.setAlignment("center");
271
+ label.setVerticalCentering();
272
+ return label;
273
+ }
274
+
275
+ getAllowedColor(inputColor) {
276
+ if (!inputColor) return "black";
277
+ const k = String(inputColor).trim();
278
+ return omdColor[k.toLowerCase()] || k || "black"; //first try omdColor, then try string, then black
279
+ }
280
+
281
+ assignFromData(data, mappings) {
282
+ for (const [key, type] of Object.entries(mappings)) {
283
+ if (typeof data[key] === type) this[key] = data[key];
284
+ }
285
+ }
286
+
287
+ calculateGraphDimensions() {
288
+ let baseSize = 200;
289
+ let dotSize = 8;
290
+
291
+ if (this.size === "large") {
292
+ baseSize = 300;
293
+ dotSize = 12;
294
+ } else if (this.size === "small") {
295
+ baseSize = 100;
296
+ dotSize = 6;
297
+ }
298
+
299
+ this.dotSize = dotSize;
300
+ this.xSpan = this.xMax - this.xMin;
301
+ this.ySpan = this.yMax - this.yMin;
302
+
303
+ const unit = Math.min(baseSize / this.xSpan, baseSize / this.ySpan);
304
+
305
+ this.graphWidth = unit * this.xSpan;
306
+ this.graphHeight = unit * this.ySpan;
307
+
308
+ this.width = this.graphWidth + this.paddingLeft + this.paddingRight;
309
+ this.height = this.graphHeight + this.paddingTop + this.paddingBottom;
310
+
311
+ this.xSpacer = unit;
312
+ this.ySpacer = unit;
313
+
314
+ this.originX = this.paddingLeft - this.xMin * this.xSpacer;
315
+ this.originY = this.height - this.paddingBottom + this.yMin * this.ySpacer;
316
+ }
317
+
318
+ graphMultipleFunctions(holder) {
319
+ for (const functionConfig of this.graphEquations) {
320
+ const path = new jsvgPath();
321
+ path.setStrokeColor(this.getAllowedColor(functionConfig.color));
322
+ path.setStrokeWidth(functionConfig.strokeWidth);
323
+ this.graphFunctionWithDomain(path, functionConfig.equation, functionConfig.domain);
324
+ holder.addChild(path);
325
+
326
+ if (functionConfig.label) {
327
+ this.addFunctionLabel(holder, functionConfig);
328
+ }
329
+ }
330
+ }
331
+
332
+ graphFunctionWithDomain(pathObject, functionString, domain) {
333
+ pathObject.clearPoints();
334
+
335
+ let expression = functionString;
336
+ if (expression.toLowerCase().startsWith("y=")) {
337
+ expression = expression.substring(2).trim();
338
+ }
339
+
340
+ const compiledExpression = math.compile(expression);
341
+
342
+ const leftLimit = domain.min;
343
+ const rightLimit = domain.max;
344
+
345
+ const step = Math.abs(rightLimit - leftLimit) / 1000;
346
+ for (let x = leftLimit; x <= rightLimit; x += step) {
347
+ const y = compiledExpression.evaluate({ x });
348
+ pathObject.addPoint(this.toGraphPixelX(x), this.toGraphPixelY(y));
349
+ }
350
+ pathObject.updatePath();
351
+ }
352
+
353
+ addFunctionLabel(holder, functionConfig) {
354
+ const labelText = String(functionConfig.label);
355
+ const fontSize = 12;
356
+ const padding = 6;
357
+ const estimatedWidth = Math.max(40, labelText.length * (fontSize * 0.6));
358
+ const estimatedHeight = fontSize + padding;
359
+
360
+ const label = new jsvgTextBox();
361
+ label.setWidthAndHeight(estimatedWidth, estimatedHeight);
362
+ label.setText(labelText);
363
+ label.setFontSize(fontSize);
364
+ label.setFontColor(this.getAllowedColor(functionConfig.color || "black"));
365
+ label.setAlignment("center");
366
+ label.setVerticalCentering();
367
+ label.setFontWeight(500);
368
+
369
+ const anchorX = (typeof functionConfig.labelAtX === 'number')
370
+ ? functionConfig.labelAtX
371
+ : this.xMin + (this.xMax - this.xMin) * 0.1;
372
+
373
+ let anchorY = this.yMin + (this.yMax - this.yMin) * 0.1;
374
+ let equationBody = functionConfig.equation;
375
+ if (equationBody.toLowerCase().startsWith("y=")) {
376
+ equationBody = equationBody.substring(2).trim();
377
+ }
378
+ const compiled = math.compile(equationBody);
379
+ const yVal = compiled.evaluate({ x: anchorX });
380
+ anchorY = yVal;
381
+
382
+ const xPx = this.toGraphPixelX(anchorX);
383
+ const yPx = this.toGraphPixelY(anchorY);
384
+
385
+ const positionPref = (functionConfig.labelPosition || 'below').toLowerCase();
386
+ const offset = 10;
387
+ let finalX = xPx;
388
+ let finalY = yPx;
389
+ switch (positionPref) {
390
+ case 'above':
391
+ finalY = yPx - offset;
392
+ break;
393
+ case 'below':
394
+ finalY = yPx + offset;
395
+ break;
396
+ case 'left':
397
+ finalX = xPx - offset;
398
+ break;
399
+ case 'right':
400
+ finalX = xPx + offset;
401
+ break;
402
+ default:
403
+ finalY = yPx + offset;
404
+ }
405
+
406
+ label.setPosition(finalX, finalY);
407
+ holder.addChild(label);
408
+ }
409
+
410
+ drawLineSegments(holder) {
411
+ for (const segment of this.lineSegments) {
412
+ const x1 = this.toGraphPixelX(segment.point1[0]);
413
+ const y1 = this.toGraphPixelY(segment.point1[1]);
414
+ const x2 = this.toGraphPixelX(segment.point2[0]);
415
+ const y2 = this.toGraphPixelY(segment.point2[1]);
416
+
417
+ const line = new jsvgLine();
418
+ line.setEndpoints(x1, y1, x2, y2);
419
+ line.setStrokeColor(this.getAllowedColor(segment.color || "blue"));
420
+ line.setStrokeWidth(segment.strokeWidth || 2);
421
+ holder.addChild(line);
422
+ }
423
+ }
424
+
425
+ plotDots(holder) {
426
+ for (const dot of this.dotValues) {
427
+ const pX = this.toGraphPixelX(dot[0]);
428
+ const pY = this.toGraphPixelY(dot[1]);
429
+
430
+ const ellipse = new jsvgEllipse();
431
+ ellipse.setWidthAndHeight(this.dotSize, this.dotSize);
432
+ ellipse.setPosition(pX, pY);
433
+ ellipse.setFillColor(this.getAllowedColor(dot.length >= 3 ? dot[2] : "black"));
434
+ holder.addChild(ellipse);
435
+ }
436
+ }
437
+
438
+ addShapes(holder) {
439
+ holder.setPosition(
440
+ -this.xMin * this.xSpacer,
441
+ this.graphHeight + this.yMin * this.ySpacer
442
+ );
443
+
444
+ const typeToCtor = {
445
+ rightTriangle: omdRightTriangle,
446
+ isoscelesTriangle: omdIsoscelesTriangle,
447
+ rectangle: omdRectangle,
448
+ ellipse: omdEllipse,
449
+ circle: omdCircle,
450
+ regularPolygon: omdRegularPolygon
451
+ };
452
+
453
+ for (const shapeData of this.shapeSet) {
454
+ const Ctor = typeToCtor[shapeData.omdType];
455
+ if (!Ctor) {
456
+ continue;
457
+ }
458
+ shapeData.unitScale = this.xSpacer;
459
+ const shape = new Ctor();
460
+ shape.loadFromJSON(shapeData);
461
+ if (shape.shapePath) {
462
+ shape.shapePath.setFillColor("none");
463
+ }
464
+ holder.addChild(shape);
465
+ }
466
+ }
467
+ }
@@ -0,0 +1,125 @@
1
+
2
+ import { omdColor } from "./omdColor.js";
3
+ import { jsvgLayoutGroup, jsvgGroup } from "@teachinglab/jsvg";
4
+ import { omdOperator } from "./omdOperator.js";
5
+ import { omdMetaExpression } from "./omdMetaExpression.js"
6
+ import { omdTerm } from "./omdTerm.js";
7
+ import { omdExpression } from "./omdExpression.js";
8
+ import { omdVariable } from "./omdVariable.js";
9
+ import { omdString } from "./omdString.js";
10
+
11
+
12
+ export class omdEquation extends omdMetaExpression
13
+ {
14
+ constructor()
15
+ {
16
+ // initialization
17
+ super();
18
+
19
+ this.type = "omdPowerExpression";
20
+
21
+ this.leftExpression = null;
22
+ this.rightExpression = null;
23
+
24
+ this.centerEquation = true;
25
+ this.inset = 5;
26
+
27
+ this.equationStack = new jsvgLayoutGroup();
28
+ this.equationStack.setSpacer(-7);
29
+ this.addChild( this.equationStack );
30
+
31
+ this.leftHolder = new jsvgGroup();
32
+ this.equationStack.addChild( this.leftHolder );
33
+
34
+ this.equalSign = new omdOperator('=');
35
+ this.equationStack.addChild( this.equalSign );
36
+
37
+ this.rightHolder = new jsvgGroup();
38
+ this.equationStack.addChild( this.rightHolder );
39
+ }
40
+
41
+ // make an equation (x + 2) = (2x - 3)
42
+
43
+ loadFromJSON( data )
44
+ {
45
+ if ( typeof data.leftExpression != "undefined" )
46
+ {
47
+ // console.log("A");
48
+ // console.log( data.leftExpression );
49
+ if ( data.leftExpression.omdType == "expression" )
50
+ this.leftExpression = new omdExpression();
51
+ if ( data.leftExpression.omdType == "number" )
52
+ this.leftExpression = new omdNumber();
53
+ if ( data.leftExpression.omdType == "variable" )
54
+ this.leftExpression = new omdVariable();
55
+ if ( data.leftExpression.omdType == "term" )
56
+ this.leftExpression = new omdTerm();
57
+ this.leftExpression.loadFromJSON( data.leftExpression );
58
+ this.leftHolder.removeAllChildren();
59
+ this.leftHolder.addChild( this.leftExpression );
60
+ }
61
+ if ( typeof data.rightExpression != "undefined" )
62
+ {
63
+ // console.log("B");
64
+ // console.log( data.exponentExpression );
65
+ if ( data.rightExpression.omdType == "expression" )
66
+ this.rightExpression = new omdExpression();
67
+ if ( data.rightExpression.omdType == "number" )
68
+ this.rightExpression = new omdNumber();
69
+ if ( data.rightExpression.omdType == "variable" )
70
+ this.rightExpression = new omdVariable();
71
+ if ( data.rightExpression.omdType == "term" )
72
+ this.rightExpression = new omdTerm();
73
+ this.rightExpression.loadFromJSON( data.rightExpression );
74
+ this.rightHolder.removeAllChildren();
75
+ this.rightHolder.addChild( this.rightExpression );
76
+ }
77
+
78
+ this.equalSign.hideBackgroundByDefault();
79
+ this.leftExpression.hideBackgroundByDefault();
80
+ this.rightExpression.hideBackgroundByDefault();
81
+
82
+ this.centerEquation = false;
83
+ this.updateLayout();
84
+ }
85
+
86
+
87
+ setLeftAndRightExpressions( leftExp, rightExp )
88
+ {
89
+ this.leftExpression = leftExp;
90
+ this.leftHolder.removeAllChildren();
91
+ this.leftHolder.addChild( leftExp );
92
+
93
+ this.rightExpression = rightExp;
94
+ this.rightHolder.removeAllChildren();
95
+ this.rightHolder.addChild( rightExp );
96
+
97
+ this.equalSign.hideBackgroundByDefault();
98
+ this.leftExpression.hideBackgroundByDefault();
99
+ this.rightExpression.hideBackgroundByDefault();
100
+
101
+ this.updateLayout();
102
+ }
103
+
104
+ updateLayout()
105
+ {
106
+ this.leftHolder.setWidthAndHeight( this.leftExpression.width, this.leftExpression.height );
107
+ this.rightHolder.setWidthAndHeight( this.rightExpression.width, this.rightExpression.height );
108
+
109
+ this.equationStack.doHorizontalLayout();
110
+ this.equationStack.setPosition( this.inset, 0 );
111
+
112
+ var W = this.equationStack.width;
113
+ this.backRect.setWidthAndHeight( W + this.inset*2, 30 );
114
+
115
+ this.setWidthAndHeight( this.backRect.width, this.backRect.height );
116
+
117
+ if ( this.centerEquation )
118
+ {
119
+ var leftShift = this.leftExpression.width + this.equalSign.width*0.50;
120
+ this.backRect.setPosition( -1.0 * leftShift + this.inset/2, 0 );
121
+ this.equationStack.setPosition( -1.0 * leftShift + this.inset + this.inset/2, 0 );
122
+ }
123
+ }
124
+
125
+ }
@@ -0,0 +1,104 @@
1
+
2
+
3
+ import { omdColor } from "./omdColor.js";
4
+ import { jsvgLayoutGroup } from "@teachinglab/jsvg";
5
+ import { omdTerm } from "./omdTerm.js";
6
+ import { omdOperator } from "./omdOperator.js";
7
+ import { omdNumber } from "./omdNumber.js";
8
+ import { omdVariable } from "./omdVariable.js";
9
+ import { omdMetaExpression } from "./omdMetaExpression.js"
10
+
11
+ export class omdExpression extends omdMetaExpression
12
+ {
13
+ constructor()
14
+ {
15
+ // initialization
16
+ super();
17
+
18
+ this.type = "omdExpression";
19
+ this.termSet = [];
20
+ this.operatorSet = [];
21
+
22
+ this.inset = 5;
23
+
24
+ this.expressionStack = new jsvgLayoutGroup();
25
+ this.expressionStack.setPosition( this.inset, 0 );
26
+ this.expressionStack.setSpacer(-6);
27
+ this.addChild( this.expressionStack );
28
+ }
29
+
30
+ loadFromJSON( data )
31
+ {
32
+ if ( typeof data.termsAndOpers != "undefined" )
33
+ {
34
+ for( var i=0; i<data.termsAndOpers.length; i++ )
35
+ {
36
+ var elem = data.termsAndOpers[i];
37
+ if ( elem.omdType == "term" )
38
+ {
39
+ var T = new omdTerm();
40
+ T.loadFromJSON( elem );
41
+ T.hideBackgroundByDefault();
42
+ this.termSet.push( T );
43
+ this.expressionStack.addChild( T );
44
+ }
45
+ else if ( elem.omdType == "variable" )
46
+ {
47
+ var T = new omdVariable();
48
+ T.loadFromJSON( elem );
49
+ T.hideBackgroundByDefault();
50
+ this.termSet.push( T );
51
+ this.expressionStack.addChild( T );
52
+ }
53
+ else if ( elem.omdType == "number" )
54
+ {
55
+ var T = new omdNumber();
56
+ T.loadFromJSON( elem );
57
+ T.hideBackgroundByDefault();
58
+ this.termSet.push( T );
59
+ this.expressionStack.addChild( T );
60
+ }
61
+ else if ( elem.omdType == "operator" )
62
+ {
63
+ var P = new omdOperator();
64
+ P.loadFromJSON( elem );
65
+ P.hideBackgroundByDefault();
66
+ this.operatorSet.push( P );
67
+ this.expressionStack.addChild( P );
68
+ }
69
+ }
70
+ }
71
+
72
+ this.updateLayout();
73
+ }
74
+
75
+ addTerm( coefficient, variable='', exponent='1' )
76
+ {
77
+ var T = new omdTerm( coefficient, variable, exponent );
78
+ this.termSet.push( T );
79
+ this.expressionStack.addChild( T );
80
+ this.updateLayout();
81
+
82
+ T.hideBackgroundByDefault();
83
+ }
84
+
85
+ addOperator( oper )
86
+ {
87
+ var P = new omdOperator(oper);
88
+ this.operatorSet.push( P );
89
+ this.expressionStack.addChild( P );
90
+ this.updateLayout();
91
+
92
+ P.hideBackgroundByDefault();
93
+ }
94
+
95
+ updateLayout()
96
+ {
97
+ this.expressionStack.doHorizontalLayout();
98
+
99
+ var W = this.expressionStack.width;
100
+ this.backRect.setWidthAndHeight( W + this.inset*2, 30 );
101
+
102
+ this.setWidthAndHeight( this.backRect.width, this.backRect.height );
103
+ }
104
+ }