@teachinglab/omd 0.7.6 → 0.7.8
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/canvas/tools/SelectTool.js +6 -5
- package/omd/nodes/omdEquationNode.js +19 -6
- package/package.json +1 -1
- package/src/omdCoordinatePlane.js +23 -16
- package/src/omdUtils.js +37 -11
|
@@ -127,6 +127,11 @@ export class SelectTool extends Tool {
|
|
|
127
127
|
this.draggedOMDElement = omdElement; // Primary drag target
|
|
128
128
|
this.startPoint = { x: event.x, y: event.y };
|
|
129
129
|
|
|
130
|
+
// Show resize handles if this is the only selected element
|
|
131
|
+
if (this.selectedOMDElements.size === 1) {
|
|
132
|
+
this.resizeHandleManager.selectElement(omdElement);
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
if (this.canvas.eventManager) {
|
|
131
136
|
this.canvas.eventManager.isDrawing = true;
|
|
132
137
|
}
|
|
@@ -903,11 +908,7 @@ export class SelectTool extends Tool {
|
|
|
903
908
|
}
|
|
904
909
|
|
|
905
910
|
// Update resize handles
|
|
906
|
-
|
|
907
|
-
this.resizeHandleManager.selectElement(this.selectedOMDElements.values().next().value);
|
|
908
|
-
} else {
|
|
909
|
-
this.resizeHandleManager.clearSelection();
|
|
910
|
-
}
|
|
911
|
+
this.resizeHandleManager.clearSelection();
|
|
911
912
|
|
|
912
913
|
this._updateSegmentSelectionVisuals();
|
|
913
914
|
}
|
|
@@ -1064,16 +1064,29 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1064
1064
|
const rightExpr = this._normalizeExpressionString(this.getRight().toString());
|
|
1065
1065
|
|
|
1066
1066
|
let graphEquations = [];
|
|
1067
|
+
|
|
1068
|
+
// Helper to check if expression is just 'y' (case-insensitive)
|
|
1069
|
+
const isY = (expr) => expr.trim().toLowerCase() === 'y';
|
|
1070
|
+
|
|
1067
1071
|
if (options.side === 'left') {
|
|
1068
|
-
|
|
1072
|
+
if (!isY(leftExpr)) {
|
|
1073
|
+
graphEquations.push({ equation: `y = ${leftExpr}`, domain: { min: options.xMin, max: options.xMax }, color: 'blue', strokeWidth: 2 });
|
|
1074
|
+
}
|
|
1069
1075
|
} else if (options.side === 'right') {
|
|
1070
|
-
|
|
1076
|
+
if (!isY(rightExpr)) {
|
|
1077
|
+
graphEquations.push({ equation: `y = ${rightExpr}`, domain: { min: options.xMin, max: options.xMax }, color: 'red', strokeWidth: 2 });
|
|
1078
|
+
}
|
|
1071
1079
|
} else {
|
|
1072
1080
|
// both: plot left and right as two functions; intersection corresponds to equality
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
{ equation: `y = ${
|
|
1076
|
-
|
|
1081
|
+
// Skip 'y' sides to avoid plotting "y=y"
|
|
1082
|
+
if (!isY(leftExpr)) {
|
|
1083
|
+
graphEquations.push({ equation: `y = ${leftExpr}`, domain: { min: options.xMin, max: options.xMax }, color: 'blue', strokeWidth: 2 });
|
|
1084
|
+
}
|
|
1085
|
+
if (!isY(rightExpr)) {
|
|
1086
|
+
// If we skipped left because it was y, use blue for right side (primary function color)
|
|
1087
|
+
const color = isY(leftExpr) ? 'blue' : 'red';
|
|
1088
|
+
graphEquations.push({ equation: `y = ${rightExpr}`, domain: { min: options.xMin, max: options.xMax }, color: color, strokeWidth: 2 });
|
|
1089
|
+
}
|
|
1077
1090
|
}
|
|
1078
1091
|
|
|
1079
1092
|
return {
|
package/package.json
CHANGED
|
@@ -363,25 +363,34 @@ export class omdCoordinatePlane extends jsvgGroup {
|
|
|
363
363
|
|
|
364
364
|
graphMultipleFunctions(holder) {
|
|
365
365
|
for (const functionConfig of this.graphEquations) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
366
|
+
try {
|
|
367
|
+
const path = new jsvgPath();
|
|
368
|
+
path.setStrokeColor(this.getAllowedColor(functionConfig.color));
|
|
369
|
+
path.setStrokeWidth(functionConfig.strokeWidth);
|
|
370
|
+
this.graphFunctionWithDomain(path, functionConfig.equation, functionConfig.domain);
|
|
371
|
+
holder.addChild(path);
|
|
372
|
+
|
|
373
|
+
if (functionConfig.label) {
|
|
374
|
+
this.addFunctionLabel(holder, functionConfig);
|
|
375
|
+
}
|
|
376
|
+
} catch (e) {
|
|
377
|
+
console.warn(`Failed to graph equation: ${functionConfig.equation}`, e);
|
|
374
378
|
}
|
|
375
379
|
}
|
|
376
380
|
}
|
|
377
381
|
|
|
382
|
+
_extractExpression(equationString) {
|
|
383
|
+
let expression = String(equationString).trim();
|
|
384
|
+
// Remove "y=" from start or "=y" from end, case insensitive, handling spaces
|
|
385
|
+
// This converts "y = 3x+2" or "3x+2 = y" into "3x+2"
|
|
386
|
+
expression = expression.replace(/^\s*y\s*=\s*/i, '').replace(/\s*=\s*y\s*$/i, '');
|
|
387
|
+
return expression;
|
|
388
|
+
}
|
|
389
|
+
|
|
378
390
|
graphFunctionWithDomain(pathObject, functionString, domain) {
|
|
379
391
|
pathObject.clearPoints();
|
|
380
392
|
|
|
381
|
-
|
|
382
|
-
if (expression.toLowerCase().startsWith("y=")) {
|
|
383
|
-
expression = expression.substring(2).trim();
|
|
384
|
-
}
|
|
393
|
+
const expression = this._extractExpression(functionString);
|
|
385
394
|
|
|
386
395
|
const compiledExpression = math.compile(expression);
|
|
387
396
|
|
|
@@ -417,10 +426,8 @@ export class omdCoordinatePlane extends jsvgGroup {
|
|
|
417
426
|
: this.xMin + (this.xMax - this.xMin) * 0.1;
|
|
418
427
|
|
|
419
428
|
let anchorY = this.yMin + (this.yMax - this.yMin) * 0.1;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
equationBody = equationBody.substring(2).trim();
|
|
423
|
-
}
|
|
429
|
+
const equationBody = this._extractExpression(functionConfig.equation);
|
|
430
|
+
|
|
424
431
|
const compiled = math.compile(equationBody);
|
|
425
432
|
const yVal = compiled.evaluate({ x: anchorX });
|
|
426
433
|
anchorY = yVal;
|
package/src/omdUtils.js
CHANGED
|
@@ -22,12 +22,32 @@ export function parseExpressionString(str) {
|
|
|
22
22
|
// term or number/variable
|
|
23
23
|
const m = tok.match(/^([+-]?)(\d*\.?\d*)([a-zA-Z]?)(?:\^(\d+))?$/);
|
|
24
24
|
if (m) {
|
|
25
|
-
const
|
|
25
|
+
const signStr = m[1];
|
|
26
|
+
let sign = signStr === '-' ? -1 : 1;
|
|
26
27
|
const coefRaw = m[2];
|
|
27
28
|
const varChar = m[3] || '';
|
|
28
29
|
const exp = m[4] ? Number(m[4]) : 1;
|
|
29
30
|
let coef = 1;
|
|
30
31
|
if (coefRaw && coefRaw.length > 0) coef = Number(coefRaw);
|
|
32
|
+
|
|
33
|
+
// Logic to split sign into operator if needed
|
|
34
|
+
// If we have a sign (+ or -), and we are NOT at the start and NOT following an operator,
|
|
35
|
+
// we should split it into an operator and a positive term.
|
|
36
|
+
let splitSign = false;
|
|
37
|
+
if (signStr) {
|
|
38
|
+
const isStart = out.length === 0;
|
|
39
|
+
const prevIsOperator = !isStart && out[out.length-1].omdType === 'operator';
|
|
40
|
+
|
|
41
|
+
if (!isStart && !prevIsOperator) {
|
|
42
|
+
splitSign = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (splitSign) {
|
|
47
|
+
out.push({ omdType: 'operator', operator: signStr });
|
|
48
|
+
sign = 1; // Term becomes positive
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
if (!varChar && coefRaw) {
|
|
32
52
|
// pure number
|
|
33
53
|
out.push({ omdType: 'number', value: sign * coef });
|
|
@@ -41,21 +61,27 @@ export function parseExpressionString(str) {
|
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
// If first token is an operator like '+' or '-', and followed by a term/number, fold it
|
|
64
|
+
// ONLY if it is a unary operator (at start or after another operator)
|
|
44
65
|
const folded = [];
|
|
45
66
|
for (let i = 0; i < out.length; i++) {
|
|
46
67
|
const t = out[i];
|
|
47
68
|
if (t.omdType === 'operator' && (t.operator === '+' || t.operator === '-')) {
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
const isStart = folded.length === 0;
|
|
70
|
+
const prevIsOperator = !isStart && folded[folded.length-1].omdType === 'operator';
|
|
71
|
+
|
|
72
|
+
// Only fold if it's unary
|
|
73
|
+
if (isStart || prevIsOperator) {
|
|
74
|
+
const next = out[i+1];
|
|
75
|
+
if (next && (next.omdType === 'term' || next.omdType === 'number')) {
|
|
76
|
+
if (next.omdType === 'number') {
|
|
77
|
+
next.value = (t.operator === '-') ? -next.value : next.value;
|
|
78
|
+
} else if (next.omdType === 'term') {
|
|
79
|
+
next.coefficient = (t.operator === '-') ? -next.coefficient : next.coefficient;
|
|
80
|
+
}
|
|
81
|
+
i++; // skip next because we've merged
|
|
82
|
+
folded.push(next);
|
|
83
|
+
continue;
|
|
55
84
|
}
|
|
56
|
-
i++; // skip next because we've merged
|
|
57
|
-
folded.push(next);
|
|
58
|
-
continue;
|
|
59
85
|
}
|
|
60
86
|
}
|
|
61
87
|
folded.push(t);
|