btm-expressions
Version:
BTM (bowtie-math) is a math object model to enable parsing of mathematical expressions into a tree structure that can be manipulated, evaluated, and compared.
1,208 lines (1,041 loc) • 50.5 kB
JavaScript
/*!
* BTM JavaScript Library v@VERSION
* https://github.com/dbrianwalton/BTM
*
* Copyright D. Brian Walton
* Released under the MIT license (https://opensource.org/licenses/MIT)
*
* Date: @DATE
*/
import { BTM, exprType, exprValue } from "./BTM_root.js";
import { newRule, findMatchRules } from "./reductions.js";
var myBTM = new BTM('1357');
myBTM.setReductionRules();
/* The autoIdentityList set of rules is used for drag/drop of input elements */
var autoIdentityList = [];
var ruleCounter = 0;
newRule(myBTM, autoIdentityList, 'a+0==a', 'Additive Identity', true, true);
newRule(myBTM, autoIdentityList, '0+a==a', 'Additive Identity', true, true);
newRule(myBTM, autoIdentityList, 'a*1==a', 'Multiplicative Identity', true, true);
newRule(myBTM, autoIdentityList, '1*a==a', 'Multiplicative Identity', true, true);
newRule(myBTM, autoIdentityList, 'a*0==0', 'Multiply by Zero', true, true);
newRule(myBTM, autoIdentityList, '0*a==0', 'Multiply by Zero', true, true);
newRule(myBTM, autoIdentityList, 'a-a==0', 'Additive Inverses Cancel', true, true);
newRule(myBTM, autoIdentityList, 'a+-a==0', 'Additive Inverses Cancel', true, true);
newRule(myBTM, autoIdentityList, '-a+a==0', 'Additive Inverses Cancel', true, true);
newRule(myBTM, autoIdentityList, 'a/a==1', 'Multiplicative Inverses Cancel', true, true);
newRule(myBTM, autoIdentityList, 'a*/a==1', 'Multiplicative Inverses Cancel', true, true);
newRule(myBTM, autoIdentityList, '/a*a==1', 'Multiplicative Inverses Cancel', true, true);
// Inverse Operations.
newRule(myBTM, autoIdentityList, '-(a+b)==-a+-b', "Additive Inverse", true, true);
newRule(myBTM, autoIdentityList, '-(a-b)==b-a', "Additive Inverse", true, true);
newRule(myBTM, autoIdentityList, '-(-a)==a', "Additive Inverse's Inverse", true, true);
newRule(myBTM, autoIdentityList, '-a==-1*a', "Additive Inverse's Inverse", true);
newRule(myBTM, autoIdentityList, '/(/a)==a', "Multiplicative Inverse's Inverse", true, true);
newRule(myBTM, autoIdentityList, 'ln(exp(a))==a', 'Logarithm of Exponential', true, true);
newRule(myBTM, autoIdentityList, 'exp(ln(a))==a', 'Exponential of Logarithm', true, true);
newRule(myBTM, autoIdentityList, '(sqrt(a))^2==a', 'Square of a Square Root', true, true);
newRule(myBTM, autoIdentityList, 'sqrt(a)*sqrt(a)==a', 'Square of a Square Root', true, true);
newRule(myBTM, autoIdentityList, 'sqrt(a^2)==abs(a)', 'Square Root of a Square', true, true);
newRule(myBTM, autoIdentityList, '(/a)*(/b)==/(a*b)', 'Multiplicative Inverse', true);
newRule(myBTM, autoIdentityList, 'a*a==a^2', 'Definition of Square', true);
newRule(myBTM, autoIdentityList, 'a^b*a^c==a^(b+c)', 'Algebra of Powers', true);
newRule(myBTM, autoIdentityList, 'a^b/a^c==a^(b-c)', 'Algebra of Powers', true);
newRule(myBTM, autoIdentityList, 'a^b*/(a^c)==a^(b-c)', 'Algebra of Powers', true,true);
newRule(myBTM, autoIdentityList, '/a^b*a^c==a^(c-b)', 'Algebra of Powers', true,true);
newRule(myBTM, autoIdentityList, 'a*a^c==a^(c+1)', 'Algebra of Powers', true);
newRule(myBTM, autoIdentityList, 'a^c*a==a^(c+1)', 'Algebra of Powers', true,true);
newRule(myBTM, autoIdentityList, 'a^c/a==a^(c-1)', 'Algebra of Powers', true);
newRule(myBTM, autoIdentityList, '/a*a^c==a^(c-1)', 'Algebra of Powers', true,true);
newRule(myBTM, autoIdentityList, 'a^c*/a==a^(c-1)', 'Algebra of Powers', true,true);
newRule(myBTM, autoIdentityList, '(a*b)/(a*c)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, autoIdentityList, '(a*b)/(c*a)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, autoIdentityList, '(b*a)/(a*c)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, autoIdentityList, '(b*a)/(c*a)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, autoIdentityList, '(a/b)/c==a/(b*c)', 'Division', true,true);
newRule(myBTM, autoIdentityList, '(a+b)*(c+d)==a*c+a*d+b*c+b*d', 'FOIL', true);
newRule(myBTM, autoIdentityList, '(a-b)*(c+d)==a*c+a*d+-(b*c)+-(b*d)', 'FOIL', true);
newRule(myBTM, autoIdentityList, '(a-b)*(c-d)==a*c+-(a*d)+-(b*c)+b*d', 'FOIL', true);
newRule(myBTM, autoIdentityList, 'a*b+a*c==a*(b+c)', 'Common Factor', true);
newRule(myBTM, autoIdentityList, 'a*b+c*a==a*(b+c)', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'b*a+a*c==a*(b+c)', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'b*a+c*a==a*(b+c)', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'a*b-a*c==a*(b-c)', 'Common Factor', true);
newRule(myBTM, autoIdentityList, 'a*b-c*a==a*(b-c)', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'b*a-a*c==a*(b-c)', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'b*a-c*a==a*(b-c)', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'b*a+a==(b+1)*a', 'Common Factor', true);
newRule(myBTM, autoIdentityList, 'a*b+a==(b+1)*a', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'a+b*a==(b+1)*a', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'a+a*b==(b+1)*a', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, 'a*b-a==(b-1)*a', 'Common Factor', true);
newRule(myBTM, autoIdentityList, 'b*a-a==(b-1)*a', 'Common Factor', true,true);
newRule(myBTM, autoIdentityList, '-a+a*b==(b-1)*a', 'Common Factor', true, true);
newRule(myBTM, autoIdentityList, '-a+b*a==(b-1)*a', 'Common Factor', true, true);
// Function Identities.
newRule(myBTM, autoIdentityList, 'exp(a+b)==exp(a)*exp(b)', 'Exponential of Sum', true);
newRule(myBTM, autoIdentityList, 'exp(a-b)==exp(a)/exp(b)', 'Exponential of Difference', true);
newRule(myBTM, autoIdentityList, 'exp(a)^b==exp(a*b)', 'Exponential of Product', true, true);
newRule(myBTM, autoIdentityList, 'ln(a*b)==ln(a)+ln(b)', 'Logarithm of a Product', true);
newRule(myBTM, autoIdentityList, 'ln(/a)==-ln(a)', 'Logarithm of a Quotient', true);
newRule(myBTM, autoIdentityList, 'ln(a/b)==ln(a)-ln(b)', 'Logarithm of a Quotient', true);
newRule(myBTM, autoIdentityList, 'ln(a^b)==b*ln(a)', 'Logarithm of a Power', true);
newRule(myBTM, autoIdentityList, 'sin(a+b)==sin(a)*cos(b)+cos(a)*sin(b)', 'Sine of a Sum', true);
newRule(myBTM, autoIdentityList, 'sin(a-b)==sin(a)*cos(b)-cos(a)*sin(b)', 'Sine of a Difference', true);
newRule(myBTM, autoIdentityList, 'cos(a+b)==cos(a)*cos(b)-sin(a)*sin(b)', 'Cosine of a Sum', true);
newRule(myBTM, autoIdentityList, 'cos(a-b)==cos(a)*cos(b)+sin(a)*sin(b)', 'Cosine of a Difference', true);
/* The ruleList set of rules is used for the matched list that appears */
var ruleList = [];
newRule(myBTM, ruleList, 'a*b=0==a=0$b=0', 'Product is Zero', true);
newRule(myBTM, ruleList, 'a=b==a+c=b+c', 'Equivalent Equation', true);
newRule(myBTM, ruleList, 'a=b==a*c=b*c', 'Equivalent Equation', true);
newRule(myBTM, ruleList, 'a=b==exp(a)=exp(b)', 'Equivalent Equation', true);
newRule(myBTM, ruleList, 'a=b==ln(a)=ln(b)', 'Equivalent Equation', true);
newRule(myBTM, ruleList, 'a+0==a', 'Additive Identity', true, true);
newRule(myBTM, ruleList, '0+a==a', 'Additive Identity', true, true);
newRule(myBTM, ruleList, 'a-a==0', 'Additive Inverses Cancel', true, true);
newRule(myBTM, ruleList, 'a+-a==0', 'Additive Inverses Cancel', true, true);
newRule(myBTM, ruleList, '-a+a==0', 'Additive Inverses Cancel', true, true);
newRule(myBTM, ruleList, 'a*0==0', 'Multiply by Zero', true, true);
newRule(myBTM, ruleList, '0*a==0', 'Multiply by Zero', true, true);
newRule(myBTM, ruleList, 'a*1==a', 'Multiplicative Identity', true, true);
newRule(myBTM, ruleList, '1*a==a', 'Multiplicative Identity', true, true);
newRule(myBTM, ruleList, 'a/1==a', 'Multiplicative Identity', true, true);
newRule(myBTM, ruleList, '1/a==/a', 'Multiplicative Inverse', true);
newRule(myBTM, ruleList, 'a/a==1', 'Multiplicative Inverses Cancel', true, true);
newRule(myBTM, ruleList, 'a*/a==1', 'Multiplicative Inverses Cancel', true, true);
newRule(myBTM, ruleList, '/a*a==1', 'Multiplicative Inverses Cancel', true, true);
newRule(myBTM, ruleList, '(a*b)/(a*c)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, ruleList, '(a*b)/(c*a)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, ruleList, '(b*a)/(a*c)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, ruleList, '(b*a)/(c*a)==b/c', 'Cancellation in Division', true,true);
newRule(myBTM, ruleList, 'a*(b+c)==a*b+a*c', 'Distribution', true, true);
newRule(myBTM, ruleList, '(b+c)*a==a*b+a*c', 'Distribution', true, true);
newRule(myBTM, ruleList, 'a*(b-c)==a*b-a*c', 'Distribution', true, true);
newRule(myBTM, ruleList, '(b-c)*a==a*b-a*c', 'Distribution', true, true);
newRule(myBTM, ruleList, 'a*b+a*c==a*(b+c)', 'Common Factor', true, true);
newRule(myBTM, ruleList, 'a*b+c*a==a*(b+c)', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'b*a+a*c==a*(b+c)', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'b*a+c*a==a*(b+c)', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'a*b-a*c==a*(b-c)', 'Common Factor', true);
newRule(myBTM, ruleList, 'a*b-c*a==a*(b-c)', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'b*a-a*c==a*(b-c)', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'b*a-c*a==a*(b-c)', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'b*a+a==(b+1)*a', 'Common Factor', true);
newRule(myBTM, ruleList, 'a*b+a==(b+1)*a', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'a+b*a==(b+1)*a', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'a+a*b==(b+1)*a', 'Common Factor', true,true);
newRule(myBTM, ruleList, 'a*b-a==(b-1)*a', 'Common Factor', true);
newRule(myBTM, ruleList, 'b*a-a==(b-1)*a', 'Common Factor', true,true);
newRule(myBTM, ruleList, '-a+a*b==(b-1)*a', 'Common Factor', true, true);
newRule(myBTM, ruleList, '-a+b*a==(b-1)*a', 'Common Factor', true, true);
newRule(myBTM, ruleList, 'a+a==2*a', 'Common Factor', true, true);
newRule(myBTM, ruleList, '(a+b)/c==a/c+b/c', 'Distribution', true);
newRule(myBTM, ruleList, '(a+b)*(c+d)==a*c+a*d+b*c+b*d', 'FOIL', true);
newRule(myBTM, ruleList, '(a-b)*(c+d)==a*c+a*d-b*c-b*d', 'FOIL', true);
newRule(myBTM, ruleList, '(a-b)*(c-d)==a*c-a*d-b*c+b*d', 'FOIL', true);
newRule(myBTM, ruleList, '(a+b)^2==a^2+2*a*b+b^2', 'Expand Quadratic', true, true);
newRule(myBTM, ruleList, '(a+b)^3==a^3+3*a^2*b+3*a*b^2+b^3', 'Expand Cubic', true, true);
newRule(myBTM, ruleList, '(a+b)^4==a^4+4*a^3*b+6*a^2*b^2+4*a*b^3+b^4', 'Expand Quartic', true, true);
newRule(myBTM, ruleList, 'a-b==a+-b', "Additive Inverse", true);
newRule(myBTM, ruleList, '-(a+b)==-a+-b', "Additive Inverse", true);
newRule(myBTM, ruleList, '-(a-b)==b-a', "Additive Inverse", true);
newRule(myBTM, ruleList, '-(a-b)==-a+b', "Additive Inverse", true,true);
newRule(myBTM, ruleList, '-(-a)==a', "Additive Inverse's Inverse", true, true);
newRule(myBTM, ruleList, '-a*b==-(a*b)', "Additive Inverse", true);
newRule(myBTM, ruleList, 'a*-b==-(a*b)', "Additive Inverse", true, true);
newRule(myBTM, ruleList, 'a/b==a*/b', "Multiplicative Inverse", true);
newRule(myBTM, ruleList, '/b*a==a/b', "Multiplicative Inverse", true, true);
newRule(myBTM, ruleList, '/(/a)==a', "Multiplicative Inverse's Inverse", true, true);
newRule(myBTM, ruleList, '/(a*b)==(/a)*(/b)', "Multiplicative Inverse", true);
newRule(myBTM, ruleList, '(a/b)*(c/d)==(a*c)/(b*d)', 'Multiplication of Quotients', true);
newRule(myBTM, ruleList, 'a/(b*c)==(a/b)*(1/c)', 'Multiplication of Quotients', true);
newRule(myBTM, ruleList, '(1/c)*(a/b)==a/(b*c)', 'Multiplication of Quotients', true, true);
newRule(myBTM, ruleList, 'a/(b*c)==(1/b)*(a/c)', 'Multiplication of Quotients', true, true);
newRule(myBTM, ruleList, 'a*(b/c)==(a*b)/c', 'Multiplication of Quotients', true);
newRule(myBTM, ruleList, '(a/c)*b==(a*b)/c', 'Multiplication of Quotients', true);
newRule(myBTM, ruleList, '(a/b)/c==a/(b*c)', 'Multiplication of Quotients', true);
newRule(myBTM, ruleList, '(1/b)+(1/d)==(d+b)/(b*d)', 'Common Denominator', true);
newRule(myBTM, ruleList, '(a/b)+(c/d)==(a*d+b*c)/(b*d)', 'Common Denominator', true);
newRule(myBTM, ruleList, '(1/b)-(1/d)==(d-b)/(b*d)', 'Common Denominator', true);
newRule(myBTM, ruleList, '(a/b)-(c/d)==(a*d-b*c)/(b*d)', 'Common Denominator', true);
newRule(myBTM, ruleList, '-1*a==-a', 'Additive Inverse', true);
newRule(myBTM, ruleList, 'a^0==1', 'Algebra of Powers', true, true);
newRule(myBTM, ruleList, 'a^1==a', 'Algebra of Powers', true, true);
newRule(myBTM, ruleList, 'a^-1==/a', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'sqrt(a)^2==a', 'Square Root of a Square', true, true);
newRule(myBTM, ruleList, 'sqrt(a)*sqrt(a)==a', 'Square of a Square Root', true, true);
newRule(myBTM, ruleList, 'sqrt(a^2)==abs(a)', 'Square Root of a Square', true);
newRule(myBTM, ruleList, 'sqrt(a)==a^(1/2)', 'Square Root as Power', true);
newRule(myBTM, ruleList, 'sqrt(a*b)==sqrt(a)*sqrt(b)', 'Distribute Power over Multiplication', true);
// Powers
newRule(myBTM, ruleList, 'a^b*a^c==a^(b+c)', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a*a==a^2', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a^n==a*a^(n-1)', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a*a^n==a^(n+1)', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a^n*a==a^(n+1)', 'Algebra of Powers', true, true);
newRule(myBTM, ruleList, 'a^c*b^c==(a*b)^c', 'Algebra of Powers', true);
newRule(myBTM, ruleList, '/(a^b)==a^(-b)', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a^b==/(a^(-b))', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a^b/a^c==a^(b-c)', 'Algebra of Powers', true);
newRule(myBTM, ruleList, 'a^c/b^c==(a/b)^c', 'Algebra of Powers', true);
newRule(myBTM, ruleList, '(a^b)^c==a^(b*c)', 'Algebra of Powers', true);
// Functions
newRule(myBTM, ruleList, 'e^a==exp(a)', 'Exponential Definition', true);
newRule(myBTM, ruleList, 'exp(ln(a))==a', 'Exponential of Logarithm', true);
newRule(myBTM, ruleList, 'ln(exp(a))==a', 'Logarithm of Exponential', true);
newRule(myBTM, ruleList, 'exp(a+b)==exp(a)*exp(b)', 'Exponential of Sum', true);
newRule(myBTM, ruleList, 'exp(-a)==/exp(a)', 'Exponential of Difference', true);
newRule(myBTM, ruleList, 'exp(a-b)==exp(a)/exp(b)', 'Exponential of Difference', true);
newRule(myBTM, ruleList, 'exp(a)^b==exp(a*b)', 'Exponential of Product', true);
newRule(myBTM, ruleList, 'exp(a*b)==exp(b)^a', 'Exponential of Product', true, true);
newRule(myBTM, ruleList, 'ln(a*b)==ln(a)+ln(b)', 'Logarithm of a Product', true);
newRule(myBTM, ruleList, 'ln(/a)==-ln(a)', 'Logarithm of a Quotient', true);
newRule(myBTM, ruleList, 'ln(a/b)==ln(a)-ln(b)', 'Logarithm of a Quotient', true);
newRule(myBTM, ruleList, 'ln(a^b)==b*ln(a)', 'Logarithm of a Power', true);
newRule(myBTM, ruleList, 'sin(a+b)==sin(a)*cos(b)+cos(a)*sin(b)', 'Sine of a Sum', true);
newRule(myBTM, ruleList, 'sin(a-b)==sin(a)*cos(b)-cos(a)*sin(b)', 'Sine of a Difference', true);
newRule(myBTM, ruleList, 'cos(a+b)==cos(a)*cos(b)-sin(a)*sin(b)', 'Cosine of a Sum', true);
newRule(myBTM, ruleList, 'cos(a-b)==cos(a)*cos(b)+sin(a)*sin(b)', 'Cosine of a Difference', true);
// Rules that introduce new terms
newRule(myBTM, ruleList, 'a==a+b+-b', 'Additive Identity', true, true);
newRule(myBTM, ruleList, 'a==a*(b/b)', 'Multiply by One/Cancellation', true, true);
newRule(myBTM, ruleList, 'a==b*(a/b)', 'Factor out term', true, true);
newRule(myBTM, ruleList, 'a^n==a^b*a^(n-b)', 'Factor out Powers', true,true);
newRule(myBTM, ruleList, 'a==-(-a)', "Additive Inverse's Inverse", true, true);
newRule(myBTM, ruleList, 'a==/(/a)', "Multiplicative Inverse's Inverse", true, true);
newRule(myBTM, ruleList, 'a==1*a', 'Multiplicative Identity', true, true);
newRule(myBTM, ruleList, 'a==a/1', 'Multiplicative Identity', true, true);
newRule(myBTM, ruleList, '1==a/a', 'Inverses Cancel', true, true);
newRule(myBTM, ruleList, 'a==a^1', 'Algebra of Powers', true, true);
var algebWrist = {
theExpr : null,
selectExpr : null,
identityList : undefined,
currentTree : {},
// Used for dragging elements in the tree area.
dragging : {
curAction : null,
lastAction : null,
},
// Display control parameters
inputGap : 10,
inputCenter : 45,
currentCenter : 120,
parentCenter : 180,
// Interface control parameters
clickDuration : 300, // ms
dblClickTime : 300, // ms
clickError : 12, // total pixels
};
window.reset = function() {
var entryField = document.getElementById('expression_entry');
var entryFormula = entryField.value;
entryFormula = entryFormula.replace(/\s/g,'');
algebWrist.theExpr = myBTM.parse(entryFormula, "formula", {});
algebWrist.selectExpr = null;
updateSelect(algebWrist.theExpr);
var history = document.getElementById('history');
history.innerHTML = ""
+ "\\( \\displaystyle " + algebWrist.theExpr.toTeX() + "\\)";
updateVisual();
}
window.addHistory = function() {
var history = document.getElementById('history');
history.innerHTML += "<BR/>";
if (algebWrist.theExpr.valueType == exprValue.bool) {
history.innerHTML += " ⇔ "
} else {
history.innerHTML += " = "
}
history.innerHTML += "\\( \\displaystyle " + algebWrist.theExpr.toTeX() + "\\)";
}
// Building the tree occurs based on a queue in order that the MathJax typesetting
// can occur prior to assigning locations based on size of the output.
// This is the portion that occurs prior to typesetting.
function setupTree() {
var copyExpr, exprTreeTeX;
// Take the expression that has been entered and identify if
// a subexpression is already chosen on which to base the tree.
if (algebWrist.selectExpr === null) {
exprTreeTeX = algebWrist.theExpr.treeToTeX(false);
exprTreeTeX.inputs = [exprTreeTeX.current];
exprTreeTeX.current = "⌖";
} else {
exprTreeTeX = algebWrist.selectExpr.treeToTeX(true);
if (exprTreeTeX.parent === null) {
exprTreeTeX.parent = "⌖";
}
}
// Remove the old elements in preparation for a new subtree to display
var treeArea = document.getElementById('tree_area');
if (typeof algebWrist.currentTree.parentElement !== 'undefined' && algebWrist.currentTree.parentElement !== null) {
treeArea.removeChild(algebWrist.currentTree.parentElement);
}
if (typeof algebWrist.currentTree.activeElement !== 'undefined' && algebWrist.currentTree.activeElement !== null) {
treeArea.removeChild(algebWrist.currentTree.activeElement);
}
if (typeof algebWrist.currentTree.inputElements !== 'undefined' && algebWrist.currentTree.inputElements !== null) {
for (var i in algebWrist.currentTree.inputElements) {
treeArea.removeChild(algebWrist.currentTree.inputElements[i]);
}
}
// Create the new object for the subtree.
algebWrist.currentTree = {};
// The object needs to track the parent (lowest level)
if (exprTreeTeX.parent !== null) {
var newP = document.createElement("div");
newP.setAttribute("id", "parent-element");
$(newP).css("display", "none");
newP.innerHTML = "\\(\\displaystyle " + exprTreeTeX.parent + "\\)";
algebWrist.currentTree.parentElement = newP;
treeArea.appendChild(newP);
} else {
algebWrist.currentTree.parentElement = null;
}
// The object needs to track the current level (middle level)
if (exprTreeTeX.current !== null) {
var newP = document.createElement("div");
newP.setAttribute("id", "active-element");
$(newP).css("display", "none");
newP.innerHTML = "\\(\\displaystyle " + exprTreeTeX.current + "\\)";
algebWrist.currentTree.activeElement = newP;
treeArea.appendChild(newP);
} else {
algebWrist.currentTree.activeElement = null;
}
// The object needs to track the inputs (highest level)
if (exprTreeTeX.inputs !== null) {
algebWrist.currentTree.inputElements = [];
for (i in exprTreeTeX.inputs) {
var newP = document.createElement("div");
newP.setAttribute("id", "input-element-"+i);
$(newP).css("display", "none");
newP.innerHTML = "\\(\\displaystyle " + exprTreeTeX.inputs[i] + "\\)";
algebWrist.currentTree.inputElements[i] = newP;
treeArea.appendChild(newP);
}
} else {
algebWrist.currentTree.inputElements = null;
}
}
// This function is called after the tree expressions have been typeset, so that
// we know how much space they will need. This allows us to position them properly
// over the Canvas element.
function finishTree() {
var myElement;
if (typeof algebWrist.currentTree.parentElement !== 'undefined' && algebWrist.currentTree.parentElement !== null) {
myElement = $("#parent-element");
myElement.css("display", "block");
myElement.addClass("parent-element");
}
if (typeof algebWrist.currentTree.activeElement !== 'undefined' && algebWrist.currentTree.activeElement !== null) {
myElement = $("#active-element");
myElement.css("display", "block");
myElement.addClass("active-element");
}
if (typeof algebWrist.currentTree.inputElements !== 'undefined' && algebWrist.currentTree.inputElements !== null) {
for (var i in algebWrist.currentTree.inputElements) {
myElement = $(algebWrist.currentTree.inputElements[i]);
myElement.css("display", "block");
}
for (var i in algebWrist.currentTree.inputElements) {
myElement = $(algebWrist.currentTree.inputElements[i]);
myElement.addClass("input-formula");
}
}
treePositions();
}
// Compute positions of inputs and report back the current target of dragInput (if present)
function inputPositions(dragInput) {
var dragElement,
dragLeft, dragTop,
dragWidth, dragHeight,
curTarget = null;
if (typeof dragInput !== "undefined" && dragInput >= 0) {
dragElement = algebWrist.currentTree.inputElements[dragInput];
dragLeft = dragElement.offsetLeft;
dragTop = dragElement.offsetTop;
dragWidth = dragElement.clientWidth;
dragHeight = dragElement.clientHeight;
inputBottom = document.getElementById("active-element").offsetTop;
} else {
dragElement = null;
dragInput = -1;
}
var myElement;
var winWidth = document.getElementById("tree_area").clientWidth;
var totalWidth = (algebWrist.currentTree.inputElements.length-1) * algebWrist.inputGap;
for (var i in algebWrist.currentTree.inputElements) {
myElement = algebWrist.currentTree.inputElements[i];
$(myElement).css({
"position" : "absolute",
});
totalWidth += myElement.offsetWidth;
}
var curLeft = Math.floor((winWidth-totalWidth)/2),
curWidth, curHeight, curTop, workLeft,
i,
newI;
// See if the dragged element should be on far left.
if (dragInput >= 0 && dragTop + dragHeight < inputBottom) {
if (dragLeft < curLeft) {
curLeft += dragWidth + algebWrist.inputGap;
curTarget = "position-0";
} else {
curTarget = "position-"+(algebWrist.currentTree.inputElements.length-1);
}
}
newI = 0;
for (i in algebWrist.currentTree.inputElements) {
if (i === dragInput) {
if (dragInput >= 0 && dragTop + dragHeight >= inputBottom) {
curLeft += dragWidth + algebWrist.inputGap;
}
continue;
}
myElement = algebWrist.currentTree.inputElements[i];
curWidth = myElement.offsetWidth;
curHeight = myElement.offsetHeight;
curTop = algebWrist.inputCenter - curHeight/2;
if (dragInput >= 0
&& dragLeft >= curLeft - algebWrist.inputGap
&& dragLeft < curLeft+curWidth
&& dragTop + dragHeight < inputBottom)
{
if (dragLeft < curLeft + curWidth/5) {
workLeft = curLeft + dragWidth + algebWrist.inputGap;
curTarget = "position-"+newI;
} else if (dragLeft < curLeft + 4*curWidth/5) {
workLeft = curLeft + dragWidth / 2;
curTarget = myElement.id;
} else {
workLeft = curLeft;
curTarget = "position-"+(newI+1);
}
$(myElement).css({
"left" : "" + workLeft + "px",
"top" : curTop + "px",
});
curLeft += curWidth + dragWidth + 2 * algebWrist.inputGap;
} else {
$(myElement).css({
"left" : "" + curLeft + "px",
"top" : curTop + "px",
});
curLeft += curWidth + algebWrist.inputGap;
}
newI++;
}
return(curTarget);
}
// Report back the current target of the active operation element when dragged.
function findDropInput(x, y) {
var dragElement,
curTarget = -1,
curTree = algebWrist.currentTree;
dragElement = algebWrist.dragging.curAction.element;
// Check the inputs for a drag onto those.
for (var i in curTree.inputElements) {
if (curTarget < 0 && curTree.inputElements[i] !== dragElement
&& testInElement(x,y,curTree.inputElements[i])) {
curTarget = i;
}
}
return(curTarget);
}
function treePositions() {
var winWidth = document.getElementById("tree_area").clientWidth;
if (typeof algebWrist.currentTree.parentElement !== 'undefined' && algebWrist.currentTree.parentElement !== null) {
var myElement = document.getElementById("parent-element");
$(myElement).css({
"position" : "absolute",
});
var myWidth = myElement.offsetWidth,
myHeight = myElement.offsetHeight;
$(myElement).css({
"left" : "" + Math.floor((winWidth - myWidth)/2) + "px",
"top" : Math.floor(algebWrist.parentCenter - myHeight/2) + "px",
});
}
if (typeof algebWrist.currentTree.activeElement !== 'undefined' && algebWrist.currentTree.activeElement !== null) {
var myElement = document.getElementById("active-element");
$(myElement).css({
"position" : "absolute",
});
var myWidth = myElement.offsetWidth;
$(myElement).css({
"left" : "" + Math.floor((winWidth - myWidth)/2) + "px",
"top" : Math.floor(algebWrist.currentCenter - myHeight/2) + "px",
});
}
if (typeof algebWrist.currentTree.inputElements !== 'undefined' && algebWrist.currentTree.inputElements !== null) {
inputPositions();
}
}
function updateVisual() {
if (typeof MathJax != "undefined") {
var displayArea = document.getElementById('display_area');
displayArea.innerHTML = "\\(\\displaystyle " + algebWrist.theExpr.toTeX(true) + "\\)";
displayArea.className = "math";
setupTree();
MathJax.typesetPromise().then(() => {
finishTree();
});
}
}
function updateIdentities() {
var identityDiv = document.getElementById('identities');
identityDiv.innerHTML = '';
var idListElement = document.createElement("ul");
if (algebWrist.selectExpr !== null) {
var idList = algebWrist.identityList = findMatchRules(ruleList, algebWrist.selectExpr, true);
for (var id in idList) {
var idEntry = document.createElement("li");
if (idList[id].numInputs == 0) {
idEntry.innerHTML = "<!-- Identity " + id + " -->"
+ "<input type='button' value='✓' onclick='doSubstitute("
+ id
+ ")'></input> "
+ '\\(\\displaystyle ' + idList[id].equation + '\\) '
+ " (" + idList[id].name + ")";
} else {
idEntry.innerHTML = "<!-- Identity " + id + " -->"
+ "<span id='identity-toggle-" + id + "'>►</span> "
+ '\\(\\displaystyle ' + idList[id].equation + '\\) '
+ " (" + idList[id].name + ")";
var idInputList = document.createElement("ul");
for (var i=1; i<=idList[id].numInputs; i++) {
var inputEntry = document.createElement("li");
inputEntry.innerHTML = "\\(\\displaystyle \\boxed{...?^{"+i+"}} = \\)"
+ '<input type="text" autocapitalize="off" size="50" id="identity-' + id + '-input-' + i + '">'
+ '</input>';
if (i == idList[id].numInputs) {
inputEntry.innerHTML += " <input type='button' value='✓' onclick='doSubstitute("
+ id + ")'></input>"
}
idInputList.appendChild(inputEntry);
}
idEntry.appendChild(idInputList);
}
idListElement.appendChild(idEntry);
}
}
identityDiv.appendChild(idListElement);
}
function doSubstitute(id) {
var subFormula = algebWrist.identityList[id].subStr;
if (algebWrist.selectExpr !== null) {
var subExpr = myBTM.parseExpression(subFormula),
n = algebWrist.identityList[id].numInputs;
if (n > 0) {
var bindings = {};
for (var i=1; i<=n; i++) {
var input = "input"+i;
bindings[input] = document.getElementById("identity-"+id+"-input-"+i).value;
}
subExpr = subExpr.compose(bindings);
}
if (algebWrist.selectExpr.parent === null) {
algebWrist.theExpr = subExpr;
algebWrist.selectExpr = subExpr;
} else {
algebWrist.selectExpr.parent.inputSubst(algebWrist.selectExpr, subExpr);
}
updateSelect(subExpr, true);
updateVisual();
}
}
function doDoubleClick(action) {
var myID = action.element.getAttribute("id");
if (myID === "parent-element") {
updateSelect(algebWrist.selectExpr.parent);
updateVisual();
} else if (myID.slice(0,-2) === "input-element") {
if (algebWrist.selectExpr === null) {
updateSelect(algebWrist.theExpr);
updateVisual();
} else {
var whichInput = myID.slice(14);
updateSelect(algebWrist.selectExpr.getInput(whichInput));
updateVisual();
}
}
}
function testInElement(x, y, element) {
if (element == null)
return(false);
var clientRect = element.getBoundingClientRect();
return((clientRect.left<x) && (clientRect.right>x)
&& (clientRect.top<y) && (clientRect.bottom>y));
}
function clickAction(actionType, timeStamp, clientX, clientY, element, commuteInput) {
this.action = actionType;
this.x = clientX;
this.y = clientY;
this.time = timeStamp;
this.element = element;
this.commuteInput = commuteInput;
this.lastDropTarget = null;
this.lastX = clientX;
this.lastY = clientY;
}
// Deal with a click down inside of the tree area.
window.doTreeAreaClickDown = function (timeStamp, clientX, clientY) {
// See if it is inside of one of the elements of the current tree
var draggableElement = null,
commuteInput = -1,
doPrevent = true;
if (typeof algebWrist.currentTree.parentElement !== 'undefined' && algebWrist.currentTree.parentElement !== null
&& testInElement(clientX, clientY, algebWrist.currentTree.parentElement)) {
draggableElement = algebWrist.currentTree.parentElement;
} else if (typeof algebWrist.currentTree.activeElement !== 'undefined' && algebWrist.currentTree.activeElement !== null
&& testInElement(clientX, clientY, algebWrist.currentTree.activeElement)) {
draggableElement = algebWrist.currentTree.activeElement;
} else if (typeof algebWrist.currentTree.inputElements !== 'undefined' && algebWrist.currentTree.inputElements.length > 0) {
for (var i in algebWrist.currentTree.inputElements) {
if (testInElement(clientX, clientY, algebWrist.currentTree.inputElements[i])) {
draggableElement = algebWrist.currentTree.inputElements[i];
if (typeof algebWrist.selectExpr !== "undefined" && algebWrist.selectExpr !== null
&& (algebWrist.selectExpr.type == exprType.multiop || algebWrist.selectExpr.type == exprType.binop)
&& algebWrist.selectExpr.isCommutative())
{
commuteInput = i;
}
}
}
}
// Prepare the event tracking system to deal with the element we found.
if (draggableElement !== null) {
algebWrist.dragging.lastAction = algebWrist.dragging.curAction;
algebWrist.dragging.curAction = new clickAction("down", timeStamp, clientX, clientY, draggableElement, commuteInput);
} else {
algebWrist.dragging.lastAction = algebWrist.dragging.curAction;
algebWrist.dragging.curAction = new clickAction("empty", timeStamp, clientX, clientY, null, commuteInput);
doPrevent = false;
}
return(doPrevent);
}
// Deal with a click or touch starting inside of the tree area.
window.doTreeAreaMouseDown = function (event) {
document.getElementById("last-event").innerHTML = "Mouse Down Event";
if (doTreeAreaClickDown(event.timeStamp, event.clientX, event.clientY))
event.preventDefault();
}
window.doTreeAreaTouchStart = function(event) {
document.getElementById("last-event").innerHTML = "Touch Start Event";
var curAction = algebWrist.dragging.curAction;
if (event.touches.length === 1) {
if (doTreeAreaClickDown(event.timeStamp, event.touches[0].clientX, event.touches[0].clientY))
event.preventDefault();
} else {
if (curAction.action === "down" || curAction.action === "dragging") {
doAbortClick();
}
}
}
window.doAbortClick = function() {
var curAction = algebWrist.dragging.curAction;
treePositions();
curAction.action = "cancel";
}
// Deal with movement of click or touch inside of the tree area.
window.doTreeAreaClickMove = function(clientX, clientY) {
var curAction = algebWrist.dragging.curAction,
retValue = false;
if (curAction !== null) {
// See if we need to reclassify as a drag.
if (curAction.action === "down"
&& (Math.abs(clientX - curAction.x) + Math.abs(clientY - curAction.y) > algebWrist.clickError))
{
curAction.action = "dragging";
curAction.startLeft = curAction.element.offsetLeft;
curAction.startTop = curAction.element.offsetTop;
var parentNode = curAction.element.parentElement;
parentNode.appendChild(parentNode.removeChild(curAction.element));
}
// Update the visible location, but bounding position to stay within the canvas.
if (curAction.action === "dragging") {
var newLeft, newTop;
newLeft = this.lastX = curAction.startLeft + clientX - curAction.x;
newTop = this.lastY = curAction.startTop + clientY - curAction.y;
if (newLeft < 0) {
newLeft = 0;
}
if (newLeft + curAction.element.clientWidth > curAction.element.parentNode.clientWidth) {
newLeft = curAction.element.parentNode.clientWidth - curAction.element.clientWidth;
}
if (newTop < 0) {
newTop = 0;
}
if (newTop + curAction.element.clientHeight > curAction.element.parentNode.clientHeight) {
newTop = curAction.element.parentNode.clientHeight - curAction.element.clientHeight;
}
$(curAction.element).css({ "left" : newLeft, "top" : newTop });
// If commutative inputs, then adjust their locations.
if (curAction.commuteInput >= 0) {
curAction.lastDropTarget = inputPositions(curAction.commuteInput);
}
// Otherwise, see what we are hovering over.
else {
var curTree = algebWrist.currentTree;
// Check the parent element
if (curAction.element !== curTree.parentElement
&& testInElement(clientX, clientY, curTree.parentElement))
{
curAction.lastDropTarget = curTree.parentElement.id;
}
// Check the active element
else if (curAction.element !== curTree.activeElement
&& testInElement(clientX, clientY, curTree.activeElement))
{
curAction.lastDropTarget = curTree.activeElement.id;
}
// Check the inputs
else
{
var onInput = findDropInput(clientX, clientY);
if (onInput >= 0) {
curAction.lastDropTarget = curTree.inputElements[onInput].id;
}
}
}
retValue = true;
}
}
if (typeof curAction !== "undefined" && curAction !== null)
document.getElementById("last-event").innerHTML = "Mouse Move Event (" + curAction.lastDropTarget + ")";
return(retValue);
}
// Deal with movement of mouse inside of the tree area.
window.doTreeAreaMouseMove = function(event) {
if (doTreeAreaClickMove(event.clientX, event.clientY))
event.preventDefault();
}
// Deal with movement of touch inside of the tree area.
window.doTreeAreaTouchMove = function(event) {
var curAction = algebWrist.dragging.curAction;
if (event.touches.length > 1
&& curAction !== null && (curAction.action === "down" || curAction.action === "dragging")) {
doAbortClick();
} else if (curAction !== null && (curAction.action === "down" || curAction.action === "dragging")) {
if (doTreeAreaClickMove(event.touches[0].clientX, event.touches[0].clientY))
event.preventDefault();
}
}
// Deal with release of click or touch inside of the tree area.
function doOutTreeAreaMouseUp(event) {
document.getElementById("last-event").innerHTML = "Click Up Event (Out)";
var curAction = algebWrist.dragging.curAction;
// See if we classify as a click.
if (curAction.action === "dragging") {
doAbortClick();
}
document.body.removeEventListener("mouseup", doOutTreeAreaMouseUp)
event.preventDefault();
}
// Deal with release of click or touch inside of the tree area.
window.doTreeAreaMouseEnter = function (event) {
document.getElementById("last-event").innerHTML = "MouseEnter";
var curAction = algebWrist.dragging.curAction;
if (curAction !== null && curAction.action === 'dragging') {
document.body.removeEventListener("mouseup", doOutTreeAreaMouseUp)
}
}
// Deal with release of click or touch inside of the tree area.
window.doTreeAreaMouseLeave = function (event) {
document.getElementById("last-event").innerHTML = "MouseLeave";
var curAction = algebWrist.dragging.curAction;
if (curAction !== null && curAction.action === 'dragging') {
document.body.addEventListener("mouseup", doOutTreeAreaMouseUp)
}
}
function applyParentActiveRule() {
var newInputs = [],
selectExpr = algebWrist.selectExpr,
testExpr = selectExpr.parent;
if (testExpr == null)
return;
// See if the active element is a multiop. In that case, we need to group all other
// elements before checking our rules.
if (testExpr.type == exprType.multiop && testExpr.inputs.length > 2) {
var j = testExpr.inputs.indexOf(selectExpr);
for (var i in selectExpr.parent.inputs) {
if (i != j) {
newInputs.push(myBTM.parseExpression(selectExpr.parent.inputs[i].toString()));
}
testExpr = create_multiop(selectExpr.parent.op, [create_multiop(selectExpr.parent.op, newInputs), selectExpr]);
}
}
// We want to see what simplification can be done. So see what is allowed.
var ruleMatches = findMatchRules(autoIdentityList, testExpr, true);
// There was a valid simplification so make the substitution.
if (ruleMatches.length > 0) {
var subExpr = myBTM.parseExpression(ruleMatches[0].subStr).simplifyConstants();
if (algebWrist.selectExpr.parent.parent === null) {
algebWrist.theExpr = subExpr;
algebWrist.selectExpr = subExpr;
} else {
algebWrist.selectExpr.parent.parent.inputSubst(algebWrist.selectExpr.parent, subExpr);
}
updateSelect(subExpr, true);
updateVisual();
} else {
treePositions();
}
}
function applyActiveInputRule(whichInput) {
var newInputs = [],
selectExpr = algebWrist.selectExpr,
testExpr = selectExpr;
// See if the active element is a multiop. In that case, we need to group all other
// elements before checking our rules.
if (selectExpr.type == exprType.multiop && selectExpr.inputs.length > 2) {
for (var i in selectExpr.inputs) {
if (i != whichInput) {
newInputs.push(myBTM.parseExpression(selectExpr.inputs[i].toString()));
}
}
testExpr = create_multiop(selectExpr.op, [create_multiop(selectExpr.op, newInputs), selectExpr.inputs[whichInput]]);
whichInput = 1;
}
// We want to see what simplification can be done. So see what is allowed.
var ruleMatches = findMatchRules(autoIdentityList, testExpr, true);
// There was a valid simplification so make the substitution.
if (ruleMatches.length > 0) {
var subExpr = myBTM.parseExpression(ruleMatches[0].subStr).simplifyConstants();
if (algebWrist.selectExpr.parent === null) {
algebWrist.theExpr = subExpr;
algebWrist.selectExpr = subExpr;
} else {
algebWrist.selectExpr.parent.inputSubst(algebWrist.selectExpr, subExpr);
}
updateSelect(subExpr, true);
updateVisual();
}
// Check if distributive property would apply.
// This is treated specially so that can apply to all terms. If can find
// how to implement a recursive substitution, then this could be eliminated.
else if (testExpr.type == exprType.multiop && testExpr.op == '*'
&& testExpr.inputs[whichInput].type === exprType.multiop && testExpr.inputs[whichInput].op == '+')
{
var subExpr,
factorInput = 1 - whichInput, // This relies on the earlier step that recast multiop into effective binop.
factorStr = testExpr.inputs[factorInput].toString(),
sumTerm = testExpr.inputs[whichInput];
newInputs = [];
for (var i in sumTerm.inputs) {
newInputs[i] = create_multiop('*', [myBTM.parseExpression(factorStr), sumTerm.inputs[i]]).simplifyConstants();
}
subExpr = create_multiop('+', newInputs);
if (algebWrist.selectExpr.parent === null) {
algebWrist.theExpr = subExpr;
algebWrist.selectExpr = subExpr;
} else {
algebWrist.selectExpr.parent.inputSubst(algebWrist.selectExpr, subExpr);
}
updateSelect(subExpr, true);
updateVisual();
}
else {
treePositions();
}
}
// Deal with release of click inside of the tree area.
window. doTreeAreaClickUp = function (timeStamp, clientX, clientY) {
var curAction = algebWrist.dragging.curAction,
lastAction = algebWrist.dragging.lastAction,
curTree = algebWrist.currentTree,
whichInput,
doVisualUpdate=false;
// See if we classify as a click.
if (curAction.action === "down") {
if ((timeStamp - curAction.time < algebWrist.clickDuration)
&& (Math.abs(clientX - curAction.x) + Math.abs(clientY - curAction.y) < algebWrist.clickError))
{
if (lastAction !== null && lastAction.action === "click" && (curAction.time - lastAction.time) < algebWrist.dblClickTime
&& (Math.abs(lastAction.x - curAction.x) + Math.abs(lastAction.y - curAction.y) < algebWrist.clickError))
{
curAction.action = "dblclick";
} else {
curAction.action = "click";
}
curAction.time = timeStamp;
} else {
curAction.action = "context";
}
// See if we classify the release as a drop.
} else if (curAction.action === "dragging") {
var selectExpr = algebWrist.selectExpr;
curAction.action = "drag"
// Nothing ultimately happened
if (curAction.lastDropTarget === null) {
treePositions();
}
// Dragging an input to +/* (commutative+associative).
// See where it dropped.
else if (curAction.commuteInput >= 0) {
// It was dropped in a space made available for commutativity.
if (curAction.lastDropTarget.indexOf("position") >= 0) {
var newIndex = +curAction.lastDropTarget.slice(9),
oldIndex = +curAction.commuteInput;
var moveInput = selectExpr.inputs.slice(oldIndex, oldIndex+1);
selectExpr.inputs = selectExpr.inputs.slice(0,oldIndex)
.concat(selectExpr.inputs.slice(oldIndex+1));
selectExpr.inputs = selectExpr.inputs.slice(0,newIndex)
.concat(moveInput)
.concat(selectExpr.inputs.slice(newIndex));
updateSelect(selectExpr.reduce(), true);
doVisualUpdate = true;
}
// It was dropped on one of the other inputs.
// This might mean to apply:
// - a simplification
// - distribute multiplication over addition
// - associativity: just regroup some terms.
else if (curAction.lastDropTarget.indexOf("input-element") >= 0) {
var dropIndex = +curAction.lastDropTarget.slice(14),
dragIndex = +curAction.commuteInput,
newInputs = [],
dragInput = selectExpr.inputs[dragIndex],
dropInput = selectExpr.inputs[dropIndex];
// We want to see what simplification can be done. So see what is allowed.
var tempExpr = create_multiop(selectExpr.op, [dropInput, dragInput]),
ruleMatches = findMatchRules(autoIdentityList, tempExpr, true);
// There was a valid simplification so make the substitution.
if (ruleMatches.length > 0) {
selectExpr.inputs[dropIndex] = myBTM.parseExpression(ruleMatches[0].subStr).simplifyConstants();
selectExpr.inputs[dropIndex].parent = selectExpr;
selectExpr.inputs = selectExpr.inputs.slice(0,dragIndex)
.concat(selectExpr.inputs.slice(dragIndex+1));
}
// Check if distributive property would apply.
// Restricted to the case of binary multiplication so that associative rule can
// be used when more than 2.
// This is treated specially so that can apply to all terms. If can find
// how to implement a recursive substitution, then this could be eliminated.
else if ((dropInput.type === exprType.binop || dropInput.type === exprType.multiop)
&& dropInput.op === '+' && selectExpr.op === '*' && selectExpr.inputs.length == 2)
{
var newExpr,
factorStr = dragInput.toString();
for (var i in dropInput.inputs) {
newInputs[i] = create_multiop('*', [myBTM.parseExpression(factorStr), dropInput.inputs[i]]).simplifyConstants();
}
selectExpr.inputs[dropIndex] = create_multiop('+', newInputs);
selectExpr.inputs[dropIndex].parent = selectExpr;
selectExpr.inputs = selectExpr.inputs.slice(0,dragIndex)
.concat(selectExpr.inputs.slice(dragIndex+1));
}
// Final option is just to regroup (Associativity)
else {
if ((dropInput.type === exprType.binop || dropInput.type === exprType.multiop)
&& dropInput.op === selectExpr.op)
{
newInputs = newInputs.concat(dropInput.inputs);
} else {
newInputs.push(dropInput);
}
if ((dragInput.type === exprType.binop || dragInput.type === exprType.multiop)
&& dragInput.op === selectExpr.op) {
newInputs = newInputs.concat(dragInput.inputs);
} else {
newInputs.push(dragInput);
}
selectExpr.inputs[dropIndex] = create_multiop(selectExpr.op, newInputs).simplifyConstants();
selectExpr.inputs[dropIndex].parent = selectExpr;
selectExpr.inputs = selectExpr.inputs.slice(0,dragIndex)
.concat(selectExpr.inputs.slice(dragIndex+1));
}
// After making the substitution, reset the selected element.
updateSelect(selectExpr.reduce(), true);
doVisualUpdate = true;
}
}
// Was the parent dropped on the active element or vice versa.
else if ( (curAction.element.id == 'parent-element' && curAction.lastDropTarget == 'active-element')
|| (curAction.element.id == 'active-element' && curAction.lastDropTarget == 'parent-element') )
{
applyParentActiveRule();
}
// Was the active element dragged onto an input or vice versa
// (or non-commutative input dropped on another input ==> binary operator!)
else if ( (curAction.element.id.indexOf('input-element') == 0 && curAction.lastDropTarget == 'active-element')
|| (curAction.element.id == 'active-element' && curAction.lastDropTarget.indexOf('input-element')==0)
|| (curAction.element.id.indexOf('input-element') == 0
&& curAction.lastDropTarget.indexOf('input-element') == 0)
)
{
var whichInput;
if (curAction.lastDropTarget == 'active-element') {
whichInput = curTree.inputElements.indexOf(curAction.element);
} else {
whichInput = +curAction.lastDropTarget.slice(14);
}
applyActiveInputRule(whichInput);
}
else {
treePositions();
}
} else {
}
// Now act on the behavior.
switch (curAction.action) {
case "dblclick":
doDoubleClick(curAction);
break;
default:
if (doVisualUpdate) {
updateVisual();
}
break;
}
}
// Deal with release of click inside of the tree area.
window.doTreeAreaMouseUp = function (event) {
document.getElementById("last-event").innerHTML = "Click Up Event";
doTreeAreaClickUp(event.timeStamp, event.clientX, event.clientY);
event.preventDefault();
}
// Deal with release of touch inside of the tree area.
window.doTreeAreaTouchEnd = function (event) {
var curAction = algebWrist.dragging.curAction;
// Not designed to deal with multi-touch.
if (event.touches.length > 1) {
doAbortClick();
}
if (curAction.action !== "cancel") {
document.getElementById("last-event").innerHTML = "Touch Up Event";
doTreeAreaClickUp(event.timeStamp, curAction.lastX, curAction.lastY);
event.preventDefault();
}
}
function updateSelect(newSelectExpr, doHistory) {
if (algebWrist.selectExpr !== null) {
algebWrist.selectExpr.select = false;
}
algebWrist.selectExpr = newSelectExpr;
if (algebWrist.selectExpr == null) {
algebWrist.selectExpr = algebWrist.theExpr;
}
algebWrist.selectExpr.select = true;
if (typeof doHistory != "undefined" && doHistory)
addHistory();
updateIdentities();
}
window.moveLeft = function() {
if (algebWrist.selectExpr !== null && algebWrist.selectExpr.parent !== null
&& typeof algebWrist.selectExpr.parent.inputs !== 'undefined') {
var newChild = algebWrist.selectExpr.parent.inputs.indexOf(algebWrist.selectExpr) - 1;
if (newChild < 0) {
newChild = algebWrist.selectExpr.parent.inputs.length - 1;
}
updateSelect(algebWrist.selectExpr.parent.inputs[newChild]);
updateVisual();
}
}
window.moveRight = function() {
if (algebWrist.selectExpr !== null && algebWrist.selectExpr.parent !== null
&& typeof algebWrist.selectExpr.parent.inputs !== 'undefined') {
var newChild = algebWrist.selectExpr.parent.inputs.indexOf(algebWrist.selectExpr) + 1;
if (newChild >= algebWrist.selectExpr.parent.inputs.length) {