@teachinglab/omd 0.6.0 → 0.6.2
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 +546 -546
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +969 -962
- 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 -56
- 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
|
@@ -1,293 +1,293 @@
|
|
|
1
|
-
import { omdGroupNode } from "./omdGroupNode.js";
|
|
2
|
-
import { omdNode } from "./omdNode.js";
|
|
3
|
-
import { getNodeForAST } from "../core/omdUtilities.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Represents a parenthesized expression node in the mathematical expression tree
|
|
7
|
-
* Handles rendering of parentheses and their contained expression
|
|
8
|
-
* @extends omdNode
|
|
9
|
-
*/
|
|
10
|
-
export class omdParenthesisNode extends omdNode {
|
|
11
|
-
/**
|
|
12
|
-
* Creates a parenthesis node from AST data
|
|
13
|
-
* @param {Object} astNodeData - The AST node containing parenthesis information
|
|
14
|
-
*/
|
|
15
|
-
constructor(astNodeData) {
|
|
16
|
-
super(astNodeData);
|
|
17
|
-
this.type = "omdParenthesisNode";
|
|
18
|
-
|
|
19
|
-
const innerContent = astNodeData.content || (astNodeData.args && astNodeData.args[0]);
|
|
20
|
-
if (!innerContent) {
|
|
21
|
-
console.error("omdParenthesisNode requires inner content", astNodeData);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
this.open = this.createParenthesis("(");
|
|
26
|
-
this.expression = this.createExpression(innerContent);
|
|
27
|
-
this.closed = this.createParenthesis(")");
|
|
28
|
-
|
|
29
|
-
// Populate the argumentNodeList for the mathematical child node
|
|
30
|
-
this.argumentNodeList.expression = this.expression;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
createParenthesis(parenthesis) {
|
|
34
|
-
let child = new omdGroupNode(parenthesis);
|
|
35
|
-
this.addChild(child);
|
|
36
|
-
|
|
37
|
-
return child;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
createExpression(ast) {
|
|
41
|
-
let ExpressionType = getNodeForAST(ast);
|
|
42
|
-
let child = new ExpressionType(ast);
|
|
43
|
-
this.addChild(child);
|
|
44
|
-
|
|
45
|
-
return child;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Calculates the dimensions of the parenthesis node and its children
|
|
50
|
-
* @override
|
|
51
|
-
*/
|
|
52
|
-
computeDimensions() {
|
|
53
|
-
this.open.computeDimensions();
|
|
54
|
-
this.expression.computeDimensions();
|
|
55
|
-
this.closed.computeDimensions();
|
|
56
|
-
|
|
57
|
-
// Gerard: Calculate dimensions and padding
|
|
58
|
-
let padding = 4 * this.getFontSize() / this.getRootFontSize(); // Gerard: Padding should scale with font size
|
|
59
|
-
let sumWidth = this.open.width + this.expression.width + this.closed.width;
|
|
60
|
-
let maxHeight = Math.max(this.expression.height, this.closed.height, this.open.height);
|
|
61
|
-
|
|
62
|
-
this.setWidthAndHeight(sumWidth, maxHeight + padding);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Updates the layout of the parenthesis node and its children
|
|
67
|
-
* @override
|
|
68
|
-
*/
|
|
69
|
-
updateLayout() {
|
|
70
|
-
let xCurrent = 0;
|
|
71
|
-
|
|
72
|
-
// For proper mathematical typesetting, parentheses should be centered
|
|
73
|
-
// relative to the baseline of the mathematical content, properly accounting
|
|
74
|
-
// for power nodes and their baseline positioning
|
|
75
|
-
|
|
76
|
-
// First, position the expression based on its alignment baseline
|
|
77
|
-
this.expression.updateLayout();
|
|
78
|
-
const expressionBaseline = this.expression.getAlignmentBaseline();
|
|
79
|
-
const totalBaseline = this.getAlignmentBaseline();
|
|
80
|
-
const expressionY = totalBaseline - expressionBaseline;
|
|
81
|
-
this.expression.setPosition(this.open.width, expressionY);
|
|
82
|
-
|
|
83
|
-
// Position parentheses centered around the mathematical baseline
|
|
84
|
-
// This ensures proper centering for power nodes where the baseline
|
|
85
|
-
// represents the position of the base (e.g., 'x' in 'x²')
|
|
86
|
-
const mathematicalBaseline = expressionY + expressionBaseline;
|
|
87
|
-
const parenY = mathematicalBaseline - (this.open.height / 2);
|
|
88
|
-
|
|
89
|
-
this.open.updateLayout();
|
|
90
|
-
this.open.setPosition(0, parenY);
|
|
91
|
-
|
|
92
|
-
this.closed.updateLayout();
|
|
93
|
-
this.closed.setPosition(this.open.width + this.expression.width, parenY);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* For parenthesis nodes, the alignment baseline is the baseline of the inner expression.
|
|
98
|
-
* @override
|
|
99
|
-
* @returns {number} The y-coordinate for alignment.
|
|
100
|
-
*/
|
|
101
|
-
getAlignmentBaseline() {
|
|
102
|
-
// The baseline is the y-position of the expression node plus its own baseline.
|
|
103
|
-
const childY = (this.height - this.expression.height) / 2;
|
|
104
|
-
return childY + this.expression.getAlignmentBaseline();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
clone() {
|
|
108
|
-
// Create a new node. The constructor will add a backRect and temporary children.
|
|
109
|
-
const tempAst = { type: 'ParenthesisNode', content: {type: 'ConstantNode', value: 1} };
|
|
110
|
-
const clone = new omdParenthesisNode(tempAst);
|
|
111
|
-
|
|
112
|
-
// Keep the backRect, but get rid of the temporary children.
|
|
113
|
-
const backRect = clone.backRect;
|
|
114
|
-
clone.removeAllChildren();
|
|
115
|
-
clone.addChild(backRect);
|
|
116
|
-
|
|
117
|
-
// Manually clone the real children to ensure the entire tree has correct provenance.
|
|
118
|
-
clone.open = this.open.clone();
|
|
119
|
-
clone.addChild(clone.open);
|
|
120
|
-
|
|
121
|
-
clone.expression = this.expression.clone();
|
|
122
|
-
clone.addChild(clone.expression);
|
|
123
|
-
|
|
124
|
-
clone.closed = this.closed.clone();
|
|
125
|
-
clone.addChild(clone.closed);
|
|
126
|
-
|
|
127
|
-
// Rebuild the argument list
|
|
128
|
-
clone.argumentNodeList = { expression: clone.expression };
|
|
129
|
-
|
|
130
|
-
// Regenerate AST data from the cloned structure instead of copying it
|
|
131
|
-
// This ensures the AST properly reflects the cloned nodes
|
|
132
|
-
clone.astNodeData = clone.toMathJSNode();
|
|
133
|
-
|
|
134
|
-
// The crucial step: link the clone to its origin.
|
|
135
|
-
clone.provenance.push(this.id);
|
|
136
|
-
|
|
137
|
-
return clone;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* A parenthesis node is constant if its inner expression is constant.
|
|
142
|
-
* @returns {boolean}
|
|
143
|
-
*/
|
|
144
|
-
isConstant() {
|
|
145
|
-
return this.expression ? this.expression.isConstant() : false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* The value of a parenthesis node is the value of its inner expression.
|
|
150
|
-
* @returns {number}
|
|
151
|
-
*/
|
|
152
|
-
getValue() {
|
|
153
|
-
if (!this.expression) {
|
|
154
|
-
throw new Error("Parenthesis node has no expression from which to get a value.");
|
|
155
|
-
}
|
|
156
|
-
return this.expression.getValue();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Converts the omdParenthesisNode to a math.js AST node.
|
|
161
|
-
* @returns {Object} A math.js-compatible AST node.
|
|
162
|
-
*/
|
|
163
|
-
toMathJSNode() {
|
|
164
|
-
const astNode = {
|
|
165
|
-
type: 'ParenthesisNode',
|
|
166
|
-
content: this.expression.toMathJSNode(),
|
|
167
|
-
id: this.id,
|
|
168
|
-
provenance: this.provenance
|
|
169
|
-
};
|
|
170
|
-
// Add a clone method to maintain compatibility with math.js's expectations.
|
|
171
|
-
astNode.clone = function() {
|
|
172
|
-
const clonedNode = { ...this };
|
|
173
|
-
if (this.content) {
|
|
174
|
-
clonedNode.content = this.content.clone();
|
|
175
|
-
}
|
|
176
|
-
return clonedNode;
|
|
177
|
-
};
|
|
178
|
-
return astNode;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Converts the parenthesis node to a string.
|
|
183
|
-
* @returns {string} The string representation of the parenthesized expression.
|
|
184
|
-
*/
|
|
185
|
-
toString() {
|
|
186
|
-
const innerExpr = this.expression.toString();
|
|
187
|
-
return `(${innerExpr})`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Evaluate the content within parentheses
|
|
192
|
-
* @param {Object} variables - Variable name to value mapping
|
|
193
|
-
* @returns {number} The evaluated result
|
|
194
|
-
*/
|
|
195
|
-
evaluate(variables = {}) {
|
|
196
|
-
if (!this.expression) {
|
|
197
|
-
throw new Error("Parenthesis node has no expression to evaluate");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Evaluate the inner expression
|
|
201
|
-
if (this.expression.evaluate) {
|
|
202
|
-
return this.expression.evaluate(variables);
|
|
203
|
-
} else if (this.expression.isConstant()) {
|
|
204
|
-
return this.expression.getValue();
|
|
205
|
-
} else {
|
|
206
|
-
throw new Error("Cannot evaluate parenthesis content");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Check if parentheses are necessary based on context
|
|
212
|
-
* @returns {boolean} Whether parentheses affect evaluation order
|
|
213
|
-
*/
|
|
214
|
-
isNecessary() {
|
|
215
|
-
// If there's no parent, parentheses at the top level are not necessary
|
|
216
|
-
if (!this.parent) {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Check for single constants or variables - parentheses not needed
|
|
221
|
-
if (this.expression.type === 'omdConstantNode' ||
|
|
222
|
-
this.expression.type === 'omdVariableNode') {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Check parent context
|
|
227
|
-
const parent = this.parent;
|
|
228
|
-
|
|
229
|
-
// If parent is a function, parentheses are part of function syntax
|
|
230
|
-
if (parent.type === 'omdFunctionNode') {
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// If parent is a power node and this is the base or exponent
|
|
235
|
-
if (parent.type === 'omdPowerNode') {
|
|
236
|
-
// Check if we're the base
|
|
237
|
-
if (parent.base === this) {
|
|
238
|
-
// Base needs parentheses if it's a complex expression
|
|
239
|
-
return this.expression.type === 'omdBinaryExpressionNode';
|
|
240
|
-
}
|
|
241
|
-
// Check if we're the exponent
|
|
242
|
-
if (parent.exponent === this) {
|
|
243
|
-
// Exponent needs parentheses if it's not a simple value
|
|
244
|
-
return !(this.expression.type === 'omdConstantNode' ||
|
|
245
|
-
this.expression.type === 'omdVariableNode');
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// If parent is a binary expression, check operator precedence
|
|
250
|
-
if (parent.type === 'omdBinaryExpressionNode') {
|
|
251
|
-
// If the inner expression is also a binary expression,
|
|
252
|
-
// parentheses might be needed based on precedence
|
|
253
|
-
if (this.expression.type === 'omdBinaryExpressionNode') {
|
|
254
|
-
return true; // Conservative approach - keep parentheses
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Default: parentheses are not necessary
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Create a parenthesis node from a string
|
|
264
|
-
* @param {string} expressionString - Expression with parentheses
|
|
265
|
-
* @returns {omdParenthesisNode} The created parenthesis node
|
|
266
|
-
* @static
|
|
267
|
-
*/
|
|
268
|
-
static fromString(expressionString) {
|
|
269
|
-
if (!window.math) {
|
|
270
|
-
throw new Error("Math.js is required for parsing expressions");
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const trimmed = expressionString.trim();
|
|
274
|
-
|
|
275
|
-
// Ensure the string starts and ends with parentheses
|
|
276
|
-
if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
|
|
277
|
-
throw new Error("Expression must be enclosed in parentheses");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
try {
|
|
281
|
-
const ast = window.math.parse(trimmed);
|
|
282
|
-
|
|
283
|
-
// Verify it's a parenthesis node
|
|
284
|
-
if (ast.type !== 'ParenthesisNode') {
|
|
285
|
-
throw new Error("Parsed expression is not a parenthesis node");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return new omdParenthesisNode(ast);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
throw new Error(`Failed to parse parenthesis expression: ${error.message}`);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
1
|
+
import { omdGroupNode } from "./omdGroupNode.js";
|
|
2
|
+
import { omdNode } from "./omdNode.js";
|
|
3
|
+
import { getNodeForAST } from "../core/omdUtilities.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a parenthesized expression node in the mathematical expression tree
|
|
7
|
+
* Handles rendering of parentheses and their contained expression
|
|
8
|
+
* @extends omdNode
|
|
9
|
+
*/
|
|
10
|
+
export class omdParenthesisNode extends omdNode {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a parenthesis node from AST data
|
|
13
|
+
* @param {Object} astNodeData - The AST node containing parenthesis information
|
|
14
|
+
*/
|
|
15
|
+
constructor(astNodeData) {
|
|
16
|
+
super(astNodeData);
|
|
17
|
+
this.type = "omdParenthesisNode";
|
|
18
|
+
|
|
19
|
+
const innerContent = astNodeData.content || (astNodeData.args && astNodeData.args[0]);
|
|
20
|
+
if (!innerContent) {
|
|
21
|
+
console.error("omdParenthesisNode requires inner content", astNodeData);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.open = this.createParenthesis("(");
|
|
26
|
+
this.expression = this.createExpression(innerContent);
|
|
27
|
+
this.closed = this.createParenthesis(")");
|
|
28
|
+
|
|
29
|
+
// Populate the argumentNodeList for the mathematical child node
|
|
30
|
+
this.argumentNodeList.expression = this.expression;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createParenthesis(parenthesis) {
|
|
34
|
+
let child = new omdGroupNode(parenthesis);
|
|
35
|
+
this.addChild(child);
|
|
36
|
+
|
|
37
|
+
return child;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
createExpression(ast) {
|
|
41
|
+
let ExpressionType = getNodeForAST(ast);
|
|
42
|
+
let child = new ExpressionType(ast);
|
|
43
|
+
this.addChild(child);
|
|
44
|
+
|
|
45
|
+
return child;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Calculates the dimensions of the parenthesis node and its children
|
|
50
|
+
* @override
|
|
51
|
+
*/
|
|
52
|
+
computeDimensions() {
|
|
53
|
+
this.open.computeDimensions();
|
|
54
|
+
this.expression.computeDimensions();
|
|
55
|
+
this.closed.computeDimensions();
|
|
56
|
+
|
|
57
|
+
// Gerard: Calculate dimensions and padding
|
|
58
|
+
let padding = 4 * this.getFontSize() / this.getRootFontSize(); // Gerard: Padding should scale with font size
|
|
59
|
+
let sumWidth = this.open.width + this.expression.width + this.closed.width;
|
|
60
|
+
let maxHeight = Math.max(this.expression.height, this.closed.height, this.open.height);
|
|
61
|
+
|
|
62
|
+
this.setWidthAndHeight(sumWidth, maxHeight + padding);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Updates the layout of the parenthesis node and its children
|
|
67
|
+
* @override
|
|
68
|
+
*/
|
|
69
|
+
updateLayout() {
|
|
70
|
+
let xCurrent = 0;
|
|
71
|
+
|
|
72
|
+
// For proper mathematical typesetting, parentheses should be centered
|
|
73
|
+
// relative to the baseline of the mathematical content, properly accounting
|
|
74
|
+
// for power nodes and their baseline positioning
|
|
75
|
+
|
|
76
|
+
// First, position the expression based on its alignment baseline
|
|
77
|
+
this.expression.updateLayout();
|
|
78
|
+
const expressionBaseline = this.expression.getAlignmentBaseline();
|
|
79
|
+
const totalBaseline = this.getAlignmentBaseline();
|
|
80
|
+
const expressionY = totalBaseline - expressionBaseline;
|
|
81
|
+
this.expression.setPosition(this.open.width, expressionY);
|
|
82
|
+
|
|
83
|
+
// Position parentheses centered around the mathematical baseline
|
|
84
|
+
// This ensures proper centering for power nodes where the baseline
|
|
85
|
+
// represents the position of the base (e.g., 'x' in 'x²')
|
|
86
|
+
const mathematicalBaseline = expressionY + expressionBaseline;
|
|
87
|
+
const parenY = mathematicalBaseline - (this.open.height / 2);
|
|
88
|
+
|
|
89
|
+
this.open.updateLayout();
|
|
90
|
+
this.open.setPosition(0, parenY);
|
|
91
|
+
|
|
92
|
+
this.closed.updateLayout();
|
|
93
|
+
this.closed.setPosition(this.open.width + this.expression.width, parenY);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* For parenthesis nodes, the alignment baseline is the baseline of the inner expression.
|
|
98
|
+
* @override
|
|
99
|
+
* @returns {number} The y-coordinate for alignment.
|
|
100
|
+
*/
|
|
101
|
+
getAlignmentBaseline() {
|
|
102
|
+
// The baseline is the y-position of the expression node plus its own baseline.
|
|
103
|
+
const childY = (this.height - this.expression.height) / 2;
|
|
104
|
+
return childY + this.expression.getAlignmentBaseline();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
clone() {
|
|
108
|
+
// Create a new node. The constructor will add a backRect and temporary children.
|
|
109
|
+
const tempAst = { type: 'ParenthesisNode', content: {type: 'ConstantNode', value: 1} };
|
|
110
|
+
const clone = new omdParenthesisNode(tempAst);
|
|
111
|
+
|
|
112
|
+
// Keep the backRect, but get rid of the temporary children.
|
|
113
|
+
const backRect = clone.backRect;
|
|
114
|
+
clone.removeAllChildren();
|
|
115
|
+
clone.addChild(backRect);
|
|
116
|
+
|
|
117
|
+
// Manually clone the real children to ensure the entire tree has correct provenance.
|
|
118
|
+
clone.open = this.open.clone();
|
|
119
|
+
clone.addChild(clone.open);
|
|
120
|
+
|
|
121
|
+
clone.expression = this.expression.clone();
|
|
122
|
+
clone.addChild(clone.expression);
|
|
123
|
+
|
|
124
|
+
clone.closed = this.closed.clone();
|
|
125
|
+
clone.addChild(clone.closed);
|
|
126
|
+
|
|
127
|
+
// Rebuild the argument list
|
|
128
|
+
clone.argumentNodeList = { expression: clone.expression };
|
|
129
|
+
|
|
130
|
+
// Regenerate AST data from the cloned structure instead of copying it
|
|
131
|
+
// This ensures the AST properly reflects the cloned nodes
|
|
132
|
+
clone.astNodeData = clone.toMathJSNode();
|
|
133
|
+
|
|
134
|
+
// The crucial step: link the clone to its origin.
|
|
135
|
+
clone.provenance.push(this.id);
|
|
136
|
+
|
|
137
|
+
return clone;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A parenthesis node is constant if its inner expression is constant.
|
|
142
|
+
* @returns {boolean}
|
|
143
|
+
*/
|
|
144
|
+
isConstant() {
|
|
145
|
+
return this.expression ? this.expression.isConstant() : false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The value of a parenthesis node is the value of its inner expression.
|
|
150
|
+
* @returns {number}
|
|
151
|
+
*/
|
|
152
|
+
getValue() {
|
|
153
|
+
if (!this.expression) {
|
|
154
|
+
throw new Error("Parenthesis node has no expression from which to get a value.");
|
|
155
|
+
}
|
|
156
|
+
return this.expression.getValue();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Converts the omdParenthesisNode to a math.js AST node.
|
|
161
|
+
* @returns {Object} A math.js-compatible AST node.
|
|
162
|
+
*/
|
|
163
|
+
toMathJSNode() {
|
|
164
|
+
const astNode = {
|
|
165
|
+
type: 'ParenthesisNode',
|
|
166
|
+
content: this.expression.toMathJSNode(),
|
|
167
|
+
id: this.id,
|
|
168
|
+
provenance: this.provenance
|
|
169
|
+
};
|
|
170
|
+
// Add a clone method to maintain compatibility with math.js's expectations.
|
|
171
|
+
astNode.clone = function() {
|
|
172
|
+
const clonedNode = { ...this };
|
|
173
|
+
if (this.content) {
|
|
174
|
+
clonedNode.content = this.content.clone();
|
|
175
|
+
}
|
|
176
|
+
return clonedNode;
|
|
177
|
+
};
|
|
178
|
+
return astNode;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Converts the parenthesis node to a string.
|
|
183
|
+
* @returns {string} The string representation of the parenthesized expression.
|
|
184
|
+
*/
|
|
185
|
+
toString() {
|
|
186
|
+
const innerExpr = this.expression.toString();
|
|
187
|
+
return `(${innerExpr})`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Evaluate the content within parentheses
|
|
192
|
+
* @param {Object} variables - Variable name to value mapping
|
|
193
|
+
* @returns {number} The evaluated result
|
|
194
|
+
*/
|
|
195
|
+
evaluate(variables = {}) {
|
|
196
|
+
if (!this.expression) {
|
|
197
|
+
throw new Error("Parenthesis node has no expression to evaluate");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Evaluate the inner expression
|
|
201
|
+
if (this.expression.evaluate) {
|
|
202
|
+
return this.expression.evaluate(variables);
|
|
203
|
+
} else if (this.expression.isConstant()) {
|
|
204
|
+
return this.expression.getValue();
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error("Cannot evaluate parenthesis content");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if parentheses are necessary based on context
|
|
212
|
+
* @returns {boolean} Whether parentheses affect evaluation order
|
|
213
|
+
*/
|
|
214
|
+
isNecessary() {
|
|
215
|
+
// If there's no parent, parentheses at the top level are not necessary
|
|
216
|
+
if (!this.parent) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for single constants or variables - parentheses not needed
|
|
221
|
+
if (this.expression.type === 'omdConstantNode' ||
|
|
222
|
+
this.expression.type === 'omdVariableNode') {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check parent context
|
|
227
|
+
const parent = this.parent;
|
|
228
|
+
|
|
229
|
+
// If parent is a function, parentheses are part of function syntax
|
|
230
|
+
if (parent.type === 'omdFunctionNode') {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// If parent is a power node and this is the base or exponent
|
|
235
|
+
if (parent.type === 'omdPowerNode') {
|
|
236
|
+
// Check if we're the base
|
|
237
|
+
if (parent.base === this) {
|
|
238
|
+
// Base needs parentheses if it's a complex expression
|
|
239
|
+
return this.expression.type === 'omdBinaryExpressionNode';
|
|
240
|
+
}
|
|
241
|
+
// Check if we're the exponent
|
|
242
|
+
if (parent.exponent === this) {
|
|
243
|
+
// Exponent needs parentheses if it's not a simple value
|
|
244
|
+
return !(this.expression.type === 'omdConstantNode' ||
|
|
245
|
+
this.expression.type === 'omdVariableNode');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// If parent is a binary expression, check operator precedence
|
|
250
|
+
if (parent.type === 'omdBinaryExpressionNode') {
|
|
251
|
+
// If the inner expression is also a binary expression,
|
|
252
|
+
// parentheses might be needed based on precedence
|
|
253
|
+
if (this.expression.type === 'omdBinaryExpressionNode') {
|
|
254
|
+
return true; // Conservative approach - keep parentheses
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Default: parentheses are not necessary
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a parenthesis node from a string
|
|
264
|
+
* @param {string} expressionString - Expression with parentheses
|
|
265
|
+
* @returns {omdParenthesisNode} The created parenthesis node
|
|
266
|
+
* @static
|
|
267
|
+
*/
|
|
268
|
+
static fromString(expressionString) {
|
|
269
|
+
if (!window.math) {
|
|
270
|
+
throw new Error("Math.js is required for parsing expressions");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const trimmed = expressionString.trim();
|
|
274
|
+
|
|
275
|
+
// Ensure the string starts and ends with parentheses
|
|
276
|
+
if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
|
|
277
|
+
throw new Error("Expression must be enclosed in parentheses");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const ast = window.math.parse(trimmed);
|
|
282
|
+
|
|
283
|
+
// Verify it's a parenthesis node
|
|
284
|
+
if (ast.type !== 'ParenthesisNode') {
|
|
285
|
+
throw new Error("Parsed expression is not a parenthesis node");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return new omdParenthesisNode(ast);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
throw new Error(`Failed to parse parenthesis expression: ${error.message}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
293
|
}
|