@teachinglab/omd 0.6.1 → 0.6.3
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.
- package/README.md +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +606 -547
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +1045 -963
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -57
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
package/omd/nodes/omdSqrtNode.js
CHANGED
|
@@ -1,308 +1,308 @@
|
|
|
1
|
-
import { omdNode } from "./omdNode.js";
|
|
2
|
-
import { getNodeForAST } from "../core/omdUtilities.js";
|
|
3
|
-
import { omdConstantNode } from "./omdConstantNode.js";
|
|
4
|
-
import { omdPowerNode } from "./omdPowerNode.js";
|
|
5
|
-
import { omdBinaryExpressionNode } from "./omdBinaryExpressionNode.js";
|
|
6
|
-
import { simplifyStep } from "../simplification/omdSimplification.js";
|
|
7
|
-
import { jsvgPath, jsvgLine } from '@teachinglab/jsvg';
|
|
8
|
-
/**
|
|
9
|
-
* Represents a square root node in the mathematical expression tree
|
|
10
|
-
* Handles rendering of radical symbol and expression under the root
|
|
11
|
-
* @extends omdNode
|
|
12
|
-
*/
|
|
13
|
-
export class omdSqrtNode extends omdNode {
|
|
14
|
-
/**
|
|
15
|
-
* Creates a square root node from AST data
|
|
16
|
-
* @param {Object} astNodeData - The AST node containing sqrt function information
|
|
17
|
-
*/
|
|
18
|
-
constructor(astNodeData) {
|
|
19
|
-
super(astNodeData);
|
|
20
|
-
this.type = "omdSqrtNode";
|
|
21
|
-
this.args = astNodeData.args || [];
|
|
22
|
-
|
|
23
|
-
this.value = this.parseValue();
|
|
24
|
-
this.argument = this.createArgumentNode();
|
|
25
|
-
|
|
26
|
-
// Populate the argumentNodeList for the mathematical child node
|
|
27
|
-
if (this.argument) {
|
|
28
|
-
this.argumentNodeList.argument = this.argument;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
[ this.radicalPath, this.radicalLine ] = this.createRadicalElements();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
parseValue() {
|
|
35
|
-
return "sqrt";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Creates node for the expression under the radical
|
|
40
|
-
* @private
|
|
41
|
-
*/
|
|
42
|
-
createArgumentNode() {
|
|
43
|
-
if (this.args.length === 0) return null;
|
|
44
|
-
|
|
45
|
-
const argAst = this.args[0];
|
|
46
|
-
const ArgNodeType = getNodeForAST(argAst);
|
|
47
|
-
let child = new ArgNodeType(argAst);
|
|
48
|
-
this.addChild(child);
|
|
49
|
-
|
|
50
|
-
return child;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Creates radical symbol and line for sqrt
|
|
55
|
-
* @private
|
|
56
|
-
*/
|
|
57
|
-
createRadicalElements() {
|
|
58
|
-
// Create custom radical symbol using SVG path
|
|
59
|
-
let radicalPath = new jsvgPath();
|
|
60
|
-
radicalPath.setStrokeColor('black');
|
|
61
|
-
radicalPath.setStrokeWidth(2);
|
|
62
|
-
radicalPath.setFillColor('none');
|
|
63
|
-
this.addChild(radicalPath);
|
|
64
|
-
|
|
65
|
-
// Create the horizontal line over the expression
|
|
66
|
-
let radicalLine = new jsvgLine();
|
|
67
|
-
radicalLine.setStrokeColor('black');
|
|
68
|
-
radicalLine.setStrokeWidth(2);
|
|
69
|
-
this.addChild(radicalLine);
|
|
70
|
-
|
|
71
|
-
return [radicalPath, radicalLine];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Calculates the dimensions of the sqrt node and its children
|
|
76
|
-
* @override
|
|
77
|
-
*/
|
|
78
|
-
computeDimensions() {
|
|
79
|
-
if (!this.argument) return;
|
|
80
|
-
|
|
81
|
-
const fontSize = this.getFontSize();
|
|
82
|
-
const argFontSize = fontSize * 5/6; // Match rational node scaling
|
|
83
|
-
|
|
84
|
-
// Set font size for argument and compute its dimensions
|
|
85
|
-
this.argument.setFontSize(argFontSize);
|
|
86
|
-
this.argument.computeDimensions();
|
|
87
|
-
|
|
88
|
-
// Calculate dimensions using the expression height to size the radical
|
|
89
|
-
const ratio = fontSize / this.getRootFontSize();
|
|
90
|
-
const spacing = 4 * ratio;
|
|
91
|
-
|
|
92
|
-
const argWidth = this.argument.width;
|
|
93
|
-
const argHeight = this.argument.height;
|
|
94
|
-
|
|
95
|
-
// Radical width is proportional to expression height
|
|
96
|
-
const radicalWidth = Math.max(12 * ratio, argHeight * 0.3);
|
|
97
|
-
|
|
98
|
-
const totalWidth = radicalWidth + spacing + argWidth + spacing;
|
|
99
|
-
const totalHeight = argHeight + 8 * ratio; // Extra height for the radical top
|
|
100
|
-
|
|
101
|
-
this.setWidthAndHeight(totalWidth, totalHeight);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Updates the layout of the sqrt node and its children
|
|
106
|
-
* @override
|
|
107
|
-
*/
|
|
108
|
-
updateLayout() {
|
|
109
|
-
if (!this.argument) return;
|
|
110
|
-
|
|
111
|
-
const fontSize = this.getFontSize();
|
|
112
|
-
const ratio = fontSize / this.getRootFontSize();
|
|
113
|
-
const spacing = 4 * ratio;
|
|
114
|
-
|
|
115
|
-
let currentX = 0;
|
|
116
|
-
|
|
117
|
-
// Calculate radical dimensions based on expression
|
|
118
|
-
const expressionHeight = this.argument.height;
|
|
119
|
-
const radicalWidth = Math.max(12 * ratio, expressionHeight * 0.3);
|
|
120
|
-
|
|
121
|
-
// Position the expression first to get its exact position
|
|
122
|
-
const expressionX = currentX + radicalWidth + spacing;
|
|
123
|
-
const expressionY = (this.height - expressionHeight) / 2;
|
|
124
|
-
|
|
125
|
-
this.argument.setPosition(expressionX, expressionY);
|
|
126
|
-
this.argument.updateLayout();
|
|
127
|
-
|
|
128
|
-
// Draw the radical path using addPoint method
|
|
129
|
-
const radicalBottom = expressionY + expressionHeight - 2 * ratio;
|
|
130
|
-
const radicalTop = expressionY - 4 * ratio;
|
|
131
|
-
const radicalMid = expressionY + expressionHeight * 0.7;
|
|
132
|
-
|
|
133
|
-
// Clear previous points and create radical path: short diagonal down, long diagonal up
|
|
134
|
-
this.radicalPath.clearPoints();
|
|
135
|
-
this.radicalPath.addPoint(currentX + radicalWidth * 0.3, radicalMid);
|
|
136
|
-
this.radicalPath.addPoint(currentX + radicalWidth * 0.6, radicalBottom);
|
|
137
|
-
this.radicalPath.addPoint(currentX + radicalWidth, radicalTop);
|
|
138
|
-
this.radicalPath.updatePath();
|
|
139
|
-
|
|
140
|
-
// Position horizontal line above the expression
|
|
141
|
-
const lineY = expressionY - 2 * ratio;
|
|
142
|
-
const lineStartX = currentX + radicalWidth;
|
|
143
|
-
const lineEndX = expressionX + this.argument.width + spacing / 2;
|
|
144
|
-
this.radicalLine.setEndpoints(lineStartX, lineY, lineEndX, lineY);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
clone() {
|
|
148
|
-
let newAstData;
|
|
149
|
-
if (typeof this.astNodeData.clone === 'function') {
|
|
150
|
-
newAstData = this.astNodeData.clone();
|
|
151
|
-
} else {
|
|
152
|
-
newAstData = JSON.parse(JSON.stringify(this.astNodeData));
|
|
153
|
-
}
|
|
154
|
-
const clone = new omdSqrtNode(newAstData);
|
|
155
|
-
|
|
156
|
-
// Keep the backRect from the clone, not from 'this'
|
|
157
|
-
const backRect = clone.backRect;
|
|
158
|
-
clone.removeAllChildren();
|
|
159
|
-
clone.addChild(backRect);
|
|
160
|
-
|
|
161
|
-
// Create new jsvg elements for the clone
|
|
162
|
-
clone.radicalPath = new jsvgPath();
|
|
163
|
-
clone.radicalPath.setStrokeColor('black');
|
|
164
|
-
clone.radicalPath.setStrokeWidth(2);
|
|
165
|
-
clone.radicalPath.setFillColor('none');
|
|
166
|
-
clone.addChild(clone.radicalPath);
|
|
167
|
-
|
|
168
|
-
clone.radicalLine = new jsvgLine();
|
|
169
|
-
clone.radicalLine.setStrokeColor('black');
|
|
170
|
-
clone.radicalLine.setStrokeWidth(2);
|
|
171
|
-
clone.addChild(clone.radicalLine);
|
|
172
|
-
|
|
173
|
-
if (this.argument) {
|
|
174
|
-
clone.argument = this.argument.clone();
|
|
175
|
-
clone.addChild(clone.argument);
|
|
176
|
-
|
|
177
|
-
// Explicitly update the argumentNodeList in the cloned node
|
|
178
|
-
clone.argumentNodeList.argument = clone.argument;
|
|
179
|
-
|
|
180
|
-
// The crucial step: link the clone to its origin
|
|
181
|
-
clone.provenance.push(this.id);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return clone;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Highlights the sqrt node and its argument
|
|
189
|
-
*/
|
|
190
|
-
highlightAll() {
|
|
191
|
-
this.select();
|
|
192
|
-
|
|
193
|
-
if (this.argument && this.argument.highlightAll) {
|
|
194
|
-
this.argument.highlightAll();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Unhighlights the sqrt node and its argument
|
|
200
|
-
*/
|
|
201
|
-
unhighlightAll() {
|
|
202
|
-
this.deselect();
|
|
203
|
-
|
|
204
|
-
if (this.argument && this.argument.unhighlightAll) {
|
|
205
|
-
this.argument.unhighlightAll();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Converts the omdSqrtNode to a math.js AST node.
|
|
211
|
-
* @returns {Object} A math.js-compatible AST node.
|
|
212
|
-
*/
|
|
213
|
-
toMathJSNode() {
|
|
214
|
-
const astNode = {
|
|
215
|
-
type: 'FunctionNode',
|
|
216
|
-
fn: { type: 'SymbolNode', name: 'sqrt', clone: function() { return {...this}; } },
|
|
217
|
-
args: this.argument ? [this.argument.toMathJSNode()] : []
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// Add a clone method to maintain compatibility with math.js's expectations.
|
|
221
|
-
astNode.clone = function() {
|
|
222
|
-
const clonedNode = { ...this };
|
|
223
|
-
if (this.args) {
|
|
224
|
-
clonedNode.args = this.args.map(arg => arg.clone());
|
|
225
|
-
}
|
|
226
|
-
if (this.fn && typeof this.fn.clone === 'function') {
|
|
227
|
-
clonedNode.fn = this.fn.clone();
|
|
228
|
-
}
|
|
229
|
-
return clonedNode;
|
|
230
|
-
};
|
|
231
|
-
return astNode;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Converts the square root node to a string representation.
|
|
235
|
-
* @returns {string} The string representation.
|
|
236
|
-
*/
|
|
237
|
-
toString() {
|
|
238
|
-
return `sqrt(${this.argument ? this.argument.toString() : ''})`;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Evaluate the root expression.
|
|
243
|
-
* @param {Object} variables - Variable name to value mapping
|
|
244
|
-
* @returns {number} The evaluated root
|
|
245
|
-
*/
|
|
246
|
-
evaluate(variables = {}) {
|
|
247
|
-
if (!this.argument || !this.argument.evaluate) {
|
|
248
|
-
return NaN;
|
|
249
|
-
}
|
|
250
|
-
const radicandValue = this.argument.evaluate(variables);
|
|
251
|
-
if (radicandValue < 0) {
|
|
252
|
-
return NaN; // Or handle complex numbers if desired
|
|
253
|
-
}
|
|
254
|
-
return Math.sqrt(radicandValue);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Check if this is a square root (index = 2).
|
|
259
|
-
* @returns {boolean}
|
|
260
|
-
*/
|
|
261
|
-
isSquareRoot() {
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Check if this is a cube root (index = 3).
|
|
267
|
-
* @returns {boolean}
|
|
268
|
-
*/
|
|
269
|
-
isCubeRoot() {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Convert to equivalent power notation.
|
|
275
|
-
* @returns {omdPowerNode} Equivalent power expression
|
|
276
|
-
*/
|
|
277
|
-
toPowerForm() {
|
|
278
|
-
if (!this.argument) return null;
|
|
279
|
-
|
|
280
|
-
const powerAst = {
|
|
281
|
-
type: 'OperatorNode', op: '^', fn: 'pow',
|
|
282
|
-
args: [
|
|
283
|
-
this.argument.toMathJSNode(),
|
|
284
|
-
omdConstantNode.fromNumber(0.5).toMathJSNode()
|
|
285
|
-
]
|
|
286
|
-
};
|
|
287
|
-
return new omdPowerNode(powerAst);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Create a root node from a string.
|
|
292
|
-
* @static
|
|
293
|
-
* @param {string} expressionString - Expression with root
|
|
294
|
-
* @returns {omdSqrtNode}
|
|
295
|
-
*/
|
|
296
|
-
static fromString(expressionString) {
|
|
297
|
-
try {
|
|
298
|
-
const ast = window.math.parse(expressionString);
|
|
299
|
-
if (ast.type === 'FunctionNode' && ast.fn.name === 'sqrt') {
|
|
300
|
-
return new omdSqrtNode(ast);
|
|
301
|
-
}
|
|
302
|
-
throw new Error("Expression is not a 'sqrt' function.");
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error("Failed to create sqrt node from string:", error);
|
|
305
|
-
throw error;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
1
|
+
import { omdNode } from "./omdNode.js";
|
|
2
|
+
import { getNodeForAST } from "../core/omdUtilities.js";
|
|
3
|
+
import { omdConstantNode } from "./omdConstantNode.js";
|
|
4
|
+
import { omdPowerNode } from "./omdPowerNode.js";
|
|
5
|
+
import { omdBinaryExpressionNode } from "./omdBinaryExpressionNode.js";
|
|
6
|
+
import { simplifyStep } from "../simplification/omdSimplification.js";
|
|
7
|
+
import { jsvgPath, jsvgLine } from '@teachinglab/jsvg';
|
|
8
|
+
/**
|
|
9
|
+
* Represents a square root node in the mathematical expression tree
|
|
10
|
+
* Handles rendering of radical symbol and expression under the root
|
|
11
|
+
* @extends omdNode
|
|
12
|
+
*/
|
|
13
|
+
export class omdSqrtNode extends omdNode {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a square root node from AST data
|
|
16
|
+
* @param {Object} astNodeData - The AST node containing sqrt function information
|
|
17
|
+
*/
|
|
18
|
+
constructor(astNodeData) {
|
|
19
|
+
super(astNodeData);
|
|
20
|
+
this.type = "omdSqrtNode";
|
|
21
|
+
this.args = astNodeData.args || [];
|
|
22
|
+
|
|
23
|
+
this.value = this.parseValue();
|
|
24
|
+
this.argument = this.createArgumentNode();
|
|
25
|
+
|
|
26
|
+
// Populate the argumentNodeList for the mathematical child node
|
|
27
|
+
if (this.argument) {
|
|
28
|
+
this.argumentNodeList.argument = this.argument;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[ this.radicalPath, this.radicalLine ] = this.createRadicalElements();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
parseValue() {
|
|
35
|
+
return "sqrt";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates node for the expression under the radical
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
42
|
+
createArgumentNode() {
|
|
43
|
+
if (this.args.length === 0) return null;
|
|
44
|
+
|
|
45
|
+
const argAst = this.args[0];
|
|
46
|
+
const ArgNodeType = getNodeForAST(argAst);
|
|
47
|
+
let child = new ArgNodeType(argAst);
|
|
48
|
+
this.addChild(child);
|
|
49
|
+
|
|
50
|
+
return child;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates radical symbol and line for sqrt
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
createRadicalElements() {
|
|
58
|
+
// Create custom radical symbol using SVG path
|
|
59
|
+
let radicalPath = new jsvgPath();
|
|
60
|
+
radicalPath.setStrokeColor('black');
|
|
61
|
+
radicalPath.setStrokeWidth(2);
|
|
62
|
+
radicalPath.setFillColor('none');
|
|
63
|
+
this.addChild(radicalPath);
|
|
64
|
+
|
|
65
|
+
// Create the horizontal line over the expression
|
|
66
|
+
let radicalLine = new jsvgLine();
|
|
67
|
+
radicalLine.setStrokeColor('black');
|
|
68
|
+
radicalLine.setStrokeWidth(2);
|
|
69
|
+
this.addChild(radicalLine);
|
|
70
|
+
|
|
71
|
+
return [radicalPath, radicalLine];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calculates the dimensions of the sqrt node and its children
|
|
76
|
+
* @override
|
|
77
|
+
*/
|
|
78
|
+
computeDimensions() {
|
|
79
|
+
if (!this.argument) return;
|
|
80
|
+
|
|
81
|
+
const fontSize = this.getFontSize();
|
|
82
|
+
const argFontSize = fontSize * 5/6; // Match rational node scaling
|
|
83
|
+
|
|
84
|
+
// Set font size for argument and compute its dimensions
|
|
85
|
+
this.argument.setFontSize(argFontSize);
|
|
86
|
+
this.argument.computeDimensions();
|
|
87
|
+
|
|
88
|
+
// Calculate dimensions using the expression height to size the radical
|
|
89
|
+
const ratio = fontSize / this.getRootFontSize();
|
|
90
|
+
const spacing = 4 * ratio;
|
|
91
|
+
|
|
92
|
+
const argWidth = this.argument.width;
|
|
93
|
+
const argHeight = this.argument.height;
|
|
94
|
+
|
|
95
|
+
// Radical width is proportional to expression height
|
|
96
|
+
const radicalWidth = Math.max(12 * ratio, argHeight * 0.3);
|
|
97
|
+
|
|
98
|
+
const totalWidth = radicalWidth + spacing + argWidth + spacing;
|
|
99
|
+
const totalHeight = argHeight + 8 * ratio; // Extra height for the radical top
|
|
100
|
+
|
|
101
|
+
this.setWidthAndHeight(totalWidth, totalHeight);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Updates the layout of the sqrt node and its children
|
|
106
|
+
* @override
|
|
107
|
+
*/
|
|
108
|
+
updateLayout() {
|
|
109
|
+
if (!this.argument) return;
|
|
110
|
+
|
|
111
|
+
const fontSize = this.getFontSize();
|
|
112
|
+
const ratio = fontSize / this.getRootFontSize();
|
|
113
|
+
const spacing = 4 * ratio;
|
|
114
|
+
|
|
115
|
+
let currentX = 0;
|
|
116
|
+
|
|
117
|
+
// Calculate radical dimensions based on expression
|
|
118
|
+
const expressionHeight = this.argument.height;
|
|
119
|
+
const radicalWidth = Math.max(12 * ratio, expressionHeight * 0.3);
|
|
120
|
+
|
|
121
|
+
// Position the expression first to get its exact position
|
|
122
|
+
const expressionX = currentX + radicalWidth + spacing;
|
|
123
|
+
const expressionY = (this.height - expressionHeight) / 2;
|
|
124
|
+
|
|
125
|
+
this.argument.setPosition(expressionX, expressionY);
|
|
126
|
+
this.argument.updateLayout();
|
|
127
|
+
|
|
128
|
+
// Draw the radical path using addPoint method
|
|
129
|
+
const radicalBottom = expressionY + expressionHeight - 2 * ratio;
|
|
130
|
+
const radicalTop = expressionY - 4 * ratio;
|
|
131
|
+
const radicalMid = expressionY + expressionHeight * 0.7;
|
|
132
|
+
|
|
133
|
+
// Clear previous points and create radical path: short diagonal down, long diagonal up
|
|
134
|
+
this.radicalPath.clearPoints();
|
|
135
|
+
this.radicalPath.addPoint(currentX + radicalWidth * 0.3, radicalMid);
|
|
136
|
+
this.radicalPath.addPoint(currentX + radicalWidth * 0.6, radicalBottom);
|
|
137
|
+
this.radicalPath.addPoint(currentX + radicalWidth, radicalTop);
|
|
138
|
+
this.radicalPath.updatePath();
|
|
139
|
+
|
|
140
|
+
// Position horizontal line above the expression
|
|
141
|
+
const lineY = expressionY - 2 * ratio;
|
|
142
|
+
const lineStartX = currentX + radicalWidth;
|
|
143
|
+
const lineEndX = expressionX + this.argument.width + spacing / 2;
|
|
144
|
+
this.radicalLine.setEndpoints(lineStartX, lineY, lineEndX, lineY);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
clone() {
|
|
148
|
+
let newAstData;
|
|
149
|
+
if (typeof this.astNodeData.clone === 'function') {
|
|
150
|
+
newAstData = this.astNodeData.clone();
|
|
151
|
+
} else {
|
|
152
|
+
newAstData = JSON.parse(JSON.stringify(this.astNodeData));
|
|
153
|
+
}
|
|
154
|
+
const clone = new omdSqrtNode(newAstData);
|
|
155
|
+
|
|
156
|
+
// Keep the backRect from the clone, not from 'this'
|
|
157
|
+
const backRect = clone.backRect;
|
|
158
|
+
clone.removeAllChildren();
|
|
159
|
+
clone.addChild(backRect);
|
|
160
|
+
|
|
161
|
+
// Create new jsvg elements for the clone
|
|
162
|
+
clone.radicalPath = new jsvgPath();
|
|
163
|
+
clone.radicalPath.setStrokeColor('black');
|
|
164
|
+
clone.radicalPath.setStrokeWidth(2);
|
|
165
|
+
clone.radicalPath.setFillColor('none');
|
|
166
|
+
clone.addChild(clone.radicalPath);
|
|
167
|
+
|
|
168
|
+
clone.radicalLine = new jsvgLine();
|
|
169
|
+
clone.radicalLine.setStrokeColor('black');
|
|
170
|
+
clone.radicalLine.setStrokeWidth(2);
|
|
171
|
+
clone.addChild(clone.radicalLine);
|
|
172
|
+
|
|
173
|
+
if (this.argument) {
|
|
174
|
+
clone.argument = this.argument.clone();
|
|
175
|
+
clone.addChild(clone.argument);
|
|
176
|
+
|
|
177
|
+
// Explicitly update the argumentNodeList in the cloned node
|
|
178
|
+
clone.argumentNodeList.argument = clone.argument;
|
|
179
|
+
|
|
180
|
+
// The crucial step: link the clone to its origin
|
|
181
|
+
clone.provenance.push(this.id);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return clone;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Highlights the sqrt node and its argument
|
|
189
|
+
*/
|
|
190
|
+
highlightAll() {
|
|
191
|
+
this.select();
|
|
192
|
+
|
|
193
|
+
if (this.argument && this.argument.highlightAll) {
|
|
194
|
+
this.argument.highlightAll();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Unhighlights the sqrt node and its argument
|
|
200
|
+
*/
|
|
201
|
+
unhighlightAll() {
|
|
202
|
+
this.deselect();
|
|
203
|
+
|
|
204
|
+
if (this.argument && this.argument.unhighlightAll) {
|
|
205
|
+
this.argument.unhighlightAll();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Converts the omdSqrtNode to a math.js AST node.
|
|
211
|
+
* @returns {Object} A math.js-compatible AST node.
|
|
212
|
+
*/
|
|
213
|
+
toMathJSNode() {
|
|
214
|
+
const astNode = {
|
|
215
|
+
type: 'FunctionNode',
|
|
216
|
+
fn: { type: 'SymbolNode', name: 'sqrt', clone: function() { return {...this}; } },
|
|
217
|
+
args: this.argument ? [this.argument.toMathJSNode()] : []
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Add a clone method to maintain compatibility with math.js's expectations.
|
|
221
|
+
astNode.clone = function() {
|
|
222
|
+
const clonedNode = { ...this };
|
|
223
|
+
if (this.args) {
|
|
224
|
+
clonedNode.args = this.args.map(arg => arg.clone());
|
|
225
|
+
}
|
|
226
|
+
if (this.fn && typeof this.fn.clone === 'function') {
|
|
227
|
+
clonedNode.fn = this.fn.clone();
|
|
228
|
+
}
|
|
229
|
+
return clonedNode;
|
|
230
|
+
};
|
|
231
|
+
return astNode;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Converts the square root node to a string representation.
|
|
235
|
+
* @returns {string} The string representation.
|
|
236
|
+
*/
|
|
237
|
+
toString() {
|
|
238
|
+
return `sqrt(${this.argument ? this.argument.toString() : ''})`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Evaluate the root expression.
|
|
243
|
+
* @param {Object} variables - Variable name to value mapping
|
|
244
|
+
* @returns {number} The evaluated root
|
|
245
|
+
*/
|
|
246
|
+
evaluate(variables = {}) {
|
|
247
|
+
if (!this.argument || !this.argument.evaluate) {
|
|
248
|
+
return NaN;
|
|
249
|
+
}
|
|
250
|
+
const radicandValue = this.argument.evaluate(variables);
|
|
251
|
+
if (radicandValue < 0) {
|
|
252
|
+
return NaN; // Or handle complex numbers if desired
|
|
253
|
+
}
|
|
254
|
+
return Math.sqrt(radicandValue);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if this is a square root (index = 2).
|
|
259
|
+
* @returns {boolean}
|
|
260
|
+
*/
|
|
261
|
+
isSquareRoot() {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if this is a cube root (index = 3).
|
|
267
|
+
* @returns {boolean}
|
|
268
|
+
*/
|
|
269
|
+
isCubeRoot() {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Convert to equivalent power notation.
|
|
275
|
+
* @returns {omdPowerNode} Equivalent power expression
|
|
276
|
+
*/
|
|
277
|
+
toPowerForm() {
|
|
278
|
+
if (!this.argument) return null;
|
|
279
|
+
|
|
280
|
+
const powerAst = {
|
|
281
|
+
type: 'OperatorNode', op: '^', fn: 'pow',
|
|
282
|
+
args: [
|
|
283
|
+
this.argument.toMathJSNode(),
|
|
284
|
+
omdConstantNode.fromNumber(0.5).toMathJSNode()
|
|
285
|
+
]
|
|
286
|
+
};
|
|
287
|
+
return new omdPowerNode(powerAst);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Create a root node from a string.
|
|
292
|
+
* @static
|
|
293
|
+
* @param {string} expressionString - Expression with root
|
|
294
|
+
* @returns {omdSqrtNode}
|
|
295
|
+
*/
|
|
296
|
+
static fromString(expressionString) {
|
|
297
|
+
try {
|
|
298
|
+
const ast = window.math.parse(expressionString);
|
|
299
|
+
if (ast.type === 'FunctionNode' && ast.fn.name === 'sqrt') {
|
|
300
|
+
return new omdSqrtNode(ast);
|
|
301
|
+
}
|
|
302
|
+
throw new Error("Expression is not a 'sqrt' function.");
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error("Failed to create sqrt node from string:", error);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
308
|
}
|