lkarch.org/tools/i2/_miscFuncs.js
2022-11-03 19:43:40 -04:00

12394 lines
377 KiB
JavaScript

//Javascript document
var alg = {
toObj: function(txt) {
txt = simplifyText(clone(txt));
txt = removeTags(txt);
var exp = [];
//console.log('---');
//console.log('txt',txt);
var terms = splitTerms(txt);
//console.log('terms',clone(terms));
for (var t = 0; t < terms.length; t++) processTerm(terms[t]);
//console.log('toObj:',JSON.stringify(exp));
return exp;
function splitTerms(txt) {
var terms = [[]];
var bracketCount = 0;
for (var i = 0; i < txt.length; i++) {
var txt1 = txt[i];
if (typeof txt1 === 'string') {
for (var c = 0; c < txt1.length; c++) {
if (txt1.charAt(c) === '(') {
bracketCount++;
} else if (txt1.charAt(c) === ')') {
bracketCount--;
} else if (bracketCount === 0 && (txt1.charAt(c) === '+' || txt1.charAt(c) === '-')) {
if (c > 0) {
terms.last().push(txt1.slice(0,c));
txt1 = txt1.slice(c);
c = 0;
}
if (terms.last().length > 0) {
terms.push([]);
}
}
}
terms.last().push(txt1);
} else {
terms.last().push(txt1);
}
}
return terms;
}
function getAlgFrac(term) {
//console.log('term:',clone(term));
var sign = 1;
if (term[0] === '-') {
sign = -1;
term.shift();
} else if (term[0] === '+') {
term.shift();
}
var num = alg.toObj(term[0][1]);
var denom = alg.toObj(term[0][2]);
var coeff = [1,1];
if (num.length === 1) {
coeff[0] *= num[0].coeff[0];
coeff[1] *= num[0].coeff[1];
num[0].coeff = [1,1];
} else {
num = [{sign:1,coeff:[1,1],subterms:[[num,1]]}];
}
if (denom.length === 1) {
coeff[0] *= denom[0].coeff[1];
coeff[1] *= denom[0].coeff[0];
denom[0].coeff = [1,1];
for (var s = 0; s < denom[0].subterms.length; s++) {
denom[0].subterms[s][1] *= -1;
}
} else {
denom = [{sign:1,coeff:[1,1],subterms:[[denom,-1]]}];
}
var numSubterms = num[0].subterms;
var denomSubterms = denom[0].subterms;
var subterms = numSubterms.concat(denomSubterms);
//subterms = alg.simplifySubterms(subterms);
subterms = alg.orderSubterms(subterms);
var obj = {
sign:sign,
coeff:coeff,
subterms:subterms
};
term.shift();
return obj;
}
function processTerm(term) {
if (term.length === 0) return;
//console.log('term',clone(term));
var obj = {};
exp.push(obj);
while (term[0] === '') term.shift();
var coeff = 1;
if ((term[0] === '-' || term[0] === '+') && typeof term[1] === 'object' && term[1][0] === 'frac') {
if (term[1][1].length === 1 && !isNaN(Number(term[1][1][0])) && term[1][2].length === 1 && !isNaN(Number(term[1][2][0]))) {
obj.sign = term[0] === '-' ? -1: 1;
obj.coeff = [Number(term[1][1][0]),Number(term[1][2][0])];
term.shift();
term.shift();
} else {
exp[exp.length-1] = getAlgFrac(term);
}
} else if (typeof term[0] === 'string') {
var sign = 1;
if (term[0][0] === '-') {
sign = -1;
term[0] = term[0].slice(1);
} else if (term[0][0] === '+') {
term[0] = term[0].slice(1);
}
var charCount = 0;
for (var c = 1; c < term[0].length+1; c++) {
if (isNaN(Number(term[0].slice(0,c)))) {
term[0] = term[0].slice(charCount);
break;
} else {
coeff = Number(term[0].slice(0,c));
charCount++;
if (c === term[0].length) {
term[0] = term[0].slice(charCount);
break;
}
}
}
obj.sign = sign;
obj.coeff = [coeff,1];
} else if (typeof term[0] === 'object' && term[0][0] === 'frac') {
if (term[0][1].length === 1 && !isNaN(Number(term[0][1][0])) && term[0][2].length === 1 && !isNaN(Number(term[0][2][0]))) {
obj.sign = 1;
obj.coeff = [Number(term[0][1][0]),Number(term[0][2][0])];
term.shift();
} else {
exp[exp.length-1] = getAlgFrac(term);
}
}
while (term[0] === '') term.shift();
if (term.length === 0) {
obj.subterms = [];
return;
}
var subterms = [];
var count = 0;
while (!un(term[0]) && typeof term[0] === 'string' && count < 100) {
count++;
var subterms2 = [];
if (typeof term[0] === 'string') {
if (term[0][0] === '(') {
term[0] = term[0].slice(1);
if (term[0] === '') term.shift();
var term2 = [];
var bracketCount = 1;
var count = 0;
while (bracketCount > 0 && count < 100) {
count++;
if (typeof term[0] === 'string') {
var str = term[0];
for (var c = 0; c < str.length; c++) {
var char = str[c];
if (char === '(') {
bracketCount++;
} else if (char === ')') {
bracketCount--;
if (bracketCount === 0) {
if (c > 0) term2.push(str.slice(0,c));
term[0] = str.slice(c+1);
break;
}
}
}
if (bracketCount !== 0) {
term2.push(term[0]);
term.shift();
}
} else if (typeof term[0] === 'object') {
term2.push(term[0]);
term.shift();
}
}
subterms2 = [alg.toObj(term2),1];
if (term[0] === '') term.shift();
if (typeof term[0] === 'object' && term[0][0] === 'pow') {
if (!isNaN(Number(term[0][2]))) subterms2[1] = Number(term[0][2]);
term.shift();
}
} else {
subterms2 = [term[0][0],1];
term[0] = term[0].slice(1);
if (term[0] === '') term.shift();
if (typeof term[0] === 'object' && term[0][0] === 'pow') {
if (!isNaN(Number(term[0][2]))) subterms2[1] = Number(term[0][2]);
term.shift();
}
}
}
if (term[0] === '') term.shift();
subterms.push(subterms2);
}
obj.subterms = subterms;
}
},
toText: function(exp,type) {
if (un(type)) type = 'default'; // default is fracTerms
var txt = [''];
if (exp instanceof Array === false && typeof exp === 'object') exp = [exp];
if (exp.length === 0) return ["0"];
for (var e = 0; e < exp.length; e++) {
var term = exp[e];
//console.log('term',term);
var isFirstTerm = e === 0 ? true : false;
var isOnlyTerm = exp.length === 1 ? true : false;
if (type === 'inline') {
txt = txt.concat(termToTextInline(term,isFirstTerm,isOnlyTerm));
} else {
txt = txt.concat(termToTextFracTerms(term,isFirstTerm,isOnlyTerm))
}
}
txt = simplifyText(txt);
return txt;
function termToTextInline(term,isFirstTerm,isOnlyTerm) {
var txt = [];
var sign = term.sign === -1 ? '-' : '+';
var coeff = term.coeff;
if (coeff[1] === 1) coeff = coeff[0];
var subterms = term.subterms;
if (coeff === 1 && subterms.length > 0) coeff = '';
if (isFirstTerm === false || sign === '-') txt.push(sign);
if (coeff instanceof Array) {
txt.push(['frac',[String(Math.abs(coeff[0]))],[String(Math.abs(coeff[1]))]]);
} else {
txt.push(String(coeff));
}
var subtermsText = [];
for (var s = 0; s < subterms.length; s++) {
var sub = subterms[s];
if (typeof sub[0] === 'string') {
subtermsText.push(sub[0]);
} else if (typeof sub[0] === 'object') {
var brackets = sub.length > 1 ? true : false;
if (brackets = true) subtermsText.push('(');
subtermsText = subtermsText.concat(alg.toText(sub[0]));
if (brackets = true) subtermsText.push(')');
}
if (!un(sub[1]) && sub[1] !== 1) subtermsText.push(['pow',false,String(sub[1])]);
}
txt = txt.concat(subtermsText);
return txt;
}
function termToTextFracTerms(term,isFirstTerm,isOnlyTerm) {
var num = [];
var denom = [];
var subterms = term.subterms;
for (var s = 0; s < subterms.length; s++) {
var sub = subterms[s];
var type = alg.getSubtermType(sub);
if (type === 'single') {
if (sub[1] > 0) {
num.push(sub[0]);
if (sub[1] > 1) num.push(['pow',false,String(sub[1])]);
} else {
denom.push(sub[0]);
if (sub[1] < -1) denom.push(['pow',false,String(Math.abs(sub[1]))]);
}
} else if (type === 'compound') {
if (sub[1] > 0) {
var brackets = subtermRequiresBrackets(sub);
if (brackets === true) num.push('(');
num = num.concat(alg.toText(sub[0]));
if (brackets === true) num.push(')');
if (sub[1] > 1) num.push(['pow',false,String(sub[1])]);
} else {
var brackets = subtermRequiresBrackets(sub);
if (brackets === true) denom.push('(');
denom = denom.concat(alg.toText(sub[0]));
if (brackets === true) denom.push(')');
if (sub[1] < -1) denom.push(['pow',false,String(Math.abs(sub[1]))]);
}
}
}
function subtermRequiresBrackets(subterm) {
if (subterm[1] > 1 || subterm[1] < -1) return true;
if (subterm[0].length > 1) return true;
var exp = subterm[0][0].subterms;
if (exp.length > 1 || un(exp[0])) return true;
if (Math.abs(exp[0][1]) > 1) return false;
if (arraysEqual(exp[0][0].coeff,[1,1]) === true) return false;
return false;
}
var coeff = term.coeff;
if (coeff[0] !== 1 || num.length === 0) num.unshift(String(coeff[0]));
if (coeff[1] !== 1 || denom.length === 0) denom.unshift(String(coeff[1]));
var sign = term.sign === -1 ? '-' : '+';
if (isFirstTerm === true && sign === '+') sign = '';
num = simplifyText(num);
denom = simplifyText(denom);
var isFraction = (arraysEqual(denom,['1']) || denom.length === 0) ? false : true;
if (isFraction) {
if (typeof num[0] === 'string' && num[0][0] === '(' && typeof num[num.length-1] === 'string' && num[num.length-1].slice(-1) === ')') {
var bracketCount = 1;
var bracketClosed = false;
for (var i = 0; i < num.length; i++) {
if (typeof num[i] !== 'string') continue;
for (var j = 0; j < num[i].length; j++) {
if (i == 0 && j == 0) continue;
if (i == num.length-1 && j == num[i].length-1) continue;
if (num[i][j] === '(') bracketCount++;
if (num[i][j] === ')') bracketCount--;
if (bracketCount === 0) {
bracketClosed = true;
j = num[i].length;
i = num.length;
}
}
}
if (bracketClosed === false) {
num[0] = num[0].slice(1);
num[num.length-1] = num[num.length-1].slice(0,-1);
num = simplifyText(num);
}
}
if (typeof denom[0] === 'string' && denom[0][0] === '(' && typeof denom[denom.length-1] === 'string' && denom[denom.length-1].slice(-1) === ')') {
var bracketCount = 1;
var bracketClosed = false;
for (var i = 0; i < denom.length; i++) {
if (typeof denom[i] !== 'string') continue;
for (var j = 0; j < denom[i].length; j++) {
if (i == 0 && j == 0) continue;
if (i == denom.length-1 && j == denom[i].length-1) continue;
if (denom[i][j] === '(') bracketCount++;
if (denom[i][j] === ')') bracketCount--;
if (bracketCount === 0) {
bracketClosed = true;
j = denom[i].length;
i = denom.length;
}
}
}
if (bracketClosed === false) {
denom[0] = denom[0].slice(1);
denom[denom.length-1] = denom[denom.length-1].slice(0,-1);
denom = simplifyText(denom);
}
}
var txt = [sign,['frac',num,denom,1]];
} else {
var txt = [sign].concat(num);
}
return txt;
}
},
add: function(exp1,exp2) {
var exp3 = clone(exp1);
exp3 = exp3.concat(clone(exp2));
//exp3 = alg.collect(exp3);
return exp3;
},
subtract: function(exp1,exp2) {
var exp2 = clone(exp2);
var exp2 = alg.multiply(exp2,[{sign:-1,coeff:[1,1],subterms:[]}]);
var exp3 = alg.add(exp1,exp2);
return exp3;
},
multiply: function(exp1,exp2,simplify) {
exp1 = clone(exp1);
exp2 = clone(exp2);
if (exp1.length > 1) exp1 = [{sign:1,coeff:[1,1],subterms:[[exp1,1]]}];
if (exp2.length > 1) exp2 = [{sign:1,coeff:[1,1],subterms:[[exp2,1]]}];
var term1 = clone(exp1[0]);
var subterms1 = term1.subterms;
var coeff1 = term1.coeff;
var term2 = clone(exp2[0]);
var subterms2 = term2.subterms;
var coeff2 = term2.coeff;
var sign3 = term1.sign*term2.sign;
var coeff3 = [coeff1[0]*coeff2[0],coeff1[1]*coeff2[1]];
var subterms3 = subterms1.concat(subterms2);
if (boolean(simplify,true) === true) {
coeff3 = simplifyFrac2(coeff3);
subterms3 = alg.orderSubterms(subterms3);
subterms3 = alg.simplifySubterms(subterms3);
}
return [{sign:sign3,coeff:coeff3,subterms:subterms3}];
},
divide: function(exp1,exp2,simplify) {
var exp2 = alg.getExpressionReciprocal(exp2);
var exp3 = alg.multiply(exp1,exp2,simplify);
return exp3;
},
collect: function(exp) {
exp = clone(exp);
for (var e2 = exp.length-1; e2 >= 0; e2--) {
var term2 = exp[e2];
var subterms2 = term2.subterms;
for (var e1 = e2-1; e1 >= 0; e1--) {
var term1 = exp[e1];
var subterms1 = term1.subterms;
if (alg.likeTerms(term1,term2)) {
term1.coeff[0] *= term1.sign;
term2.coeff[0] *= term2.sign;
var coeff = addFracs2(term1.coeff,term2.coeff);
term1.sign = coeff[0]/coeff[1] < 0 ? -1 : 1;
term1.coeff = simplifyFrac2([Math.abs(coeff[0]),Math.abs(coeff[1])]);
exp.splice(e2,1);
break;
}
}
}
for (var e = 0; e < exp.length; e++) {
if (exp[e].coeff[0] === 0) {
exp.splice(e,1);
e--;
}
}
return exp;
},
collectable: function(exp) {
var exp2 = clone(exp);
var exp3 = alg.collect(exp2);
return !isEqual(exp2,exp3);
},
expand: function(exp) {
//console.log('exp',JSON.stringify(exp));
var exp3 = clone(exp);
var exp2 = clone(exp);
var count = 0;
do {
count++;
var exp2 = clone(exp3);
var exp3 = expandExp(exp3);
} while (!isEqual(exp2,exp3) && count < 10);
//console.log('exp3',JSON.stringify(exp3));
return exp3;
function expandExp(exp) {
var exp2 = clone(exp);
var exp3 = [];
for (var e = 0; e < exp2.length; e++) {
var term = exp2[e];
//console.log('term',JSON.stringify(term));
var num = alg.getTermNumerator(term);
var denom = alg.getTermDenominator(term);
//console.log('num:',clone(num));
//console.log('denom:',clone(denom));
var num2 = expandTerm(num);
var denom2 = expandTerm(denom);
//console.log('num2:',clone(num2));
//console.log('denom2:',clone(denom2));
var term2 = alg.divide(num2,denom2);
//console.log('term2',JSON.stringify(term2));
exp3 = exp3.concat(term2);
}
var exp4 = [];
for (var e = 0; e < exp3.length; e++) {
var term = exp3[e];
if (term.sign === 1 && arraysEqual(term.coeff,[1,1]) && !un(term.subterms) && term.subterms.length === 1 && !un(term.subterms[0]) && alg.getSubtermType(term.subterms[0]) === 'compound' && term.subterms[0][1] === 1) {
exp4 = exp4.concat(term.subterms[0][0]);
} else {
exp4.push(term);
}
}
//console.log('exp4',JSON.stringify(exp4));
return exp4;
}
function expandTerm(term) {
var term = clone(term);
// apply powers to compound subterms
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
if (alg.getSubtermType(subterm) !== 'compound') continue;
if (subterm[1] > 1) {
var index = subterm[1];
var subterm2 = clone(subterm[0]);
for (var i = 1; i < index; i++) {
subterm2 = alg.multiplyExpressions(subterm2,subterm[0]);
}
term.subterms[s] = [subterm2,1];
} else if (subterm[1] < 1) {
var index = -1*subterm[1];
var subterm2 = clone(subterm[0]);
for (var i = 1; i < index; i++) {
subterm2 = alg.multiplyExpressions(subterm2,subterm[0]);
}
term.subterms[s] = [subterm2,-1];
}
}
// mutiply subterms together
var count = 0;
while (term.subterms.length > 1 && count < 100) {
count++;
var subterm1 = term.subterms[0];
var type1 = alg.getSubtermType(subterm1);
if (type1 === 'single') {
for (var i = 1; i < term.subterms.length; i++) {
var subterm2 = term.subterms[i];
var type2 = alg.getSubtermType(subterm2);
if (type2 === 'compound') {
for (var s = 0; s < subterm2[0].length; s++) {
var subterm3 = subterm2[0][s].subterms;
subterm3.push(clone(subterm1));
subterm2[0][s].subterms = alg.simplifySubterms(subterm3);
}
term.subterms.shift();
break;
}
}
} else if (type1 === 'compound') {
var subterm2 = term.subterms[1];
var type2 = alg.getSubtermType(subterm2);
if (type2 === 'single') {
for (var s = 0; s < subterm1[0].length; s++) {
var subterm3 = subterm1[0][s].subterms;
subterm3.push(clone(subterm2));
subterm1[0][s].subterms = alg.simplifySubterms(subterm3);
}
term.subterms.splice(1,1);
} else if (type2 === 'compound') {
term.subterms[1] = [alg.multiplyExpressions(subterm1[0],subterm2[0]),1];
term.subterms.shift();
}
}
}
var exp = [];
//console.log(JSON.stringify(term));
if (!un(term.subterms[0])) {
if (alg.getSubtermType(term.subterms[0]) === 'compound') {
var terms2 = term.subterms[0][0];
for (var s = 0; s < terms2.length; s++) {
var term2 = terms2[s];
exp.push({
sign:term.sign*term2.sign,
coeff:simplifyFrac2([term.coeff[0]*term2.coeff[0],term.coeff[1]*term2.coeff[1]]),
subterms:term2.subterms
});
}
} else {
exp.push({
sign:term.sign,
coeff:simplifyFrac2([term.coeff[0],term.coeff[1]]),
subterms:term.subterms
});
}
}
if (exp.length === 0) exp.push({sign:term.sign,coeff:clone(term.coeff),subterms:[]});
return exp;
}
},
expandable: function(exp) {
var exp2 = clone(exp);
var exp3 = alg.expand(exp2);
return !isEqual(exp2,exp3);
},
factorise: function(exp) {
var exp2 = clone(exp);
if (alg.collectable(exp2) === true) return exp;
//console.log('exp',JSON.stringify(exp));
var exp3 = [];
// look in each term for factorisable subterms
for (var t = 0; t < exp2.length; t++) {
var term = exp2[t];
var num = alg.getTermNumerator(term);
//console.log('num:',JSON.stringify(num));
num = factoriseTerm(num);
var denom = alg.getTermDenominator(term);
//console.log('denom:',JSON.stringify(denom));
denom = factoriseTerm(denom);
var term2 = alg.divide([num],[denom],false);
//console.log('term2:',JSON.stringify(term2));
exp3 = exp3.concat(term2);
}
exp3 = factoriseExp(exp3);
return exp3;
function factoriseTerm(term) {
var term = clone(term);
var factors = [];
//console.log('factorise term',clone(term));
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
//console.log('-----',s);
//console.log('term',clone(term));
//console.log('subterm',JSON.stringify(subterm));
if (un(subterm)) continue;
if (alg.getSubtermType(subterm) === 'simple') continue;
var index = subterm[1];
var exp = subterm[0];
//console.log('exp',clone(exp));
//console.log('index',index);
var factor = alg.getHCF(exp);
//console.log('factor',JSON.stringify(factor));
if (isEqual(factor,[{sign:1,coeff:[1,1],subterms:[]}])) continue;
var dividend = [];
for (var t = 0; t < exp.length; t++) {
var termdiv = alg.divide([exp[t]],factor);
termdiv = alg.simplify(termdiv);
dividend.push(termdiv[0]);
}
if (isEqual(dividend,[{sign:1,coeff:[1,1],subterms:[]}])) continue;
//console.log('dividend',JSON.stringify(dividend));
if (index !== 1) {
factor[0].coeff[0] = Math.pow(factor[0].coeff[0],index);
factor[0].coeff[1] = Math.pow(factor[0].coeff[1],index);
for (var s2 = 0; s2 < factor[0].subterms.length; s2++) {
factor[0].subterms[s2][1] = index;
}
}
term.sign *= factor[0].sign;
term.coeff = [term.coeff[0]*factor[0].coeff[0],term.coeff[1]*factor[0].coeff[1]];
factors = factors.concat(factor[0].subterms);
term.subterms[s] = [dividend,index];
//console.log('subterm',JSON.stringify(term.subterms[s]));
//console.log('term',JSON.stringify(term));
}
term.subterms = factors.concat(term.subterms);
//console.log('term = '+JSON.stringify(term,null,2));
return term;
}
function factoriseExp(exp) {
//console.log('exp',clone(exp));
var factor = alg.getHCF(exp);
//console.log('-----');
//console.log('exp',JSON.stringify(exp));
//console.log('factor',JSON.stringify(factor));
if (isEqual(factor,[{sign:1,coeff:[1,1],subterms:[]}])) {
var exp2 = exp;
} else {
var dividend = [];
for (var t = 0; t < exp.length; t++) {
var termdiv = alg.divide([exp[t]],factor);
termdiv = alg.simplify(termdiv);
dividend.push(termdiv[0]);
}
//console.log('dividend',JSON.stringify(dividend));
if (isEqual(dividend,[{sign:1,coeff:[1,1],subterms:[]}])) {
var exp2 = exp;
} else {
var exp2 = factor;
exp2[0].subterms.push([dividend,1]);
//console.log('exp2',JSON.stringify(exp2));
}
}
for (var t = 0; t < exp2.length; t++) {
var term = exp2[t];
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
if (alg.getSubtermType(subterm) !== 'compound') continue;
term.subterms[s][0] = alg.quadFactorise(term.subterms[s][0]);
}
}
exp2 = alg.quadFactorise(exp2);
//console.log('exp2',JSON.stringify(exp2));
return exp2;
}
},
factorisable: function(exp) {
var exp2 = clone(exp);
var exp3 = alg.factorise(exp2);
return !isEqual(exp2,exp3);
},
quadFactorise: function(exp) {
if (exp.length > 3) return exp;
var exp2 = clone(exp);
exp2 = alg.collect(exp2);
var terms = [];
for (var t = 0; t < exp2.length; t++) {
var term = exp2[t];
var subterms = term.subterms;
if (subterms.length === 0) {
terms[2] = term;
} else if (subterms[0][1] === 2) {
terms[0] = term;
} else if (subterms[0][1] === 1) {
terms[1] = term;
}
}
if (un(terms[0])) return exp;
if (un(terms[2])) return exp;
var vari = terms[0].subterms[0][0];
if (un(terms[1])) terms[1] = {sign:1,coeff:[0,1],subterms:[[vari,1]]};
if (un(terms[2])) terms[2] = {sign:1,coeff:[0,1],subterms:[]};
var a = terms[0].sign*terms[0].coeff[0];
var b = terms[1].sign*terms[1].coeff[0];
var c = terms[2].sign*terms[2].coeff[0];
var det = b*b-4*a*c;
if (det < 0) return exp;
var detroot = Math.sqrt(det);
if (detroot !== Math.round(detroot)) return exp;
var roots = [
simplifyFrac2([-b+detroot,2*a]),
simplifyFrac2([-b-detroot,2*a])
];
var sign1 = roots[0][0]/roots[0][1] < 0 ? 1 : -1;
var sign2 = roots[1][0]/roots[1][1] < 0 ? 1 : -1;
roots[0][0] = Math.abs(roots[0][0]);
roots[0][1] = Math.abs(roots[0][1]);
roots[1][0] = Math.abs(roots[1][0]);
roots[1][1] = Math.abs(roots[1][1]);
var exp3 = [{sign:1,coeff:[1,1],subterms:[]}];
if (sign1 === sign2 && arraysEqual(roots[0],roots[1])) {
var exp3 = [{sign:1,coeff:[1,1],subterms:[[[{sign:1,coeff:[roots[0][1],1],subterms:[[vari,1]]},{sign:sign1,coeff:[roots[0][0],1],subterms:[]}],2]]}];
} else {
var exp3 = [
{sign:1,coeff:[1,1],subterms:[
[[{sign:1,coeff:[roots[0][1],1],subterms:[[vari,1]]},{sign:sign1,coeff:[roots[0][0],1],subterms:[]}],1],
[[{sign:1,coeff:[roots[1][1],1],subterms:[[vari,1]]},{sign:sign2,coeff:[roots[1][0],1],subterms:[]}],1]
]}
];
}
return exp3;
},
simplify: function(exp) { // simplifies fractions
var exp2 = clone(exp);
for (var e = 0; e < exp2.length; e++) {
var term = exp2[e];
term.coeff = simplifyFrac2(term.coeff);
term.subterms = alg.simplifySubterms(term.subterms);
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
if (alg.getSubtermType(subterm) === 'compound') {
term.subterms[s] = [alg.simplify(subterm[0]),subterm[1]];
}
}
}
var exp3 = [];
for (var e = 0; e < exp2.length; e++) {
var term = exp2[e];
if (term.sign === 1 && arraysEqual(term.coeff,[1,1]) && !un(term.subterms) && term.subterms.length === 1 && !un(term.subterms[0]) && alg.getSubtermType(term.subterms[0]) === 'compound' && term.subterms[0][1] === 1) {
exp3 = exp3.concat(term.subterms[0][0]);
} else {
exp3.push(term);
}
}
return exp3;
/*var exp2 = clone(exp);
alg.collect(exp2);
for (var t = 0; t < exp2.length; t++) {
var term = exp2[t];
term.coeff = simplifyFrac2(term.coeff);
term.subterms = alg.simplifySubterms(term.subterms);
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
if (alg.getSubtermType(subterm) === 'compound') {
term.subterms[s] = [alg.simplify(subterm[0]),subterm[1]];
}
}
term.subterms = alg.orderSubterms(term.subterms);
}
return exp2;*/
},
simplifiable: function(exp) {
var exp2 = clone(exp);
var exp3 = alg.simplify(exp2);
return !isEqual(exp2,exp3);
},
likeTerms: function(term1,term2) {
var subterms1 = clone(term1.subterms);
var subterms2 = clone(term2.subterms);
if (subterms1.length !== subterms2.length) return false;
for (var s1 = 0; s1 < subterms1.length; s1++) {
var sub1 = subterms1[s1];
for (var s2 = 0; s2 < subterms2.length; s2++) {
var sub2 = subterms2[s2];
if (alg.likeSubterm(sub1,sub2) === false || sub1[1] !== sub2[1]) return false;
}
return true;
}
return true;
},
getTermNumerator: function(term) {
if (typeof term === 'string') return {sign:1,coeff:[1,1],subterms:[[term,1]]};
var num = {sign:term.sign,coeff:[term.coeff[0],1],subterms:[]};
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
if (subterm[1] >= 0) {
num.subterms.push(subterm);
}
}
return num;
},
getTermDenominator: function(term) {
if (typeof term === 'string') return {sign:1,coeff:[1,1],subterms:[]};
var denom = {sign:1,coeff:[term.coeff[1],1],subterms:[]};
for (var s = 0; s < term.subterms.length; s++) {
var subterm = term.subterms[s];
if (subterm[1] < 0) {
denom.subterms.push([subterm[0],-1*subterm[1]]);
}
}
return denom;
},
multiplyExpressions: function(exp1,exp2) {
var exp3 = [];
for (var e1 = 0; e1 < exp1.length; e1++) {
var term1 = clone(exp1[e1]);
var subterms1 = term1.subterms;
var coeff1 = term1.coeff;
for (var e2 = 0; e2 < exp2.length; e2++) {
var term2 = clone(exp2[e2]);
var subterms2 = term2.subterms;
var coeff2 = term2.coeff;
var sign3 = term1.sign*term2.sign;
var coeff3 = simplifyFrac2([coeff1[0]*coeff2[0],coeff1[1]*coeff2[1]]);
var subterms3 = subterms1.concat(subterms2);
subterms3 = alg.simplifySubterms(subterms3);
exp3.push({sign:sign3,coeff:coeff3,subterms:subterms3});
}
}
return exp3;
},
getTermReciprocal: function(term) {
var term2 = {
sign:term.sign,
coeff:[term.coeff[1],term.coeff[0]],
subterms:[]
};
for (var s = 0; s < term.subterms.length; s++) {
var sub = clone(term.subterms[s]);
sub[1] *= -1;
term2.subterms.push(sub);
}
return term2;
},
getExpressionReciprocal: function(exp) {
var exp = clone(exp);
if (exp.length === 1) {
var term = alg.getTermReciprocal(exp[0]);
return [term];
} else {
return [{sign:1,coeff:[1,1],subterms:[[exp,-1]]}];
}
},
simplifySubterms: function(subterms) {
var subterms = clone(subterms);
for (var v2 = subterms.length-1; v2 >= 0; v2--) { // combine like subterms
for (var v1 = v2-1; v1 >= 0; v1--) {
if (alg.likeSubterm(subterms[v2],subterms[v1])) {
subterms[v1][1] += subterms[v2][1];
subterms.splice(v2,1);
break;
}
}
}
for (var v2 = subterms.length-1; v2 >= 0; v2--) { // remove subterms with power 0
if (subterms[v2][1] === 0) subterms.splice(v2,1);
}
return subterms;
},
orderSubterms: function(subterms) {
//console.log(subterms);
subterms.sort(function(a,b) {
if (isEqual(a[0],b[0])) return b[1]-a[1];
var typeA = alg.getSubtermType(a);
var typeB = alg.getSubtermType(b);
if (typeA === 'single' && typeB === 'single') {
if (a[0] < b[0]) return -1;
if (a[0] > b[0]) return 1;
} else if (typeA === 'single') {
return -1;
} else if (typeB === 'single') {
return 1;
}
return 0;
});
for (var s = 0; s < subterms.length; s++) {
var subterm = subterms[s];
var type = alg.getSubtermType(subterm);
if (type === 'compound') {
//console.log(subterm);
for (var s2 = 0; s2 < subterm[0].length; s2++) {
var sub2 = subterm[0][s2];
alg.orderSubterms(sub2.subterms);
}
}
}
return subterms;
},
getSubtermType: function(subterm) {
if (subterm.length === 2 && typeof subterm[0] === 'string' && typeof subterm[1] === 'number') {
return 'single';
} else {
return 'compound';
}
},
likeSubterm: function(subterm1,subterm2) {
if (un(subterm1) && un(subterm2)) return true;
var sub1 = subterm1[0];
var sub2 = subterm2[0];
if (un(sub1) && un(sub2)) return true;
if (isEqual(sub1,sub2)) return true;
if (sub1 instanceof Array && sub2 instanceof Array) {
if (sub1.length !== sub2.length) return false;
sub1 = clone(sub1);
sub2 = clone(sub2);
for (var s1 = 0; s1 < sub1.length; s1++) {
var sub11 = sub1[s1];
var found = false;
for (var s2 = 0; s2 < sub2.length; s2++) {
var sub22 = sub2[s2];
if (sub11.sign === sub22.sign && arraysEqual(sub11.coeff,sub22.coeff) === true && arraysEqual(sub11.subterms,sub22.subterms) === true) {
found = true;
break;
}
}
if (found === false) return false;
}
return true;
}
return false;
},
getHCF: function(exp) {
var exp = clone(exp);
if (exp.length === 1) return [{sign:1,coeff:[1,1],subterms:[]}];
var term = exp[0];
var sign = term.sign;
var num = term.coeff[0];
var denom = term.coeff[1];
var subterms = clone(term.subterms);
for (var t = 1; t < exp.length; t++) {
var term = exp[t];
if (term.sign === 1) sign = 1;
num = hcf(num,term.coeff[0]);
denom = hcf(denom,term.coeff[1]);
for (var s1 = 0; s1 < subterms.length; s1++) {
var subterm1 = subterms[s1];
var found = false;
for (var s2 = 0; s2 < term.subterms.length; s2++) {
var subterm2 = term.subterms[s2];
if (alg.likeSubterm(subterm1,subterm2) === true) {
found = true;
subterm1[1] = Math.min(subterm1[1],subterm2[1]);
break;
}
}
if (found === false) {
subterms.splice(s1,1);
s1--;
}
}
}
return [{sign:sign,coeff:[num,denom],subterms:subterms}];
},
hasNegativeIndices: function(exp) {
for (var e = 0; e < exp.length; e++) {
if (exp[e][1] < 0) return true;
}
return false;
},
getCommonDenominator: function(exp) {
var exp = clone(exp);
var denomCoeff = 1;
var denomSubterms = [];
for (var e = 0; e < exp.length; e++) {
var term = exp[e];
denomCoeff = (denomCoeff*term.coeff[1])/hcf(denomCoeff,term.coeff[1]);
var subterms = term.subterms;
for (var s = 0; s < subterms.length; s++) {
if (subterms[s][1] < 0) {
subterms[s][1] *= -1;
denomSubterms.push(subterms[s]);
}
}
}
for (var s1 = denomSubterms.length-1; s1 >= 0; s1--) {
var sub1 = denomSubterms[s1];
for (var s2 = s1-1; s2 >= 0; s2--) {
var sub2 = denomSubterms[s2];
if (alg.likeSubterm(sub1,sub2) === true) {
sub2[1] = Math.max(sub1[1],sub2[1]);
denomSubterms.splice(s1,1);
break;
}
}
}
var exp2 = {sign:1,coeff:denomCoeff,subterms:denomSubterms};
return exp2;
}
}
function bound(value, min, max, roundTo) {
if (!un(roundTo))
value = roundToNearest(value, roundTo);
return Math.max(min, Math.min(max, value));
}
function pngVis(sf, dl) {
if (un(sf)) sf = 0.4;
var ctx = newctx({
vis: false
});
var obj = container.childNodes;
for (var o = 0; o < obj.length; o++) {
if (obj[o].nodeType !== 1)
continue;
if (obj[o].nodeName.toLowerCase() !== 'canvas')
continue;
var dims = obj[o].getBoundingClientRect();
var left = roundToNearest(xWindowToCanvas(dims.left), 1);
var top = roundToNearest(yWindowToCanvas(dims.top), 1);
flattenCanvases(ctx.canvas, obj[o], left, top);
}
if (sf !== 1) {
var w = mainCanvasWidth * sf;
var h = mainCanvasHeight * sf;
var ctx2 = newctx({
rect: [0, 0, w, h],
vis: false
});
ctx2.drawImage(ctx.canvas, 0, 0, w, h);
var imgURL = canvasToPNG(ctx2.canvas);
} else {
var imgURL = canvasToPNG(ctx.canvas);
}
//window.open(imgURL,'_blank');
return imgURL;
/*
if (boolean(dl, true) == true) {
var dlLink = document.createElement('a');
dlLink.download = 'dl.png';
dlLink.href = imgURL;
dlLink.dataset.downloadurl = ["image/png", dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
return;
}
return imgURL;*/
}
function canvasToPNG(canvas, filename, l, t, w, h) {
var width = mainCanvasWidth;
var height = mainCanvasHeight;
if (isElement(canvas)) {
width = canvas.width;
height = canvas.height;
}
var canvas2 = document.createElement('canvas');
canvas2.width = width;
canvas2.height = height;
var ctx2 = canvas2.getContext('2d');
if (isElement(canvas)) {
ctx2.drawImage(canvas, 0, 0);
} else if (typeof canvas == 'object') {
for (var i = 0; i < canvas.length; i++) {
ctx2.drawImage(canvas[i], canvas[i].data[100], canvas[i].data[101]);
}
}
var left = l || 0;
var top = t || 0;
var w2 = w || width;
var h2 = h || height;
var canvas3 = document.createElement('canvas');
canvas3.width = w2;
canvas3.height = h2;
var ctx3 = canvas3.getContext('2d');
ctx3.drawImage(canvas2, -left, -top);
var imgURL = canvas3.toDataURL("image/png");
return imgURL;
}
function calcRects(obj) {
if (un(obj))
obj = {};
var left = def([obj.left, 0]);
var top = def([obj.top, 80]);
if (typeof obj.margin == 'object') {
obj.marginLeft = obj.margin[0];
obj.marginTop = obj.margin[1];
obj.marginRight = obj.margin[2];
obj.marginBottom = obj.margin[3];
}
var marginLeft = def([obj.marginLeft, obj.margin, 20]);
var marginBottom = def([obj.marginBottom, obj.margin, 20]);
var marginTop = def([obj.marginTop, obj.margin, 20]);
var marginRight = def([obj.marginRight, obj.margin, 20]);
var paddingVert = def([obj.paddingVert, obj.padding, 40]);
var paddingHoriz = def([obj.paddingHoriz, obj.padding, 40]);
var rows = def([obj.rows, 2]);
var cols = def([obj.cols, 2]);
var order = def([obj.order, 'v']);
var width = (1200 - left - marginLeft - marginRight - (cols - 1) * paddingHoriz) / cols;
var height = (700 - top - marginTop - marginBottom - (rows - 1) * paddingVert) / rows;
var arr = [];
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if (order == 'h') {
var index = r * cols + c;
} else {
var index = c * rows + r;
}
arr[index] = [
left + marginLeft + c * (width + paddingHoriz),
top + marginTop + r * (height + paddingVert),
width,
height,
left + marginLeft + c * (width + paddingHoriz) + width,
top + marginTop + r * (height + paddingVert) + height,
left + marginLeft + c * (width + paddingHoriz) + 0.5 * width,
top + marginTop + r * (height + paddingVert) + 0.5 * height,
];
}
}
if (!un(obj.ctx)) { // if ctx is supplied, draw rects
for (var i = 0; i < arr.length; i++) {
obj.ctx.save();
obj.ctx.strokeStyle = '#000';
obj.ctx.lineWidth = 2;
obj.ctx.strokeRect(arr[i][0], arr[i][1], arr[i][2], arr[i][3]);
obj.ctx.lineWidth = 1;
obj.ctx.strokeStyle = '#666';
obj.ctx.setLineDash([15, 15]);
obj.ctx.beginPath();
obj.ctx.moveTo(arr[i][0], arr[i][7]);
obj.ctx.lineTo(arr[i][4], arr[i][7]);
obj.ctx.moveTo(arr[i][6], arr[i][1]);
obj.ctx.lineTo(arr[i][6], arr[i][5]);
obj.ctx.stroke();
obj.ctx.restore();
}
}
return arr;
}
var dropMenus = [];
function dropMenu(obj) {
obj.open = false;
obj.selected = -1;
obj.buttonColor = def([obj.buttonColor, obj.selectedColor, '#3FF']);
obj.buttonBorderColor = def([obj.buttonBorderColor, '#000']);
obj.buttonBorder = boolean(obj.buttonBorder, true);
obj.showDownArrow = boolean(obj.showDownArrow, true);
obj.selectedColor = def([obj.selectedColor, '#3FF']);
obj.unselectedColor = def([obj.unselectedColor, '#CFF']);
obj.z = def([obj.z, 100000000]);
obj.listShowMax = def([obj.listShowMax, -1]);
obj.overflow = false;
obj.fullWidth = clone(obj.listRect[2]);
obj.font = def([obj.font, 'Arial']);
obj.fontSize = def([obj.fontSize, 16]);
obj.align = def([obj.align, 'left']);
obj.canvas1 = createCanvas(obj.buttonRect[0], obj.buttonRect[1], obj.buttonRect[2], obj.buttonRect[3], true, false, true, obj.z);
obj.canvas1.parent = obj;
obj.canvas1.click = function () {
var obj = this.parent;
obj.open = !obj.open;
if (obj.open == true) {
showObj(obj.canvas2);
showObj(obj.canvas3);
if (obj.overflow)
showScroller(obj.scroller);
addListenerMove(window, obj.move);
addListener(obj.canvas2, obj.click);
addListenerStart(window, obj.windowClickClose);
if (obj.overflow == true)
window.addEventListener("mousewheel", obj.mouseWheelHandler, false);
} else {
obj.close();
}
}
obj.canvas1.draw = function () {
var obj = this.parent;
var ctx = this.ctx;
var w = obj.buttonRect[2];
var h = obj.buttonRect[3];
ctx.clearRect(0, 0, w, h);
if (obj.buttonBorder == true) {
text({
ctx: obj.canvas1.ctx,
text: [obj.title],
left: 1.5,
top: 1.5,
width: w - 3,
height: h - 3,
align: 'center',
vertAlign: 'middle',
box: {
type: 'loose',
color: obj.buttonColor,
borderWidth: 3,
borderColor: obj.buttonBorderColor
}
});
} else {
text({
ctx: obj.canvas1.ctx,
text: [obj.title],
left: 1.5,
top: 1.5,
width: w - 3,
height: h - 3,
align: 'center',
vertAlign: 'middle'
});
}
if (obj.showDownArrow == true) {
ctx.fillStyle = '#000';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
var l = w - 15;
var t = h / 2;
ctx.beginPath();
ctx.moveTo(l - 8, t - 4);
ctx.lineTo(l + 8, t - 4);
ctx.lineTo(l, t + 8);
ctx.lineTo(l - 8, t - 4);
ctx.fill();
}
}
obj.canvas1.draw();
addListenerEnd(obj.canvas1, obj.canvas1.click);
obj.windowClickClose = function (e) {
for (var i = 0; i < dropMenus.length; i++) {
if (dropMenus[i].open == true) {
var obj = dropMenus[i];
if (e.target !== obj.canvas1 && e.target !== obj.canvas2 && (un(obj.scroller) || (e.target !== obj.scroller.canvas && e.target !== obj.scroller.sliderCanvas))) {
obj.close();
}
}
}
}
obj.close = function () {
var obj = this;
hideObj(obj.canvas2);
hideObj(obj.canvas3);
hideScroller(obj.scroller);
removeListenerMove(window, obj.move);
removeListener(obj.canvas2, obj.click);
removeListener(window, obj.windowClickClose);
if (obj.overflow == true)
window.removeEventListener("mousewheel", obj.mouseWheelHandler, false);
obj.selected = -1;
obj.open = false;
obj.draw();
}
obj.canvas2 = createCanvas(obj.listRect[0], obj.listRect[1], obj.listRect[2], obj.listRect[3], false, false, true, obj.z); // text - drawn once
obj.canvas2.parent = obj;
obj.canvas3 = createCanvas(obj.listRect[0], obj.listRect[1], obj.listRect[2], obj.listRect[3], false, false, false, obj.z + 1); // colors
obj.canvas3.parent = obj;
obj.scroller = createScroller({
rect: [obj.listRect[0] + obj.listRect[2] - 20, obj.listRect[1], 20, obj.listRect[3] * obj.listShowMax],
z: obj.z + 2,
min: 0,
max: obj.data.length - obj.listShowMax,
inc: 1,
sliderHeight: (obj.listRect[3] * obj.listShowMax - 2 * 20) * (obj.listShowMax / obj.data.length),
funcMove: function (value) {
var obj = this.parent;
value = roundToNearest(value, 1);
obj.draw(value);
removeListenerMove(window, obj.move);
removeListener(obj.canvas2, obj.click);
removeListener(window, obj.windowClickClose);
},
funcStop: function (value) {
var obj = this.parent;
value = roundToNearest(value, 1);
obj.draw(value);
addListenerMove(window, obj.move);
addListener(obj.canvas2, obj.click);
addListener(window, obj.windowClickClose);
}
});
obj.scroller.parent = obj;
obj.mouseWheelHandler = function (e) {
// cross-browser wheel delta
var e = window.event || e; // old IE support
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
for (var i = 0; i < dropMenus.length; i++) {
if (dropMenus[i].open == true) {
var obj = dropMenus[i];
setScrollerValue(obj.scroller, obj.scrollPos - delta, true);
break;
}
}
}
hideScroller(obj.scroller);
obj.updateData = function () {
var obj = this;
var height = this.listRect[3] * this.data.length;
if (this.listShowMax !== -1 && this.listShowMax < this.data.length) {
this.overflow = true;
this.listRect[2] = this.fullWidth - 20;
height = this.listRect[3] * this.listShowMax;
var s = this.scroller;
s.max = this.data.length - this.listShowMax;
s.sliderHeight = (height - 2 * 20) * (this.listShowMax / this.data.length);
s.incDist = (s.rect[3] - 2 * 20 - s.sliderHeight) / ((s.max - s.min) / s.inc);
s.sliderRect[3] = s.sliderHeight;
s.sliderCanvas.data[100] = s.sliderRect[0];
s.sliderCanvas.data[101] = s.sliderRect[1];
s.sliderCanvas.data[102] = s.sliderRect[2];
s.sliderCanvas.data[103] = s.sliderRect[3];
resizeCanvas(s.sliderCanvas, s.sliderCanvas.data[100], s.sliderCanvas.data[101], s.sliderCanvas.data[102], s.sliderCanvas.data[103]);
setScrollerValue(s, 0, true);
} else {
this.overflow = false;
this.listRect[2] = this.fullWidth;
}
this.scrollPos = 0;
this.canvas2.data[102] = this.listRect[2];
this.canvas2.width = this.listRect[2];
this.canvas2.data[103] = height;
this.canvas2.height = height;
resizeCanvas(this.canvas2, this.listRect[0], this.listRect[1], this.listRect[2], height);
this.canvas3.data[102] = this.listRect[2];
this.canvas3.width = this.listRect[2];
this.canvas3.data[103] = height;
this.canvas3.height = height;
resizeCanvas(this.canvas3, this.listRect[0], this.listRect[1], this.listRect[2], height);
obj.drawListText();
obj.draw();
}
obj.drawListText = function () {
var obj = this;
var top = 0;
if (obj.overflow)
top -= obj.scrollPos * obj.listRect[3];
var ctx = obj.canvas3.ctx;
for (var d = 0; d < obj.data.length; d++) {
text({
ctx: ctx,
text: ['<<font:' + this.font + '>><<fontSize:' + this.fontSize + '>>' + obj.data[d]],
left: 0,
top: top,
width: obj.listRect[2],
height: obj.listRect[3],
align: this.align,
vertAlign: 'middle',
box: {
color: 'none',
borderWidth: 0.01
}
});
top += obj.listRect[3];
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, top);
ctx.lineTo(obj.listRect[2], top);
ctx.stroke();
}
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(obj.listRect[2], 0);
ctx.moveTo(0, 0);
ctx.lineTo(0, top);
ctx.moveTo(obj.listRect[2], 0);
ctx.lineTo(obj.listRect[2], top);
ctx.stroke();
}
obj.draw = function (newScrollPos) {
var obj = this;
var ctx = this.canvas2.ctx;
if (this.overflow == true) {
if (!un(newScrollPos) && newScrollPos !== obj.scrollPos) {
obj.scrollPos = newScrollPos;
var ctx2 = this.canvas3.ctx;
ctx2.clearRect(0, 0, this.listRect[2], this.listRect[3] * this.listShowMax);
obj.drawListText();
}
ctx.fillStyle = this.unselectedColor;
ctx.fillRect(0, 0 - this.scrollPos * this.listRect[3], this.listRect[2], this.listRect[3] * this.data.length);
ctx.fillStyle = this.selectedColor;
ctx.fillRect(0, this.listRect[3] * this.selected - this.scrollPos * this.listRect[3], this.listRect[2], this.listRect[3]);
} else {
ctx.fillStyle = this.unselectedColor;
ctx.fillRect(0, 0, this.listRect[2], this.listRect[3] * this.data.length);
ctx.fillStyle = this.selectedColor;
ctx.fillRect(0, this.listRect[3] * this.selected, this.listRect[2], this.listRect[3]);
}
}
obj.updateData();
obj.move = function (e) {
updateMouse(e);
var found = false;
for (var i = 0; i < dropMenus.length; i++) {
if (dropMenus[i].open == true) {
obj = dropMenus[i];
var found = true;
break;
}
}
if (found == false)
return;
if (mouse.x < obj.listRect[0] || mouse.x > obj.listRect[0] + obj.listRect[2] || mouse.y < obj.listRect[1] || mouse.y > obj.listRect[1] + obj.data.length * obj.listRect[3]) {
if (obj.selected !== -1) {
obj.selected = -1;
obj.draw();
}
} else {
var sel = Math.floor((mouse.y - obj.listRect[1]) / obj.listRect[3]);
if (obj.overflow == true)
sel += obj.scrollPos;
if (obj.selected !== sel) {
obj.selected = sel;
obj.draw();
}
}
}
obj.click = function () {
var obj = this.parent;
if (!un(obj.func))
obj.func.apply();
obj.close();
}
resize();
obj.index = dropMenus.length;
dropMenus.push(obj); // place in global array
return obj;
}
function drawTable(context, lineWidth, lineColor, l, t, hLines, vLines) {
context.save();
context.beginPath();
context.lineWidth = lineWidth;
context.strokeStyle = lineColor;
context.lineCap = 'round';
// draw horizontal lines
for (h = 0; h < hLines.length; h++) {
context.moveTo(l + vLines[0], t + hLines[h]);
context.lineTo(l + vLines[vLines.length - 1], t + hLines[h]);
}
// draw vertical lines
for (v = 0; v < vLines.length; v++) {
context.moveTo(l + vLines[v], t + hLines[0]);
context.lineTo(l + vLines[v], t + hLines[hLines.length - 1]);
}
context.stroke();
context.restore();
}
function calcTable2(object) {
var left = object.left;
var top = object.top;
var cells = object.cells;
var minCellWidth = object.minCellWidth || 80;
var maxCellWidth = object.maxCellWidth || 150;
var minCellHeight = object.minCellHeight || 100;
var minCellPadding = object.minCellPadding || 10;
var horizAlign = object.horizAlign || object.align || 'center';
if (typeof object.text == 'object') {
var font = object.text.font || 'Arial';
var fontSize = object.text.size || 32;
var textColor = object.text.color || '#000';
} else {
var font = 'Arial';
var fontSize = 32;
var textColor = '#000';
}
var numRows = cells.length;
var numCols = 0;
for (var i = 0; i < cells.length; i++) {
numCols = Math.max(cells[i].length, numCols);
}
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
for (var i = 0; i < cells.length; i++) {
var maxHeight = minCellHeight;
for (var j = 0; j < cells[i].length; j++) {
if (typeof cells[i][j] == 'object') {
if (typeof cells[i][j].text !== 'object')
cells[i][j].text = [];
if (typeof cells[i][j].minWidth !== 'number')
cells[i][j].minWidth = 0;
if (typeof cells[i][j].minHeight !== 'number')
cells[i][j].minHeight = 0;
cells[i][j].text.unshift('<<font:' + font + '>><<fontSize:' + fontSize + '>><<color:' + textColor + '>>');
cells[i][j].text = reduceTags(cells[i][j].text);
var dims = drawMathsText(ctx, cells[i][j].text, fontSize, 0, 0, false, [], horizAlign, 'middle', text.color, 'measure');
maxHeight = Math.max(dims[1] + 2 * minCellPadding, cells[i][j].minHeight, maxHeight);
cellWidths[j] = Math.max(dims[0] + 2 * minCellPadding, cells[i][j].minWidth, cellWidths[j]);
}
}
cellHeights[i] = Math.max(maxHeight, cellHeights[i]);
totalHeight += cellHeights[i];
}
for (var j = 0; j < cellWidths.length; j++) {
totalWidth += cellWidths[j];
}
var horizPos = [left];
for (var i = 0; i < cellWidths.length; i++) {
horizPos.push(horizPos[horizPos.length - 1] + cellWidths[i])
}
var vertPos = [top];
for (var i = 0; i < cellHeights.length; i++) {
vertPos.push(vertPos[vertPos.length - 1] + cellHeights[i])
}
var cellDims = [];
var topPos = top;
for (var i = 0; i < cells.length; i++) {
var leftPos = left;
cellDims[i] = [];
for (var j = 0; j < cells[i].length; j++) {
cellDims[i][j] = {
left: leftPos + 2,
top: topPos + 1,
width: cellWidths[j] - 9,
height: cellHeights[i] - 8,
border: false,
offset: [-40, 0.5 * (cellHeights[i] - 8) - 20],
fontSize: fontSize,
leftPoint: minCellPadding,
textColor: textColor,
textAlign: horizAlign,
fontSize: fontSize
};
leftPos += cellWidths[j];
}
topPos += cellHeights[i];
}
return {
cell: cellDims,
xPos: horizPos,
yPos: vertPos
};
}
function drawTable2(object) {
/* EXAMPLE USAGE:
var j0001table1 = drawTable2({
ctx:j0001buttonctx[0],
left:100,
top:150,
minCellWidth:80,
minCellHeight:50,
horizAlign:'center',
text:{font:'Arial',size:32,color:'#000'},
outerBorder:{show:true,width:4,color:'#000'},
innerBorder:{show:true,width:2,color:'#666',dash:[5,5]},
cells:[
[ // row 0{text:['<<fontSize:36>><<font:algebra>>x'],color:'#CCF',minWidth:100,minHeight:70},{text:['<<fontSize:36>><<font:algebra>>y'],color:'#CCF',minWidth:100,minHeight:70},{text:['<<fontSize:36>><<font:algebra>><<color:#F00>>z'],color:'#CCF',minWidth:100,minHeight:70},
], [ // row 1{},{text:['2']},{text:['<<color:#F00>>3']},
], [ // row 2{text:['4']},{},{text:['<<color:#F00>>6']},
],
]
});
// CAN EASILY USE IN CONJUNCTION WITH INPUTS()...
inputs({
inputs:[
// j0001table1.cell[row][col] - nb. start counting from zero
j0001table1.cell[1][0],
j0001table1.cell[2][1]
],
checkFuncs:[
function(input) {
if (input.stringJS == '1') {
return true;
} else {
return false;
}
},
function(input) {
if (input.stringJS == '5') {
return true;
} else {
return false;
}
},
]
});
*/
if (typeof object.sf !== 'undefined') {
var sf = object.sf;
} else {
var sf = 1;
}
var ctx = object.ctx || object.context;
var left = object.left * sf;
var top = object.top * sf;
var cells = object.cells;
var minCellWidth = object.minCellWidth * sf || 80 * sf;
var maxCellWidth = object.maxCellWidth * sf || Math.max(1200 * sf, object.minCellWidth * sf);
var minCellHeight = object.minCellHeight * sf || 100 * sf;
var minCellPadding = object.minCellPadding * sf || 7 * sf;
var paddingH = minCellPadding;
var paddingV = minCellPadding;
if (typeof object.paddingH == 'number')
paddingH = object.paddingH * sf;
if (typeof object.paddingV == 'number')
paddingV = object.paddingV * sf;
var horizAlign = object.horizAlign || object.align || 'center';
if (typeof object.text == 'object') {
var font = object.text.font || 'Arial';
var fontSize = object.text.size || 32;
var textColor = object.text.color || '#000';
} else {
var font = 'Arial';
var fontSize = 32;
var textColor = '#000';
}
if (typeof object.alpha == 'number') {
var alpha = object.alpha;
} else {
var alpha = 1;
}
var lineJoin = object.lineJoin || object.lineCap || 'round';
var lineCap = object.lineCap || object.lineJoin || 'round';
var outerBorder = {};
if (typeof object.outerBorder == 'object') {
outerBorder.show = boolean(object.outerBorder.show, true);
outerBorder.width = object.outerBorder.width * sf || 4 * sf;
if (typeof object.outerBorder.color == 'undefined') {
outerBorder.color = colorA('#000', alpha);
} else {
outerBorder.color = colorA(object.outerBorder.color, alpha);
}
outerBorder.dash = object.outerBorder.dash || [];
} else {
outerBorder.show = true;
outerBorder.width = 4 * sf;
outerBorder.color = colorA('#000', alpha);
outerBorder.dash = [];
}
outerBorder.dash = enlargeDash(outerBorder.dash, sf);
var innerBorder = {};
if (typeof object.innerBorder == 'object') {
innerBorder.show = boolean(object.innerBorder.show, true);
innerBorder.width = object.innerBorder.width * sf || 4 * sf;
if (typeof object.innerBorder.color == 'undefined') {
innerBorder.color = colorA('#000', alpha);
} else {
innerBorder.color = colorA(object.innerBorder.color, alpha);
}
innerBorder.dash = object.innerBorder.dash || [];
} else {
innerBorder.show = true;
innerBorder.width = 4 * sf;
innerBorder.color = colorA('#000', alpha);
innerBorder.dash = [20, 15];
}
innerBorder.dash = enlargeDash(innerBorder.dash, sf);
var tableAlignHoriz = object.tableAlignHoriz || 'left'; // is the whole table centred on [left,top]?
var tableAlignVert = object.tableAlignVert || 'top';
var numRows = cells.length;
var numCols = 0;
for (var i = 0; i < cells.length; i++) {
numCols = Math.max(cells[i].length, numCols);
}
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
if (typeof hiddenCanvas == 'undefined') {
var hiddenCanvas = document.createElement('canvas');
hiddenCanvas.width = mainCanvasWidth * sf;
hiddenCanvas.height = mainCanvasHeight * sf;
hiddenCanvas.ctx = hiddenCanvas.getContext('2d');
}
for (var r = 0; r < cells.length; r++) {
var maxHeight = minCellHeight;
for (var c = 0; c < cells[r].length; c++) {
if (typeof cells[r][c] == 'object') {
if (typeof cells[r][c].text !== 'object') {
cells[r][c].text = [];
} else {
cells[r][c].text = clone(cells[r][c].text);
}
if (typeof cells[r][c].color !== 'string')
cells[r][c].color = 'none';
if (typeof cells[r][c].minWidth !== 'number')
cells[r][c].minWidth = 0;
if (typeof cells[r][c].minHeight !== 'number')
cells[r][c].minHeight = 0;
var font2 = font;
var fontSize2 = fontSize;
var textColor2 = textColor;
if (!un(cells[r][c].font))
font2 = cells[r][c].font;
if (!un(cells[r][c].fontSize))
fontSize2 = cells[r][c].fontSize;
if (!un(cells[r][c].textColor))
textColor2 = cells[r][c].textColor;
if (un(cells[r][c].styled)) {
cells[r][c].text.unshift('<<font:' + font2 + '>><<fontSize:' + fontSize2 + '>><<color:' + textColor2 + '>>');
cells[r][c].styled = true;
}
//var dims = drawMathsText(ctx,cells[r][c].text,fontSize,0,0,false,[],horizAlign,'middle',text.color,'measure');
//maxHeight = Math.max(dims[1]+2*minCellPadding,cells[r][c].minHeight,maxHeight);
//cellWidths[c] = Math.max(dims[0]+2*minCellPadding,cells[r][c].minWidth,cellWidths[c]);
var cellText = text({
ctx: hiddenCanvas.ctx,
left: 0,
top: 0,
width: maxCellWidth,
textArray: cells[r][c].text,
minTightWidth: 5,
minTightHeight: 5,
box: cells[r][c].box,
sf: sf
});
maxHeight = Math.max(cellText.tightRect[3] + 2 * paddingV, cells[r][c].minHeight * sf, maxHeight);
cellWidths[c] = Math.max(cellText.tightRect[2] + 3 * paddingH, cells[r][c].minWidth * sf, cellWidths[c]);
}
}
cellHeights[r] = Math.max(maxHeight, cellHeights[r]);
totalHeight += cellHeights[r];
}
for (var j = 0; j < cellWidths.length; j++) {
totalWidth += cellWidths[j];
}
if (tableAlignHoriz == 'center') {
left = left - totalWidth / 2;
} else if (tableAlignHoriz == 'right') {
left = left - totalWidth;
}
if (tableAlignVert == 'middle') {
top = top - totalHeight / 2;
} else if (tableAlignVert == 'bottom') {
top = top - totalHeight;
}
ctx.save();
ctx.lineCap = lineCap;
ctx.lineJoin = lineJoin;
var cellDims = [];
var horizPos = [left];
for (var i = 0; i < cellWidths.length; i++) {
horizPos.push(horizPos[horizPos.length - 1] + cellWidths[i])
}
var vertPos = [top];
for (var i = 0; i < cellHeights.length; i++) {
vertPos.push(vertPos[vertPos.length - 1] + cellHeights[i])
}
// write text to each cell
var topPos = top;
for (var i = 0; i < cells.length; i++) {
var leftPos = left;
cellDims[i] = [];
for (var j = 0; j < cells[i].length; j++) {
cellDims[i][j] = {
left: leftPos + 2,
top: topPos + 1,
width: cellWidths[j] - 9,
height: cellHeights[i] - 8,
border: false,
offset: [-40, 0.5 * (cellHeights[i] - 8) - 20],
fontSize: fontSize,
leftPoint: paddingH,
textColor: textColor,
textAlign: horizAlign,
fontSize: fontSize
};
if (cells[i][j].highlight == true) {
if (typeof cells[i][j].color == 'undefined' || cells[i][j].color !== 'none') {
ctx.fillStyle = colorA(invertColor(cells[i][j].color), alpha);
} else {
ctx.fillStyle = colorA(invertColor('#FFC'), alpha);
}
ctx.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
} else if (typeof cells[i][j].color == 'undefined' || cells[i][j].color !== 'none') {
ctx.fillStyle = colorA(cells[i][j].color, alpha);
ctx.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
}
/*if (horizAlign == 'left') {
var dims = drawMathsText(ctx,cells[i][j].text,fontSize,leftPos+minCellPadding,topPos+0.5*cellHeights[i],false,[],horizAlign,'middle',textColor);
} else if (horizAlign == 'center') {
var dims = drawMathsText(ctx,cells[i][j].text,fontSize,leftPos+0.5*cellWidths[j],topPos+0.5*cellHeights[i],false,[],horizAlign,'middle',textColor);
} else if (horizAlign == 'right') {
var dims = drawMathsText(ctx,cells[i][j].text,fontSize,leftPos+cellWidths[j]-minCellPadding,topPos+0.5*cellHeights[i],false,[],horizAlign,'middle',textColor);
}*/
var align = horizAlign;
if (!un(cells[i][j].align))
align = cells[i][j].align;
var cellText = text({
ctx: ctx,
left: leftPos + paddingH,
top: topPos + paddingV,
width: cellWidths[j] - 2 * paddingH,
height: cellHeights[i] - 2 * paddingV,
textArray: cells[i][j].text,
textAlign: align,
vertAlign: 'middle',
padding: 0.001,
box: cells[i][j].box,
sf: sf
//box:{type:'tight'}
});
//console.log(cellText.tightRect[2],cellText.tightRect[3]);
leftPos += cellWidths[j];
}
topPos += cellHeights[i];
}
// draw inner border
if (innerBorder.show == true) {
ctx.strokeStyle = innerBorder.color;
ctx.lineWidth = innerBorder.width;
if (!ctx.setLineDash) {
ctx.setLineDash = function () {}
}
ctx.setLineDash(innerBorder.dash);
var leftPos = left;
for (var i = 0; i < cellWidths.length - 1; i++) {
leftPos += cellWidths[i];
ctx.beginPath();
ctx.moveTo(leftPos, top);
ctx.lineTo(leftPos, top + totalHeight);
ctx.stroke();
}
var topPos = top;
for (var i = 0; i < cellHeights.length - 1; i++) {
topPos += cellHeights[i];
ctx.beginPath();
ctx.moveTo(left, topPos);
ctx.lineTo(left + totalWidth, topPos);
ctx.stroke();
}
}
// draw outer border
if (outerBorder.show == true) {
ctx.strokeStyle = outerBorder.color;
ctx.lineWidth = outerBorder.width;
if (!ctx.setLineDash) {
ctx.setLineDash = function () {}
}
ctx.setLineDash(outerBorder.dash);
ctx.beginPath();
ctx.strokeRect(left, top, totalWidth, totalHeight);
}
ctx.restore();
return {
cell: cellDims,
xPos: horizPos,
yPos: vertPos
};
}
function drawTable3(object) {
var sf = typeof object.sf !== 'undefined' ? object.sf : 1;
var ctx = object.ctx || object._ctx || object.context;
var left = object.left * sf;
var top = object.top * sf;
var cells = object.cells;
var widths = clone(object.widths);
var heights = clone(object.heights);
if (sf !== 1) {
for (var w = 0; w < widths.length; w++) widths[w] = widths[w] * sf;
for (var h = 0; h < heights.length; h++) heights[h] = heights[h] * sf;
}
var minCellPadding = object.minCellPadding * sf || 0;
var paddingH = minCellPadding;
var paddingV = minCellPadding;
if (typeof object.paddingH == 'number') paddingH = object.paddingH * sf;
if (typeof object.paddingV == 'number') paddingV = object.paddingV * sf;
//var innerPaddingH = !un(obj.innerPaddingH) ? obj.innerPaddingH*sf : 0;
//var innerPaddingV = !un(obj.innerPaddingV) ? obj.innerPaddingV*sf : 0;
var horizAlign = object.horizAlign || object.align || 'center';
var alpha = typeof object.alpha == 'number' ? object.alpha : 1;
var lineJoin = object.lineJoin || object.lineCap || 'round';
var lineCap = object.lineCap || object.lineJoin || 'round';
var outerBorder = {};
if (typeof object.outerBorder == 'object') {
outerBorder.show = boolean(object.outerBorder.show, true);
outerBorder.width = object.outerBorder.width * sf || 4 * sf;
if (typeof object.outerBorder.color == 'undefined') {
outerBorder.color = colorA('#000', alpha);
} else {
outerBorder.color = colorA(object.outerBorder.color, alpha);
}
outerBorder.dash = object.outerBorder.dash || [];
outerBorder.radius = object.outerBorder.radius*sf || 0;
} else {
outerBorder.show = true;
outerBorder.width = 4 * sf;
outerBorder.color = colorA('#000', alpha);
outerBorder.dash = [];
outerBorder.radius = 0;
}
outerBorder.dash = enlargeDash(outerBorder.dash, sf);
var innerBorder = {};
if (typeof object.innerBorder == 'object') {
innerBorder.show = boolean(object.innerBorder.show, true);
innerBorder.width = object.innerBorder.width * sf || 4 * sf;
if (typeof object.innerBorder.color == 'undefined') {
innerBorder.color = colorA('#000', alpha);
} else {
innerBorder.color = colorA(object.innerBorder.color, alpha);
}
innerBorder.dash = object.innerBorder.dash || [];
innerBorder.dash = enlargeDash(innerBorder.dash, sf);
} else {
innerBorder.show = false;
/*innerBorder.show = true;
innerBorder.width = 4 * sf;
innerBorder.color = colorA('#000', alpha);
innerBorder.dash = [20, 15];
innerBorder.dash = enlargeDash(innerBorder.dash, sf);*/
}
var tableAlignHoriz = object.tableAlignHoriz || 'center'; // is the whole table centred on [left,top]?
var tableAlignVert = object.tableAlignVert || 'middle';
if (!un(object.align)) {
tableAlignHoriz = object.align[0] == -1 ? 'left' : object.align[0] == 0 ? 'center' : 'right';
tableAlignVert = object.align[1] == -1 ? 'top' : object.align[1] == 0 ? 'middle' : 'bottom';
}
if (typeof object.text == 'object') {
var font = object.text.font || 'Arial';
var fontSize = object.text.size || 28;
var textColor = object.text.color || '#000';
} else {
var font = 'Arial';
var fontSize = 28;
var textColor = '#000';
}
var fracScale = object.fracScale;
var algPadding = object.algPadding;
var totalWidth = arraySum(widths);
var totalHeight = arraySum(heights);
ctx.save();
ctx.lineCap = lineCap;
ctx.lineJoin = lineJoin;
var cellDims = [];
var horizPos = [left];
for (var i = 0; i < widths.length; i++) {
horizPos.push(horizPos[horizPos.length - 1] + widths[i])
}
var vertPos = [top];
for (var i = 0; i < heights.length; i++) {
vertPos.push(vertPos[vertPos.length - 1] + heights[i])
}
// color, inner border & text for cells
var topPos = top;
var cellTextMeasure = [];
for (var i = 0; i < cells.length; i++) {
var leftPos = left;
cellDims[i] = [];
var skipCells = 0;
cellTextMeasure[i] = [];
for (var j = 0; j < cells[i].length; j++) {
var cell = cells[i][j];
if (skipCells > 0) {
skipCells--;
continue;
}
if (un(cell.colSpan)) {
var cellWidth = widths[j];
} else {
var cellWidth = 0;
for (var k = j; k < Math.min(j + cell.colSpan, cells[i].length); k++) {
cellWidth += widths[k];
}
skipCells = cell.colSpan - 1;
}
var cellPaddingH = def([cell.paddingH, cell.padding, paddingH]);
var cellPaddingV = def([cell.paddingV, cell.padding, paddingV]);
cellDims[i][j] = {
left: leftPos,
top: topPos,
width: cellWidth,
height: heights[i],
border: false,
offset: [-40, 0.5 * (heights[i]) - 20],
leftPoint: cellPaddingH,
};
var c1 = (typeof cell.color !== 'undefined' && cell.color !== 'none') ? true : false;
var c2 = (!un(cell.box) && cell.box.show == true) ? true : false;
var hl = cell.highlight;
/*if (!un(object.isInput)) {
if (draw.mode == 'interact') {
var selected = boolean(cell.toggle, false);
} else {
var selected = boolean(cell.ans, false);
}
if (!un(cell.selColors)) {
var isInputColor = selected ? cell.selColors[1] : cell.selColors[0];
} else if (!un(object.isInput.selColors)) {
var isInputColor = selected ? object.isInput.selColors[1] : object.isInput.selColors[0];
} else {
var isInputColor = selected ? '#66F' : '#CCF';
}
*/
if (c2 == true) {
var box = cell.box;
//var fillColor = isInputColor || box.fillColor || box.color || undefined;
var fillColor = box.fillColor || box.color || undefined;
var lineColor = box.borderColor || box.lineColor || undefined;
if (hl == true) {
if (!un(fillColor) && fillColor !== 'none')
fillColor = colorA(invertColor(fillColor), alpha);
if (!un(lineColor) && lineColor !== 'none')
lineColor = colorA(invertColor(lineColor), alpha);
}
var lineWidth = box.borderWidth || box.lineWidth || box.width || 3;
lineWidth = lineWidth * sf;
var dash = def([box.dash, []]);
var radius = box.borderRadius || box.radius || 0;
radius = radius * sf;
roundedRect(ctx, leftPos + cellPaddingH, topPos + cellPaddingV, cellWidth - 2 * cellPaddingH, heights[i] - 2 * cellPaddingV, radius, lineWidth, lineColor, fillColor, dash);
} else if (c1 == true) {
//var fillColor = isInputColor || cell.color;
var fillColor = cell.color;
if (hl == true) {
fillColor = colorA(invertColor(fillColor), alpha);
} else {
fillColor = colorA(fillColor, alpha);
}
/*ctx.fillStyle = fillColor;
ctx.globalCompositeOperation = 'destination-over'; // draw behind existing content
ctx.fillRect(leftPos,topPos,cellWidth,heights[i]);
ctx.globalCompositeOperation = 'source-over'; // default*/
roundedRect(ctx, leftPos + cellPaddingH, topPos + cellPaddingV, cellWidth - 2 * cellPaddingH, heights[i] - 2 * cellPaddingV, 0, 0, 'none', fillColor);
} else {
if (hl == true) {
ctx.fillStyle = colorA(invertColor(mainCanvasFillStyle), alpha);
ctx.globalCompositeOperation = 'destination-over'; // draw behind existing content
ctx.fillRect(leftPos, topPos, cellWidth, heights[i]);
ctx.globalCompositeOperation = 'source-over'; // default
}
}
if (innerBorder.show == true) {
ctx.strokeStyle = innerBorder.color;
ctx.lineWidth = innerBorder.width;
if (un(ctx.setLineDash)) {
ctx.setLineDash = function () {}
}
ctx.setLineDash(innerBorder.dash);
ctx.beginPath();
if (i > 0) {
ctx.moveTo(leftPos, topPos);
ctx.lineTo(leftPos + cellWidth, topPos);
}
if (i < cells.length - 1) {
ctx.moveTo(leftPos, topPos + heights[i]);
ctx.lineTo(leftPos + cellWidth, topPos + heights[i]);
}
if (j > 0) {
ctx.moveTo(leftPos, topPos);
ctx.lineTo(leftPos, topPos + heights[i]);
}
if (j < cells[i].length - 1 && (un(cell.colSpan) || j + cell.colSpan < cells[i].length - 1)) {
ctx.moveTo(leftPos + cellWidth, topPos);
ctx.lineTo(leftPos + cellWidth, topPos + heights[i]);
}
ctx.stroke();
}
if (!un(cell.text)) {
var txt = clone(cell.text);
var align = [0, 0];
if (tableAlignHoriz == 'left') align[0] = -1;
if (tableAlignHoriz == 'center') align[0] = 0;
if (tableAlignHoriz == 'right') align[0] = 1;
if (tableAlignVert == 'top') align[1] = -1;
if (tableAlignVert == 'middle') align[1] = 0;
if (tableAlignVert == 'bottom') align[1] = 1;
if (cell.align == 'left') align[0] = -1;
if (cell.align == 'center') align[0] = 0;
if (cell.align == 'right') align[0] = 1;
if (cell.vertAlign == 'top') align[1] = -1;
if (cell.vertAlign == 'middle') align[1] = 0;
if (cell.vertAlign == 'bottom') align[1] = 1;
if (typeof cell.align == 'object') align = cell.align;
var font2 = def([cell.font, font]);
var fontSize2 = def([cell.fontSize, fontSize]);
var textColor2 = def([cell.textColor, textColor]);
var italic2 = def([cell.italic, false]);
var bold2 = def([cell.bold, false]);
var paddingH2 = def([cell.paddingH, cell.padding, paddingH]);
var paddingV2 = def([cell.paddingV, cell.padding, paddingV]);
var fracScale = def([cell.fracScale, fracScale]);
var algPadding = def([cell.algPadding, algPadding]);
var marginLeft = def([cell.marginLeft, object.marginLeft, 0]);
var marginRight = def([cell.marginRight, object.marginRight, 0]);
var lineSpacingFactor = def([cell.lineSpacingFactor, object.lineSpacingFactor, 1.2]);
var lineSpacingStyle = def([cell.lineSpacingStyle, cell.spacingStyle, object.lineSpacingStyle, object.spacingStyle, "variable"]);
var backgroundColor = typeof cell.color !== 'undefined' && cell.color !== 'none' ? cell.color : '#FFF';
var box = clone(cell.box);
if (!un(box) && typeof isInputColor !== 'undefined') box.color = isInputColor;
cellTextMeasure[i][j] = text({
ctx: ctx,
rect: [leftPos + paddingH2, topPos + paddingV2, cellWidth - 2 * paddingH2, heights[i] - 2 * paddingV2],
text: txt,
box: box,
sf: sf,
align: align,
font: font2,
fontSize: fontSize2,
color: textColor2,
italic: italic2,
bold: bold2,
selected: hl,
backgroundColor: backgroundColor,
fracScale:fracScale,
algPadding:algPadding,
marginLeft:marginLeft,
marginRight:marginRight,
lineSpacingFactor:lineSpacingFactor,
lineSpacingStyle:lineSpacingStyle
});
}
leftPos += cellWidth;
}
topPos += heights[i];
}
// draw outer border
if (outerBorder.show == true) {
/*ctx.strokeStyle = outerBorder.color;
ctx.lineWidth = outerBorder.width;
if (un(ctx.setLineDash)) {
ctx.setLineDash = function () {}
}
ctx.setLineDash(outerBorder.dash);
ctx.beginPath();
ctx.strokeRect(left, top, totalWidth, totalHeight);*/
var color = outerBorder.color || '#000';
var width = outerBorder.width || 4;
var radius = outerBorder.radius || 0;
var dash = outerBorder.dash || [0,0];
roundedRect(ctx,left,top,totalWidth,totalHeight,radius,width,color,'none',dash)
}
ctx.restore();
return {
cellTextMeasure: cellTextMeasure,
cell: cellDims,
xPos: horizPos,
yPos: vertPos
};
}
function createScrollTable(object) {
var left = object.left;
var top = object.top;
var z = object.z || object.zIndex || 2;
var padding = 2; // padding for canvas
var fullRect = [0, 0, 100, 100];
var visRect = [left, top, 100, 100];
var topRowRect = [left, top, 100, 100];
var scrollRect = [left, top, 25, 10];
var ctxVis = newctx({
rect: visRect,
z: z
});
var ctxTopRow = newctx({
rect: topRowRect,
z: z
});
var ctxInvis = newctx({
rect: fullRect,
vis: false
});
var topRowFreeze = boolean(object.topRowFreeze, true);
var scrollMax = 10;
var scrollDiff = 5;
var scroll = createScroller({
rect: scrollRect,
max: scrollMax,
zIndex: z,
funcMove: function (value) {
this.table.scrollPos = (value / this.table.scrollMax) * this.table.scrollDiff;
this.table.redraw();
},
funcStop: function (value) {
this.table.scrollPos = (value / this.table.scrollMax) * this.table.scrollDiff;
this.table.redraw();
}
});
if (!un(object.funcMove)) {
ctxVis.canvas.style.pointerEvents = 'auto';
ctxVis.data[6] = true;
ctxVis.data[106] = true;
addListenerMove(ctxVis.canvas, function (e) {
updateMouse(e);
var r = -1,
c = -1;
for (var x = 0; x < this.table.xPos.length - 1; x++) {
if (mouse.x >= this.table.xPos[x] + this.table.ctxVis.data[100] && mouse.x <= this.table.xPos[x + 1] + this.table.ctxVis.data[100]) {
c = x;
break;
}
}
if (this.table.topRowFreeze && mouse.y <= this.table.yPos[1] + this.table.ctxVis.data[101]) {
r = 0;
} else {
for (var y = 0; y < this.table.yPos.length - 1; y++) {
if (mouse.y + this.table.scrollPos >= this.table.yPos[y] + this.table.ctxVis.data[101] && mouse.y + this.table.scrollPos <= this.table.yPos[y + 1] + this.table.ctxVis.data[101]) {
r = y;
break;
}
}
}
this.table.funcMove(r, c);
});
} else {
object.funcMove = function () {};
}
if (!un(object.funcClick)) {
ctxVis.canvas.style.pointerEvents = 'auto';
ctxVis.data[6] = true;
ctxVis.data[106] = true;
addListener(ctxVis.canvas, function (e) {
updateMouse(e);
var r = -1,
c = -1,
xProp = 0,
yProp = 0;
for (var x = 0; x < this.table.xPos.length - 1; x++) {
if (mouse.x >= this.table.xPos[x] + this.table.ctxVis.data[100] && mouse.x <= this.table.xPos[x + 1] + this.table.ctxVis.data[100]) {
c = x;
xProp = (mouse.x - (this.table.xPos[x] + this.table.ctxVis.data[100])) / (this.table.xPos[x + 1] - this.table.xPos[x]);
break;
}
}
if (this.table.topRowFreeze && mouse.y <= this.table.yPos[1] + this.table.ctxVis.data[101]) {
r = 0;
} else {
for (var y = 0; y < this.table.yPos.length - 1; y++) {
if (mouse.y + this.table.scrollPos >= this.table.yPos[y] + this.table.ctxVis.data[101] && mouse.y + this.table.scrollPos <= this.table.yPos[y + 1] + this.table.ctxVis.data[101]) {
r = y;
yProp = (mouse.y - (this.table.yPos[y] + this.table.ctxVis.data[101])) / (this.table.yPos[y + 1] - this.table.yPos[y]);
break;
}
}
}
this.table.funcClick(r, c, xProp, yProp);
});
} else {
object.funcClick = function () {};
}
object.ctxVis = ctxVis;
object.ctxInvis = ctxInvis;
object.ctxTopRow = ctxTopRow;
object.padding = padding;
object.scroller = scroll;
object.scrollPos = 0;
object.scrollMax = scrollMax;
object.scrollDiff = scrollDiff;
object.topRowFreeze = topRowFreeze;
object.redraw = function () {
this.ctxVis.clearRect(0, 0, this.ctxVis.canvas.data[102], this.ctxVis.canvas.data[103]);
this.ctxVis.drawImage(this.ctxInvis.canvas, 0, -this.scrollPos);
};
object.scroller.table = object;
object.ctxVis.canvas.table = object;
if (!un(object.additionalDraw)) {
object.additionalDraw();
}
object.refreshCells = function () {
var sf = def([this.sf, 1]);
var left = this.left * sf;
var top = this.top * sf;
var cells = this.cells;
var z = this.z || this.zIndex || 2;
var minCellWidth = this.minCellWidth * sf || 80 * sf;
var maxCellWidth = this.maxCellWidth * sf || Math.max(1200 * sf, this.minCellWidth * sf);
var minCellHeight = this.minCellHeight * sf || 100 * sf;
var minCellPadding = this.minCellPadding * sf || 7 * sf;
var paddingH = minCellPadding;
var paddingV = minCellPadding;
if (typeof this.paddingH == 'number')
paddingH = this.paddingH * sf;
if (typeof this.paddingV == 'number')
paddingV = this.paddingV * sf;
var horizAlign = this.horizAlign || this.align || 'center';
if (typeof this.text == 'this') {
var font = this.text.font || 'Arial';
var fontSize = this.text.size || 32;
var textColor = this.text.color || '#000';
} else {
var font = 'Arial';
var fontSize = 32;
var textColor = '#000';
}
if (typeof this.alpha == 'number') {
var alpha = this.alpha;
} else {
var alpha = 1;
}
var lineJoin = this.lineJoin || this.lineCap || 'round';
var lineCap = this.lineCap || this.lineJoin || 'round';
var outerBorder = {};
if (typeof this.outerBorder == 'object') {
outerBorder.show = boolean(this.outerBorder.show, true);
outerBorder.width = this.outerBorder.width * sf || 4 * sf;
if (typeof this.outerBorder.color == 'undefined') {
outerBorder.color = colorA('#000', alpha);
} else {
outerBorder.color = colorA(this.outerBorder.color, alpha);
}
outerBorder.dash = this.outerBorder.dash || [];
} else {
outerBorder.show = true;
outerBorder.width = 4 * sf;
outerBorder.color = colorA('#000', alpha);
outerBorder.dash = [];
}
outerBorder.dash = enlargeDash(outerBorder.dash, sf);
var innerBorder = {};
if (typeof this.innerBorder == 'object') {
innerBorder.show = boolean(this.innerBorder.show, true);
innerBorder.width = this.innerBorder.width * sf || 4 * sf;
if (typeof this.innerBorder.color == 'undefined') {
innerBorder.color = colorA('#000', alpha);
} else {
innerBorder.color = colorA(this.innerBorder.color, alpha);
}
innerBorder.dash = this.innerBorder.dash || [];
} else {
innerBorder.show = true;
innerBorder.width = 4 * sf;
innerBorder.color = colorA('#000', alpha);
innerBorder.dash = [20, 15];
}
innerBorder.dash = enlargeDash(innerBorder.dash, sf);
var tableAlignHoriz = this.tableAlignHoriz || 'left'; // is the whole table centred on [left,top]?
var tableAlignVert = this.tableAlignVert || 'top';
var numRows = cells.length;
var numCols = 0;
for (var i = 0; i < cells.length; i++) {
numCols = Math.max(cells[i].length, numCols);
}
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
if (typeof hiddenCanvas == 'undefined') {
var hiddenCanvas = document.createElement('canvas');
hiddenCanvas.width = mainCanvasWidth * sf;
hiddenCanvas.height = mainCanvasHeight * sf;
hiddenCanvas.ctx = hiddenCanvas.getContext('2d');
}
for (var r = 0; r < cells.length; r++) {
var maxHeight = minCellHeight;
for (var c = 0; c < cells[r].length; c++) {
if (typeof cells[r][c] == 'this') {
if (typeof cells[r][c].text !== 'this') {
cells[r][c].text = [];
} else {
cells[r][c].text = clone(cells[r][c].text);
}
if (typeof cells[r][c].color !== 'string')
cells[r][c].color = 'none';
if (typeof cells[r][c].minWidth !== 'number')
cells[r][c].minWidth = 0;
if (typeof cells[r][c].minHeight !== 'number')
cells[r][c].minHeight = 0;
var font2 = font;
var fontSize2 = fontSize;
var textColor2 = textColor;
if (!un(cells[r][c].font))
font2 = cells[r][c].font;
if (!un(cells[r][c].fontSize))
fontSize2 = cells[r][c].fontSize;
if (!un(cells[r][c].textColor))
textColor2 = cells[r][c].textColor;
if (un(cells[r][c].styled)) {
cells[r][c].text.unshift('<<font:' + font2 + '>><<fontSize:' + fontSize2 + '>><<color:' + textColor2 + '>>');
cells[r][c].styled = true;
}
//var dims = drawMathsText(ctx,cells[r][c].text,fontSize,0,0,false,[],horizAlign,'middle',text.color,'measure');
//maxHeight = Math.max(dims[1]+2*minCellPadding,cells[r][c].minHeight,maxHeight);
//cellWidths[c] = Math.max(dims[0]+2*minCellPadding,cells[r][c].minWidth,cellWidths[c]);
var cellText = text({
ctx: hiddenCanvas.ctx,
left: 0,
top: 0,
width: maxCellWidth,
textArray: cells[r][c].text,
minTightWidth: 5,
minTightHeight: 5,
box: cells[r][c].box,
sf: sf
});
maxHeight = Math.max(cellText.tightRect[3] + 2 * paddingV, cells[r][c].minHeight * sf, maxHeight);
cellWidths[c] = Math.max(cellText.tightRect[2] + 3 * paddingH, cells[r][c].minWidth * sf, cellWidths[c]);
}
}
cellHeights[r] = Math.max(maxHeight, cellHeights[r]);
totalHeight += cellHeights[r];
}
for (var j = 0; j < cellWidths.length; j++) {
totalWidth += cellWidths[j];
}
if (tableAlignHoriz == 'center') {
left = left - totalWidth / 2;
} else if (tableAlignHoriz == 'right') {
left = left - totalWidth;
}
if (tableAlignVert == 'middle') {
top = top - totalHeight / 2;
} else if (tableAlignVert == 'bottom') {
top = top - totalHeight;
}
var maxHeight = this.maxHeight;
var padding = def([this.padding, this.outerBorder.width / 2]); // padding for canvas
if (totalHeight + 2 * padding < maxHeight) {
var hasScroll = true;
} else {
var hasScroll = false;
}
var fullRect = [0, 0, totalWidth + 2 * padding, totalHeight + 2 * padding];
var visRect = [left, top, totalWidth + 2 * padding, maxHeight];
var topRowRect = [left, top, totalWidth + 2 * padding, maxHeight];
var scrollRect = [left + totalWidth + 4 * padding, top, 25, maxHeight];
var ctxInvis = this.ctxInvis;
var ctxVis = this.ctxVis;
var ctxTopRow = this.ctxTopRow;
ctxInvis.data[102] = fullRect[2];
ctxInvis.canvas.width = fullRect[2];
ctxInvis.data[103] = fullRect[3];
ctxInvis.canvas.height = fullRect[3];
ctxVis.data[100] = visRect[0];
ctxVis.data[101] = visRect[1];
ctxVis.data[102] = visRect[2];
ctxVis.canvas.width = visRect[2];
ctxVis.data[103] = visRect[3];
ctxVis.canvas.height = visRect[3];
ctxTopRow.data[100] = topRowRect[0];
ctxTopRow.data[101] = topRowRect[1];
ctxTopRow.data[102] = topRowRect[2];
ctxTopRow.canvas.width = topRowRect[2];
ctxTopRow.data[103] = topRowRect[3];
ctxTopRow.canvas.height = topRowRect[3];
resizeCanvas(ctxInvis.canvas, 0, 0, fullRect[2], fullRect[3]);
resizeCanvas(ctxVis.canvas, visRect[0], visRect[1], visRect[2], visRect[3]);
resizeCanvas(ctxTopRow.canvas, topRowRect[0], topRowRect[1], topRowRect[2], topRowRect[3]);
ctxInvis.clear();
ctxVis.clear();
ctxTopRow.clear();
var left = padding;
var top = padding;
ctxInvis.save();
ctxInvis.lineCap = lineCap;
ctxInvis.lineJoin = lineJoin;
var cellDims = [];
var horizPos = [left];
for (var i = 0; i < cellWidths.length; i++) {
horizPos.push(horizPos[horizPos.length - 1] + cellWidths[i])
}
var vertPos = [top];
for (var i = 0; i < cellHeights.length; i++) {
vertPos.push(vertPos[vertPos.length - 1] + cellHeights[i])
}
// write text to each cell
var topPos = top;
for (var i = 0; i < cells.length; i++) {
var leftPos = left;
cellDims[i] = [];
for (var j = 0; j < cells[i].length; j++) {
cellDims[i][j] = {
left: leftPos + 2,
top: topPos + 1,
width: cellWidths[j] - 9,
height: cellHeights[i] - 8,
border: false,
offset: [-40, 0.5 * (cellHeights[i] - 8) - 20],
fontSize: fontSize,
leftPoint: paddingH,
textColor: textColor,
textAlign: horizAlign,
fontSize: fontSize
};
if (cells[i][j].highlight == true) {
if (typeof cells[i][j].color !== 'undefined' && cells[i][j].color !== 'none') {
ctxInvis.fillStyle = colorA(invertColor(cells[i][j].color), alpha);
} else {
ctxInvis.fillStyle = colorA(invertColor('#FFC'), alpha);
}
ctxInvis.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
} else if (typeof cells[i][j].color !== 'undefined' && cells[i][j].color !== 'none') {
ctxInvis.fillStyle = colorA(cells[i][j].color, alpha);
ctxInvis.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
}
var align = horizAlign;
if (!un(cells[i][j].align))
align = cells[i][j].align;
var cellText = text({
ctx: ctxInvis,
left: leftPos + paddingH,
top: topPos + paddingV,
width: cellWidths[j] - 2 * paddingH,
height: cellHeights[i] - 2 * paddingV,
textArray: cells[i][j].text,
textAlign: align,
vertAlign: 'middle',
padding: 0.001,
box: cells[i][j].box,
sf: sf
});
/*console.log(cellText.tightRect[2],cellText.tightRect[3],{
ctx:ctxInvis,
left:leftPos+paddingH,
top:topPos+paddingV,
width:cellWidths[j]-2*paddingH,
height:cellHeights[i]-2*paddingV,
textArray:cells[i][j].text,
textAlign:align,
vertAlign:'middle',
padding:0.001,
box:cells[i][j].box,
sf:sf
});*/
leftPos += cellWidths[j];
}
topPos += cellHeights[i];
}
// draw inner border
if (innerBorder.show == true) {
ctxInvis.strokeStyle = innerBorder.color;
ctxInvis.lineWidth = innerBorder.width;
if (!ctxInvis.setLineDash) {
ctxInvis.setLineDash = function () {}
}
ctxInvis.setLineDash(innerBorder.dash);
var leftPos = left;
for (var i = 0; i < cellWidths.length - 1; i++) {
leftPos += cellWidths[i];
ctxInvis.beginPath();
ctxInvis.moveTo(leftPos, top);
ctxInvis.lineTo(leftPos, top + totalHeight);
ctxInvis.stroke();
}
var topPos = top;
for (var i = 0; i < cellHeights.length - 1; i++) {
topPos += cellHeights[i];
ctxInvis.beginPath();
ctxInvis.moveTo(left, topPos);
ctxInvis.lineTo(left + totalWidth, topPos);
ctxInvis.stroke();
}
}
// draw outer border
if (outerBorder.show == true) {
ctxInvis.strokeStyle = outerBorder.color;
ctxInvis.lineWidth = outerBorder.width;
if (!ctxInvis.setLineDash) {
ctxInvis.setLineDash = function () {}
}
ctxInvis.setLineDash(outerBorder.dash);
ctxInvis.beginPath();
ctxInvis.strokeRect(left, top, totalWidth, totalHeight);
}
ctxInvis.restore();
this.cell = cellDims;
this.xPos = horizPos;
this.yPos = vertPos;
if (!un(this.additionalDraw)) {
this.additionalDraw();
}
//this.scroller.max = totalHeight/maxHeight;
//this.scroller.rect = scrollRect;
this.scroller.reposition(totalHeight / maxHeight, scrollRect);
setScrollerValue(this.scroller, 0, true);
this.scrollPos = 0;
this.scrollMax = scrollMax;
this.scrollDiff = totalHeight - maxHeight + 2 * padding;
this.redraw();
if (totalHeight + 2 * padding > this.maxHeight) {
this.hasScroll = true;
//this.scrollPos = 0;
//this.scrollMax = totalHeight/maxHeight;
//this.scrollDiff = totalHeight-maxHeight+2*padding;
//this.scroller.max = totalHeight/maxHeight;
//this.scroller.value = 0;
//this.scroller = updateScrollerDims(this.scroller);
showScroller(this.scroller);
if (this.topRowFreeze) {
showObj(ctxTopRow.canvas);
ctxTopRow.data[3] = ctxTopRow.data[103] = padding + vertPos[1];
ctxTopRow.canvas.width = ctxTopRow.data[102];
ctxTopRow.canvas.height = ctxTopRow.data[103];
resize();
ctxTopRow.drawImage(ctxInvis.canvas, 0, 0);
} else {
hideObj(ctxTopRow.canvas);
}
} else {
this.hasScroll = false;
hideScroller(this.scroller);
hideObj(ctxTopRow.canvas);
}
}
object.refreshCells();
object.move = function (left, top) {
var relLeft = this.scroller.rect[0] - this.ctxVis.canvas.data[100];
this.left = left;
this.top = top;
this.ctxVis.canvas.data[100] = left;
this.ctxVis.canvas.data[101] = top;
resizeCanvas3(this.ctxVis.canvas);
this.ctxTopRow.canvas.data[100] = left;
this.ctxTopRow.canvas.data[101] = top;
resizeCanvas3(this.ctxTopRow.canvas);
this.scroller.rect[0] = left + relLeft;
this.scroller.rect[1] = top;
this.scroller.reposition();
}
return object;
}
function drawScrollTable(object) {
/* EXAMPLE USAGE:
var j0001table1 = drawScrollTable({
left:100,
top:150,
minCellWidth:80,
minCellHeight:50,
horizAlign:'center',
text:{font:'Arial',size:32,color:'#000'},
outerBorder:{show:true,width:4,color:'#000'},
innerBorder:{show:true,width:2,color:'#666'},
cells:[
[ // row 0{text:['<<fontSize:36>><<font:algebra>>x'],color:'#CCF',minWidth:100,minHeight:70},{text:['<<fontSize:36>><<font:algebra>>y'],color:'#CCF',minWidth:100,minHeight:70},{text:['<<fontSize:36>><<font:algebra>><<color:#F00>>z'],color:'#CCF',minWidth:100,minHeight:70},
], [ // row 1{},{text:['2']},{text:['<<color:#F00>>3']},
], [ // row 2{text:['4']},{},{text:['<<color:#F00>>6']},
],
],
maxHeight:300,
moveFunc:function(r,c) {console.log(r,c)),
clickFunc:function(r,c) {console.log(r,c)),
padding:2 // def: outerBorder.width/2
});
*/
if (typeof object.sf !== 'undefined') {
var sf = object.sf;
} else {
var sf = 1;
}
var left = object.left * sf;
var top = object.top * sf;
var cells = object.cells;
var z = object.z || object.zIndex || 2;
var minCellWidth = object.minCellWidth * sf || 80 * sf;
var maxCellWidth = object.maxCellWidth * sf || Math.max(1200 * sf, object.minCellWidth * sf);
var minCellHeight = object.minCellHeight * sf || 100 * sf;
var minCellPadding = object.minCellPadding * sf || 7 * sf;
var paddingH = minCellPadding;
var paddingV = minCellPadding;
if (typeof object.paddingH == 'number')
paddingH = object.paddingH * sf;
if (typeof object.paddingV == 'number')
paddingV = object.paddingV * sf;
var horizAlign = object.horizAlign || object.align || 'center';
if (typeof object.text == 'object') {
var font = object.text.font || 'Arial';
var fontSize = object.text.size || 32;
var textColor = object.text.color || '#000';
} else {
var font = 'Arial';
var fontSize = 32;
var textColor = '#000';
}
if (typeof object.alpha == 'number') {
var alpha = object.alpha;
} else {
var alpha = 1;
}
var lineJoin = object.lineJoin || object.lineCap || 'round';
var lineCap = object.lineCap || object.lineJoin || 'round';
var outerBorder = {};
if (typeof object.outerBorder == 'object') {
outerBorder.show = boolean(object.outerBorder.show, true);
outerBorder.width = object.outerBorder.width * sf || 4 * sf;
if (typeof object.outerBorder.color == 'undefined') {
outerBorder.color = colorA('#000', alpha);
} else {
outerBorder.color = colorA(object.outerBorder.color, alpha);
}
outerBorder.dash = object.outerBorder.dash || [];
} else {
outerBorder.show = true;
outerBorder.width = 4 * sf;
outerBorder.color = colorA('#000', alpha);
outerBorder.dash = [];
}
outerBorder.dash = enlargeDash(outerBorder.dash, sf);
var innerBorder = {};
if (typeof object.innerBorder == 'object') {
innerBorder.show = boolean(object.innerBorder.show, true);
innerBorder.width = object.innerBorder.width * sf || 4 * sf;
if (typeof object.innerBorder.color == 'undefined') {
innerBorder.color = colorA('#000', alpha);
} else {
innerBorder.color = colorA(object.innerBorder.color, alpha);
}
innerBorder.dash = object.innerBorder.dash || [];
} else {
innerBorder.show = true;
innerBorder.width = 4 * sf;
innerBorder.color = colorA('#000', alpha);
innerBorder.dash = [20, 15];
}
innerBorder.dash = enlargeDash(innerBorder.dash, sf);
var tableAlignHoriz = object.tableAlignHoriz || 'left'; // is the whole table centred on [left,top]?
var tableAlignVert = object.tableAlignVert || 'top';
var numRows = cells.length;
var numCols = 0;
for (var i = 0; i < cells.length; i++) {
numCols = Math.max(cells[i].length, numCols);
}
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
//calcTableDims(object);
if (typeof hiddenCanvas == 'undefined') {
var hiddenCanvas = document.createElement('canvas');
hiddenCanvas.width = mainCanvasWidth * sf;
hiddenCanvas.height = mainCanvasHeight * sf;
hiddenCanvas.ctx = hiddenCanvas.getContext('2d');
}
for (var r = 0; r < cells.length; r++) {
var maxHeight = minCellHeight;
for (var c = 0; c < cells[r].length; c++) {
if (typeof cells[r][c] == 'object') {
if (typeof cells[r][c].text !== 'object') {
cells[r][c].text = [];
} else {
cells[r][c].text = clone(cells[r][c].text);
}
if (typeof cells[r][c].color !== 'string')
cells[r][c].color = 'none';
if (typeof cells[r][c].minWidth !== 'number')
cells[r][c].minWidth = 0;
if (typeof cells[r][c].minHeight !== 'number')
cells[r][c].minHeight = 0;
var font2 = font;
var fontSize2 = fontSize;
var textColor2 = textColor;
if (!un(cells[r][c].font))
font2 = cells[r][c].font;
if (!un(cells[r][c].fontSize))
fontSize2 = cells[r][c].fontSize;
if (!un(cells[r][c].textColor))
textColor2 = cells[r][c].textColor;
if (un(cells[r][c].styled)) {
cells[r][c].text.unshift('<<font:' + font2 + '>><<fontSize:' + fontSize2 + '>><<color:' + textColor2 + '>>');
cells[r][c].styled = true;
}
//var dims = drawMathsText(ctx,cells[r][c].text,fontSize,0,0,false,[],horizAlign,'middle',text.color,'measure');
//maxHeight = Math.max(dims[1]+2*minCellPadding,cells[r][c].minHeight,maxHeight);
//cellWidths[c] = Math.max(dims[0]+2*minCellPadding,cells[r][c].minWidth,cellWidths[c]);
var cellText = text({
ctx: hiddenCanvas.ctx,
left: 0,
top: 0,
width: maxCellWidth,
textArray: cells[r][c].text,
minTightWidth: 5,
minTightHeight: 5,
box: cells[r][c].box,
sf: sf
});
maxHeight = Math.max(cellText.tightRect[3] + 2 * paddingV, cells[r][c].minHeight * sf, maxHeight);
cellWidths[c] = Math.max(cellText.tightRect[2] + 3 * paddingH, cells[r][c].minWidth * sf, cellWidths[c]);
}
}
cellHeights[r] = Math.max(maxHeight, cellHeights[r]);
totalHeight += cellHeights[r];
}
for (var j = 0; j < cellWidths.length; j++) {
totalWidth += cellWidths[j];
}
if (tableAlignHoriz == 'center') {
left = left - totalWidth / 2;
} else if (tableAlignHoriz == 'right') {
left = left - totalWidth;
}
if (tableAlignVert == 'middle') {
top = top - totalHeight / 2;
} else if (tableAlignVert == 'bottom') {
top = top - totalHeight;
}
// now the table dims are known, create canvases
var maxHeight = object.maxHeight;
if (totalHeight + 2 * padding < maxHeight) {
var hasScroll = true;
} else {
var hasScroll = false;
}
var padding = def([object.padding, object.outerBorder.width / 2]); // padding for canvas
var fullRect = [0, 0, totalWidth + 2 * padding, totalHeight + 2 * padding];
var visRect = [left, top, totalWidth + 2 * padding, maxHeight];
var topRowRect = [left, top, totalWidth + 2 * padding, maxHeight];
var scrollRect = [left + totalWidth + 4 * padding, top, 25, maxHeight];
var ctxVis = newctx({
rect: visRect,
z: z
});
var ctxTopRow = newctx({
rect: topRowRect,
z: z
});
var ctxInvis = newctx({
rect: fullRect,
vis: false
});
var left = padding;
var top = padding;
ctxInvis.save();
ctxInvis.lineCap = lineCap;
ctxInvis.lineJoin = lineJoin;
var cellDims = [];
var horizPos = [left];
for (var i = 0; i < cellWidths.length; i++) {
horizPos.push(horizPos[horizPos.length - 1] + cellWidths[i])
}
var vertPos = [top];
for (var i = 0; i < cellHeights.length; i++) {
vertPos.push(vertPos[vertPos.length - 1] + cellHeights[i])
}
// write text to each cell
var topPos = top;
for (var i = 0; i < cells.length; i++) {
var leftPos = left;
cellDims[i] = [];
for (var j = 0; j < cells[i].length; j++) {
cellDims[i][j] = {
left: leftPos + 2,
top: topPos + 1,
width: cellWidths[j] - 9,
height: cellHeights[i] - 8,
border: false,
offset: [-40, 0.5 * (cellHeights[i] - 8) - 20],
fontSize: fontSize,
leftPoint: paddingH,
textColor: textColor,
textAlign: horizAlign,
fontSize: fontSize
};
if (cells[i][j].highlight == true) {
if (typeof cells[i][j].color == 'undefined' || cells[i][j].color !== 'none') {
ctxInvis.fillStyle = colorA(invertColor(cells[i][j].color), alpha);
} else {
ctxInvis.fillStyle = colorA(invertColor('#FFC'), alpha);
}
ctxInvis.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
} else if (typeof cells[i][j].color == 'undefined' || cells[i][j].color !== 'none') {
ctxInvis.fillStyle = colorA(cells[i][j].color, alpha);
ctxInvis.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
}
var align = horizAlign;
if (!un(cells[i][j].align))
align = cells[i][j].align;
var cellText = text({
ctx: ctxInvis,
left: leftPos + paddingH,
top: topPos + paddingV,
width: cellWidths[j] - 2 * paddingH,
height: cellHeights[i] - 2 * paddingV,
textArray: cells[i][j].text,
textAlign: align,
vertAlign: 'middle',
padding: 0.001,
box: cells[i][j].box,
sf: sf
});
//console.log(cellText.tightRect[2],cellText.tightRect[3]);
leftPos += cellWidths[j];
}
topPos += cellHeights[i];
}
// draw inner border
if (innerBorder.show == true) {
ctxInvis.strokeStyle = innerBorder.color;
ctxInvis.lineWidth = innerBorder.width;
if (!ctxInvis.setLineDash) {
ctxInvis.setLineDash = function () {}
}
ctxInvis.setLineDash(innerBorder.dash);
var leftPos = left;
for (var i = 0; i < cellWidths.length - 1; i++) {
leftPos += cellWidths[i];
ctxInvis.beginPath();
ctxInvis.moveTo(leftPos, top);
ctxInvis.lineTo(leftPos, top + totalHeight);
ctxInvis.stroke();
}
var topPos = top;
for (var i = 0; i < cellHeights.length - 1; i++) {
topPos += cellHeights[i];
ctxInvis.beginPath();
ctxInvis.moveTo(left, topPos);
ctxInvis.lineTo(left + totalWidth, topPos);
ctxInvis.stroke();
}
}
// draw outer border
if (outerBorder.show == true) {
ctxInvis.strokeStyle = outerBorder.color;
ctxInvis.lineWidth = outerBorder.width;
if (!ctxInvis.setLineDash) {
ctxInvis.setLineDash = function () {}
}
ctxInvis.setLineDash(outerBorder.dash);
ctxInvis.beginPath();
ctxInvis.strokeRect(left, top, totalWidth, totalHeight);
}
ctxInvis.restore();
var topRowFreeze = boolean(object.topRowFreeze, true);
var scrollMax = totalHeight / maxHeight;
var scrollDiff = totalHeight - maxHeight + 2 * padding;
//console.log(scrollMax,scrollDiff);
var scroll = createScroller({
rect: scrollRect,
max: scrollMax,
zIndex: z,
funcMove: function (value) {
this.table.scrollPos = (value / this.table.scrollMax) * this.table.scrollDiff;
this.table.redraw();
},
funcStop: function (value) {
this.table.scrollPos = (value / this.table.scrollMax) * this.table.scrollDiff;
this.table.redraw();
}
});
if (!un(object.funcMove)) {
ctxVis.canvas.style.pointerEvents = 'auto';
ctxVis.data[6] = true;
ctxVis.data[106] = true;
addListenerMove(ctxVis.canvas, function (e) {
updateMouse(e);
var r = -1,
c = -1;
for (var x = 0; x < this.table.xPos.length - 1; x++) {
if (mouse.x >= this.table.xPos[x] + this.table.ctxVis.data[100] && mouse.x <= this.table.xPos[x + 1] + this.table.ctxVis.data[100]) {
c = x;
break;
}
}
if (this.table.topRowFreeze && mouse.y <= this.table.yPos[1] + this.table.ctxVis.data[101]) {
r = 0;
} else {
for (var y = 0; y < this.table.yPos.length - 1; y++) {
if (mouse.y + this.table.scrollPos >= this.table.yPos[y] + this.table.ctxVis.data[101] && mouse.y + this.table.scrollPos <= this.table.yPos[y + 1] + this.table.ctxVis.data[101]) {
r = y;
break;
}
}
}
this.table.funcMove(r, c);
});
} else {
object.funcMove = function () {};
}
if (!un(object.funcClick)) {
ctxVis.canvas.style.pointerEvents = 'auto';
ctxVis.data[6] = true;
ctxVis.data[106] = true;
addListener(ctxVis.canvas, function (e) {
updateMouse(e);
var r = -1,
c = -1,
xProp = 0,
yProp = 0;
for (var x = 0; x < this.table.xPos.length - 1; x++) {
if (mouse.x >= this.table.xPos[x] + this.table.ctxVis.data[100] && mouse.x <= this.table.xPos[x + 1] + this.table.ctxVis.data[100]) {
c = x;
xProp = (mouse.x - (this.table.xPos[x] + this.table.ctxVis.data[100])) / (this.table.xPos[x + 1] - this.table.xPos[x]);
break;
}
}
if (this.table.topRowFreeze && mouse.y <= this.table.yPos[1] + this.table.ctxVis.data[101]) {
r = 0;
} else {
for (var y = 0; y < this.table.yPos.length - 1; y++) {
if (mouse.y + this.table.scrollPos >= this.table.yPos[y] + this.table.ctxVis.data[101] && mouse.y + this.table.scrollPos <= this.table.yPos[y + 1] + this.table.ctxVis.data[101]) {
r = y;
yProp = (mouse.y - (this.table.yPos[y] + this.table.ctxVis.data[101])) / (this.table.yPos[y + 1] - this.table.yPos[y]);
break;
}
}
}
this.table.funcClick(r, c, xProp, yProp);
});
} else {
object.funcClick = function () {};
}
var returnObj = clone(object);
returnObj.ctxVis = ctxVis;
returnObj.ctxInvis = ctxInvis;
returnObj.ctxTopRow = ctxTopRow;
returnObj.cell = cellDims;
returnObj.xPos = horizPos;
returnObj.yPos = vertPos;
returnObj.padding = padding;
returnObj.hasScroll = hasScroll;
returnObj.scroller = scroll;
returnObj.scrollPos = 0;
returnObj.scrollMax = scrollMax;
returnObj.scrollDiff = scrollDiff;
returnObj.topRowFreeze = topRowFreeze;
returnObj.redraw = function () {
this.ctxVis.clearRect(0, 0, this.ctxVis.canvas.data[102], this.ctxVis.canvas.data[103]);
this.ctxVis.drawImage(this.ctxInvis.canvas, 0, -this.scrollPos);
};
returnObj.scroller.table = returnObj;
returnObj.ctxVis.canvas.table = returnObj;
if (!un(returnObj.additionalDraw)) {
returnObj.additionalDraw();
}
returnObj.redraw();
if (topRowFreeze) {
ctxTopRow.data[3] = ctxTopRow.data[103] = padding + vertPos[1];
ctxTopRow.canvas.width = ctxTopRow.data[102];
ctxTopRow.canvas.height = ctxTopRow.data[103];
resizeCanvas2(ctxTopRow.canvas, ctxTopRow.data[102], ctxTopRow.data[103]);
resize();
ctxTopRow.drawImage(ctxInvis.canvas, 0, 0);
} else {
hideObj(ctxTopRow.canvas);
}
return returnObj;
}
function updateScrollTable(object) { // update previously drawn scrollTable with changed cell data
var sf = def([object.sf, 1]);
var left = object.left * sf;
var top = object.top * sf;
var cells = object.cells;
var z = object.z || object.zIndex || 2;
///console.log(object);
var minCellWidth = object.minCellWidth * sf || 80 * sf;
var maxCellWidth = object.maxCellWidth * sf || Math.max(1200 * sf, object.minCellWidth * sf);
var minCellHeight = object.minCellHeight * sf || 100 * sf;
var minCellPadding = object.minCellPadding * sf || 7 * sf;
var paddingH = minCellPadding;
var paddingV = minCellPadding;
if (typeof object.paddingH == 'number')
paddingH = object.paddingH * sf;
if (typeof object.paddingV == 'number')
paddingV = object.paddingV * sf;
var horizAlign = object.horizAlign || object.align || 'center';
if (typeof object.text == 'object') {
var font = object.text.font || 'Arial';
var fontSize = object.text.size || 32;
var textColor = object.text.color || '#000';
} else {
var font = 'Arial';
var fontSize = 32;
var textColor = '#000';
}
if (typeof object.alpha == 'number') {
var alpha = object.alpha;
} else {
var alpha = 1;
}
var lineJoin = object.lineJoin || object.lineCap || 'round';
var lineCap = object.lineCap || object.lineJoin || 'round';
var outerBorder = {};
if (typeof object.outerBorder == 'object') {
outerBorder.show = boolean(object.outerBorder.show, true);
outerBorder.width = object.outerBorder.width * sf || 4 * sf;
if (typeof object.outerBorder.color == 'undefined') {
outerBorder.color = colorA('#000', alpha);
} else {
outerBorder.color = colorA(object.outerBorder.color, alpha);
}
outerBorder.dash = object.outerBorder.dash || [];
} else {
outerBorder.show = true;
outerBorder.width = 4 * sf;
outerBorder.color = colorA('#000', alpha);
outerBorder.dash = [];
}
outerBorder.dash = enlargeDash(outerBorder.dash, sf);
var innerBorder = {};
if (typeof object.innerBorder == 'object') {
innerBorder.show = boolean(object.innerBorder.show, true);
innerBorder.width = object.innerBorder.width * sf || 4 * sf;
if (typeof object.innerBorder.color == 'undefined') {
innerBorder.color = colorA('#000', alpha);
} else {
innerBorder.color = colorA(object.innerBorder.color, alpha);
}
innerBorder.dash = object.innerBorder.dash || [];
} else {
innerBorder.show = true;
innerBorder.width = 4 * sf;
innerBorder.color = colorA('#000', alpha);
innerBorder.dash = [20, 15];
}
innerBorder.dash = enlargeDash(innerBorder.dash, sf);
var tableAlignHoriz = object.tableAlignHoriz || 'left'; // is the whole table centred on [left,top]?
var tableAlignVert = object.tableAlignVert || 'top';
var numRows = cells.length;
var numCols = 0;
for (var i = 0; i < cells.length; i++) {
numCols = Math.max(cells[i].length, numCols);
}
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
var cellHeights = [];
for (var i = 0; i < numRows; i++) {
cellHeights[i] = minCellHeight;
}
var cellWidths = [];
for (var j = 0; j < numCols; j++) {
cellWidths[j] = minCellWidth;
}
var totalWidth = 0;
var totalHeight = 0;
if (typeof hiddenCanvas == 'undefined') {
var hiddenCanvas = document.createElement('canvas');
hiddenCanvas.width = mainCanvasWidth * sf;
hiddenCanvas.height = mainCanvasHeight * sf;
hiddenCanvas.ctx = hiddenCanvas.getContext('2d');
}
for (var r = 0; r < cells.length; r++) {
var maxHeight = minCellHeight;
for (var c = 0; c < cells[r].length; c++) {
if (typeof cells[r][c] == 'object') {
if (typeof cells[r][c].text !== 'object') {
cells[r][c].text = [];
} else {
cells[r][c].text = clone(cells[r][c].text);
}
if (typeof cells[r][c].color !== 'string')
cells[r][c].color = 'none';
if (typeof cells[r][c].minWidth !== 'number')
cells[r][c].minWidth = 0;
if (typeof cells[r][c].minHeight !== 'number')
cells[r][c].minHeight = 0;
var font2 = font;
var fontSize2 = fontSize;
var textColor2 = textColor;
if (!un(cells[r][c].font))
font2 = cells[r][c].font;
if (!un(cells[r][c].fontSize))
fontSize2 = cells[r][c].fontSize;
if (!un(cells[r][c].textColor))
textColor2 = cells[r][c].textColor;
if (un(cells[r][c].styled)) {
cells[r][c].text.unshift('<<font:' + font2 + '>><<fontSize:' + fontSize2 + '>><<color:' + textColor2 + '>>');
cells[r][c].styled = true;
}
//var dims = drawMathsText(ctx,cells[r][c].text,fontSize,0,0,false,[],horizAlign,'middle',text.color,'measure');
//maxHeight = Math.max(dims[1]+2*minCellPadding,cells[r][c].minHeight,maxHeight);
//cellWidths[c] = Math.max(dims[0]+2*minCellPadding,cells[r][c].minWidth,cellWidths[c]);
var cellText = text({
ctx: hiddenCanvas.ctx,
left: 0,
top: 0,
width: maxCellWidth,
textArray: cells[r][c].text,
minTightWidth: 5,
minTightHeight: 5,
box: cells[r][c].box,
sf: sf
});
maxHeight = Math.max(cellText.tightRect[3] + 2 * paddingV, cells[r][c].minHeight * sf, maxHeight);
cellWidths[c] = Math.max(cellText.tightRect[2] + 3 * paddingH, cells[r][c].minWidth * sf, cellWidths[c]);
}
}
cellHeights[r] = Math.max(maxHeight, cellHeights[r]);
totalHeight += cellHeights[r];
}
for (var j = 0; j < cellWidths.length; j++) {
totalWidth += cellWidths[j];
}
if (tableAlignHoriz == 'center') {
left = left - totalWidth / 2;
} else if (tableAlignHoriz == 'right') {
left = left - totalWidth;
}
if (tableAlignVert == 'middle') {
top = top - totalHeight / 2;
} else if (tableAlignVert == 'bottom') {
top = top - totalHeight;
}
//update canvases
var maxHeight = object.maxHeight;
var padding = def([object.padding, object.outerBorder.width / 2]); // padding for canvas
var fullRect = [0, 0, totalWidth + 2 * padding, totalHeight + 2 * padding];
var visRect = [left, top, totalWidth + 2 * padding, maxHeight];
var topRowRect = [left, top, totalWidth + 2 * padding, maxHeight];
var scrollRect = [left + totalWidth + 4 * padding, top, 25, maxHeight];
var ctxInvis = object.ctxInvis;
var ctxVis = object.ctxVis;
var ctxTopRow = object.ctxTopRow;
ctxInvis.clear();
ctxVis.clear();
ctxTopRow.clear();
ctxInvis.data[102] = fullRect[2];
ctxInvis.data[103] = fullRect[3];
ctxInvis.canvas.width = fullRect[2];
ctxInvis.canvas.height = fullRect[3];
ctxVis.data[100] = visRect[0];
ctxVis.data[101] = visRect[1];
ctxVis.data[102] = visRect[2];
ctxVis.data[103] = visRect[3];
ctxVis.canvas.width = visRect[2];
ctxVis.canvas.height = visRect[3];
ctxTopRow.data[100] = topRowRect[0];
ctxTopRow.data[101] = topRowRect[1];
ctxTopRow.data[102] = topRowRect[2];
ctxTopRow.data[103] = topRowRect[3];
ctxTopRow.canvas.width = visRect[2];
ctxTopRow.canvas.height = visRect[3];
resize();
var left = padding;
var top = padding;
ctxInvis.save();
ctxInvis.lineCap = lineCap;
ctxInvis.lineJoin = lineJoin;
var cellDims = [];
var horizPos = [left];
for (var i = 0; i < cellWidths.length; i++) {
horizPos.push(horizPos[horizPos.length - 1] + cellWidths[i])
}
var vertPos = [top];
for (var i = 0; i < cellHeights.length; i++) {
vertPos.push(vertPos[vertPos.length - 1] + cellHeights[i])
}
// write text to each cell
var topPos = top;
for (var i = 0; i < cells.length; i++) {
var leftPos = left;
cellDims[i] = [];
for (var j = 0; j < cells[i].length; j++) {
cellDims[i][j] = {
left: leftPos + 2,
top: topPos + 1,
width: cellWidths[j] - 9,
height: cellHeights[i] - 8,
border: false,
offset: [-40, 0.5 * (cellHeights[i] - 8) - 20],
fontSize: fontSize,
leftPoint: paddingH,
textColor: textColor,
textAlign: horizAlign,
fontSize: fontSize
};
if (cells[i][j].highlight == true) {
if (typeof cells[i][j].color == 'undefined' || cells[i][j].color !== 'none') {
ctxInvis.fillStyle = colorA(invertColor(cells[i][j].color), alpha);
} else {
ctxInvis.fillStyle = colorA(invertColor('#FFC'), alpha);
}
ctxInvis.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
} else if (typeof cells[i][j].color == 'undefined' || cells[i][j].color !== 'none') {
ctxInvis.fillStyle = colorA(cells[i][j].color, alpha);
ctxInvis.fillRect(leftPos, topPos, cellWidths[j], cellHeights[i]);
}
var align = horizAlign;
if (!un(cells[i][j].align))
align = cells[i][j].align;
var cellText = text({
ctx: ctxInvis,
left: leftPos + paddingH,
top: topPos + paddingV,
width: cellWidths[j] - 2 * paddingH,
height: cellHeights[i] - 2 * paddingV,
textArray: cells[i][j].text,
textAlign: align,
vertAlign: 'middle',
padding: 0.001,
box: cells[i][j].box,
sf: sf
});
/*console.log(cellText.tightRect[2],cellText.tightRect[3],{
ctx:ctxInvis,
left:leftPos+paddingH,
top:topPos+paddingV,
width:cellWidths[j]-2*paddingH,
height:cellHeights[i]-2*paddingV,
textArray:cells[i][j].text,
textAlign:align,
vertAlign:'middle',
padding:0.001,
box:cells[i][j].box,
sf:sf
});*/
leftPos += cellWidths[j];
}
topPos += cellHeights[i];
}
// draw inner border
if (innerBorder.show == true) {
ctxInvis.strokeStyle = innerBorder.color;
ctxInvis.lineWidth = innerBorder.width;
if (!ctxInvis.setLineDash) {
ctxInvis.setLineDash = function () {}
}
ctxInvis.setLineDash(innerBorder.dash);
var leftPos = left;
for (var i = 0; i < cellWidths.length - 1; i++) {
leftPos += cellWidths[i];
ctxInvis.beginPath();
ctxInvis.moveTo(leftPos, top);
ctxInvis.lineTo(leftPos, top + totalHeight);
ctxInvis.stroke();
}
var topPos = top;
for (var i = 0; i < cellHeights.length - 1; i++) {
topPos += cellHeights[i];
ctxInvis.beginPath();
ctxInvis.moveTo(left, topPos);
ctxInvis.lineTo(left + totalWidth, topPos);
ctxInvis.stroke();
}
}
// draw outer border
if (outerBorder.show == true) {
ctxInvis.strokeStyle = outerBorder.color;
ctxInvis.lineWidth = outerBorder.width;
if (!ctxInvis.setLineDash) {
ctxInvis.setLineDash = function () {}
}
ctxInvis.setLineDash(outerBorder.dash);
ctxInvis.beginPath();
ctxInvis.strokeRect(left, top, totalWidth, totalHeight);
}
ctxInvis.restore();
object.cell = cellDims;
object.xPos = horizPos;
object.yPos = vertPos;
if (!un(object.additionalDraw)) {
object.additionalDraw();
}
object.redraw();
if (totalHeight + 2 * padding > object.maxHeight) {
moveScroller(object.scroller, object.left + object.xPos.last(), object.top + object.yPos[0]);
object.hasScroll = true;
object.scrollPos = 0;
object.scrollMax = totalHeight / maxHeight;
object.scrollDiff = totalHeight - maxHeight + 2 * padding;
object.scroller.max = totalHeight / maxHeight;
object.scroller.value = 0;
object.scroller = updateScrollerDims(object.scroller);
showScroller(object.scroller);
if (object.topRowFreeze) {
showObj(ctxTopRow.canvas);
ctxTopRow.data[3] = ctxTopRow.data[103] = padding + vertPos[1];
ctxTopRow.canvas.width = ctxTopRow.data[102];
ctxTopRow.canvas.height = ctxTopRow.data[103];
resizeCanvas3(ctxTopRow.canvas);
ctxTopRow.drawImage(ctxInvis.canvas, 0, 0);
} else {
hideObj(ctxTopRow.canvas);
}
} else {
object.hasScroll = false;
hideScroller(object.scroller);
hideObj(ctxTopRow.canvas);
}
return object;
}
function moveScrollTable(object, left, top) {
var relLeft = object.scroller.rect[0] - object.ctxVis.canvas.data[100];
object.left = left;
object.top = top;
object.ctxVis.canvas.data[100] = left;
object.ctxVis.canvas.data[101] = top;
resizeCanvas3(object.ctxVis.canvas);
object.ctxTopRow.canvas.data[100] = left;
object.ctxTopRow.canvas.data[101] = top;
resizeCanvas3(object.ctxTopRow.canvas);
object.scroller.rect[0] = left + relLeft;
object.scroller.rect[1] = top;
object.scroller.reposition();
}
function showScrollTable(object, includeTopRow) {
showObj(object.ctxVis.canvas);
if (boolean(includeTopRow, true))
showObj(object.ctxTopRow.canvas);
/*if (object.hasScroll == true)*/
showScroller(object.scroller);
}
function hideScrollTable(object) {
hideObj(object.ctxVis.canvas);
hideObj(object.ctxTopRow.canvas);
hideScroller(object.scroller);
}
function createSlider(object) {
if (typeof slider[pageIndex] == 'undefined')
slider[pageIndex] = [];
var id = object.id || slider[pageIndex].length;
if (typeof object.gridDetails == 'undefined')
object.gridDetails = {};
var left = object.left || object.l || (object.gridDetails.left - 10);
var top = object.top || object.t || (object.gridDetails.top + object.gridDetails.height);
var width = object.width || object.w || (object.gridDetails.width + 20);
var height = object.height || object.h || 60;
var bottom = top + height;
var min,
max;
if (typeof object.min == 'number') {
min = object.min;
} else {
min = object.gridDetails.xMin;
}
if (typeof object.max == 'number') {
max = object.max;
} else {
max = object.gridDetails.xMax;
}
var vari = object.vari || "a";
var linkedVar = object.linkedVar;
var varChangeListener = object.varChangeListener;
var onchange = object.onchange;
var startNum = min;
if (typeof object.startNum == 'number')
startNum = object.startNum;
var inc = object.inc || 1;
var label = true;
if (typeof object.label == 'boolean')
label = object.label;
var labelFont = object.labelFont || "24px Arial";
var labelColor = object.labelColor || '#000';
var visible = boolean(object.visible, true);
var zIndex = object.zIndex || 2;
var handleColor = object.handleColor || '#00F';
var handleStyle = object.handleStyle || 'rect'; // rect or circle
var discrete = false;
if (typeof object.discrete == 'boolean')
discrete = object.discrete;
var stepNum = 1;
if (typeof object.stepNum == 'number')
stepNum = object.stepNum;
var snap = false;
if (typeof object.snap == 'boolean')
snap = object.snap;
var snapNum = 1;
if (typeof object.snapNum == 'number')
snapNum = object.snapNum;
var vert = boolean(object.vertical, false);
if (vert == false) {
var sliderWidth = 20;
if (handleStyle == 'circle')
sliderWidth = 40;
var sliderHeight = 40;
} else {
var sliderHeight = 20;
if (handleStyle == 'circle')
sliderHeight = 40;
var sliderWidth = 40;
}
var backCanvas = document.createElement('canvas');
backCanvas.width = width;
backCanvas.height = height * 1.5;
backCanvas.setAttribute('position', 'absolute');
backCanvas.setAttribute('cursor', 'auto');
backCanvas.setAttribute('draggable', 'false');
backCanvas.setAttribute('class', 'buttonClass');
backCanvas.style.pointerEvents = 'none';
backCanvas.style.zIndex = zIndex;
if (visible == true)
container.appendChild(backCanvas);
canvases[pageIndex].push(backCanvas);
var backCtx = backCanvas.getContext('2d');
var backCanvasData = [left, top, width, height * 1.5, visible, false, false, zIndex];
for (var i = 0; i < 8; i++) {
backCanvasData[100 + i] = backCanvasData[i];
}
backCanvasData[130] = visible;
backCanvas.data = backCanvasData;
backCtx.lineWidth = 5;
backCtx.strokeStyle = object.backColor || '#666';
backCtx.lineCap = 'round';
backCtx.lineJoin = 'round';
backCtx.beginPath();
if (vert == false) {
backCtx.moveTo(0.5 * sliderWidth, 0.5 * height);
backCtx.lineTo(width - 0.5 * sliderWidth, 0.5 * height);
} else {
backCtx.moveTo(0.5 * width, 0.5 * sliderHeight);
backCtx.lineTo(0.5 * width, height - 0.5 * sliderHeight);
}
backCtx.closePath();
backCtx.stroke();
if (!un(object.scale)) {
var step = def([object.scale.step, 1]);
var xInc = (width - 20) / ((max - min) / step);
var color = def([object.scale.color, backCtx.strokeStyle]);
backCtx.lineWidth = 2;
backCtx.strokeStyle = color;
backCtx.beginPath();
var l = 10;
for (var i = min; i <= max; i += step) {
backCtx.moveTo(l, 0.5 * height);
backCtx.lineTo(l, 0.85 * height);
text({
ctx: backCtx,
left: l - 50,
top: 0.85 * height,
width: 100,
height: height,
align: 'center',
color: color,
textArray: ['<<fontSize:20>>' + i]
});
l += xInc;
}
backCtx.stroke();
}
var sliderCanvas = document.createElement('canvas');
sliderCanvas.width = sliderWidth;
sliderCanvas.height = sliderHeight;
sliderCanvas.setAttribute('position', 'absolute');
sliderCanvas.setAttribute('cursor', 'auto');
sliderCanvas.setAttribute('draggable', 'false');
sliderCanvas.setAttribute('class', 'buttonClass');
sliderCanvas.style.zIndex = zIndex;
if (visible == true)
container.appendChild(sliderCanvas);
canvases[pageIndex].push(sliderCanvas);
var sliderCtx = sliderCanvas.getContext('2d');
if (vert == false) {
var leftPos = left + (startNum - min) * (width - sliderWidth) / (max - min);
var sliderCanvasData = [leftPos, top + 0.5 * height - 0.5 * sliderHeight, sliderWidth, sliderHeight, visible, false, true, zIndex];
} else {
var topPos = bottom - sliderHeight - (startNum - min) * (height - sliderHeight) / (max - min);
var sliderCanvasData = [left + 0.5 * width - 0.5 * sliderWidth, topPos, sliderWidth, sliderHeight, visible, false, true, zIndex];
}
for (var i = 0; i < 8; i++) {
sliderCanvasData[100 + i] = sliderCanvasData[i];
}
sliderCanvasData[130] = visible;
sliderCanvas.data = sliderCanvasData;
addListenerStart(sliderCanvas, sliderDragStart);
sliderCtx.fillStyle = handleColor;
if (handleStyle == 'rect') {
sliderCtx.fillRect(0, 0, sliderWidth, sliderHeight);
} else if (handleStyle == 'circle') {
sliderCtx.beginPath();
sliderCtx.arc(0.5 * sliderWidth, 0.5 * sliderHeight, 0.5 * sliderHeight - 2, 0, 2 * Math.PI);
sliderCtx.closePath();
sliderCtx.fill();
}
var labelCanvas = document.createElement('canvas');
labelCanvas.width = width;
labelCanvas.height = parseInt(labelFont) * 2;
labelCanvas.setAttribute('position', 'absolute');
labelCanvas.setAttribute('cursor', 'auto');
labelCanvas.setAttribute('draggable', 'false');
labelCanvas.setAttribute('class', 'buttonClass');
labelCanvas.style.pointerEvents = 'none';
labelCanvas.style.zIndex = zIndex;
if (label == true && visible == true) {
container.appendChild(labelCanvas);
}
canvases[pageIndex].push(labelCanvas);
var labelCtx = labelCanvas.getContext('2d');
var labelCanvasData = [left, top + height, width, height, visible, false, false, zIndex];
for (var i = 0; i < 8; i++) {
labelCanvasData[100 + i] = labelCanvasData[i];
}
labelCanvasData[130] = visible;
labelCanvas.data = labelCanvasData;
if (label == true) {
labelCtx.fillStyle = labelColor;
labelCtx.font = "24px Arial";
labelCtx.textAlign = 'center';
labelCtx.textBaseline = 'middle';
labelCtx.fillText(vari + " = " + startNum, 0.5 * width, parseInt(labelFont));
}
resize();
slider[pageIndex][id] = {
id: id,
backCanvas: backCanvas,
backctx: backCtx,
backData: backCanvasData,
sliderCanvas: sliderCanvas,
sliderctx: sliderCtx,
sliderData: sliderCanvasData,
labelCanvas: labelCanvas,
labelctx: labelCtx,
labelData: labelCanvasData,
left: left,
top: top,
width: width,
height: height,
bottom: bottom,
sliderWidth: sliderWidth,
sliderHeight: sliderHeight,
min: min,
max: max,
startNum: startNum,
currNum: startNum,
vari: vari,
linkedVar: linkedVar,
varChangeListener: varChangeListener,
onchange: onchange,
inc: inc,
label: label,
labelFont: labelFont,
labelColor: labelColor,
visible: visible,
zIndex: zIndex,
discrete: discrete,
stepNum: stepNum,
snap: snap,
snapNum: snapNum,
vert: vert
};
return slider[pageIndex][id];
}
function showSlider(slider) {
showObj(slider.backCanvas, slider.backData);
showObj(slider.sliderCanvas, slider.sliderData);
showObj(slider.labelCanvas, slider.labelData);
}
function hideSlider(slider) {
hideObj(slider.backCanvas, slider.backData);
hideObj(slider.sliderCanvas, slider.sliderData);
hideObj(slider.labelCanvas, slider.labelData);
}
function setSliderValue(slider, value) {
var val = Math.min(Math.max(slider.min, value), slider.max);
if (slider.vert == false) {
var left = slider.left + ((val - slider.min) / (slider.max - slider.min)) * (slider.width - slider.sliderWidth);
slider.sliderData[100] = left;
} else {
var top = slider.bottom - slider.sliderHeight - ((val - slider.min) / (slider.max - slider.min)) * (slider.height - slider.sliderHeight);
slider.sliderData[101] = top;
}
slider.currNum = val;
if (slider.label == true) {
slider.labelctx.clearRect(0, 0, slider.width, mainCanvasHeight);
slider.labelctx.fillStyle = slider.labelColor;
slider.labelctx.font = slider.labelFont;
slider.labelctx.textAlign = 'center';
slider.labelctx.textBaseline = 'middle';
slider.labelctx.fillText(slider.vari + " = " + roundToNearest(val, 0.01), 0.5 * slider.width, parseInt(slider.labelFont));
}
resizeCanvas2(slider.sliderCanvas, slider.sliderData[100], slider.sliderData[101]);
if (!un(slider.linkedVar))
eval(slider.linkedVar + "=" + val);
if (!un(slider.varChangeListener))
eval(slider.varChangeListener + "()");
if (!un(slider.onchange))
slider.onchange(val);
}
function sliderDragStart(e) {
for (var i = 0; i < slider[pageIndex].length; i++) {
if (slider[pageIndex][i].sliderCanvas == e.target) {
currSlider = i;
break;
}
}
removeListenerStart(e.target, sliderDragStart)
addListenerMove(window, sliderDragMove);
addListenerEnd(window, sliderDragStop);
}
function sliderDragMove(e) {
var s = slider[pageIndex][currSlider];
if (s.vert == false) {
var left = Math.min(Math.max(mouse.x, s.left), s.left + s.width - s.sliderWidth);
slider[pageIndex][currSlider].sliderData[100] = left;
var val = s.min + (left - s.left) * (s.max - s.min) / (s.width - s.sliderWidth);
} else {
var top = Math.min(Math.max(mouse.y, s.top), s.bottom - s.sliderHeight);
slider[pageIndex][currSlider].sliderData[101] = top;
var val = s.min + (s.bottom - s.sliderHeight - top) * (s.max - s.min) / (s.height - s.sliderHeight);
}
if (s.discrete == true) {
val = roundToNearest(val, s.stepNum);
}
s.currNum = val;
if (!un(s.linkedVar))
eval(s.linkedVar + "=" + val);
if (!un(s.varChangeListener))
eval(s.varChangeListener + "()");
if (!un(s.onchange))
s.onchange(val);
if (s.label == true) {
s.labelctx.clearRect(0, 0, s.width, mainCanvasHeight);
s.labelctx.fillStyle = s.labelColor;
s.labelctx.font = s.labelFont;
s.labelctx.textAlign = 'center';
s.labelctx.textBaseline = 'middle';
s.labelctx.fillText(s.vari + " = " + roundToNearest(val, 0.01), 0.5 * s.width, parseInt(s.labelFont));
}
resize();
}
function sliderDragStop(e) {
removeListenerMove(window, sliderDragMove);
removeListenerEnd(window, sliderDragStop);
// snap slider to position
var s = slider[pageIndex][currSlider];
if (s.snap == true) {
if (s.vert == false) {
var left = Math.min(Math.max(mouse.x, s.left), s.left + s.width - s.sliderWidth);
var val = s.min + (left - s.left) * (s.max - s.min) / (s.width - s.sliderWidth);
} else {
var top = Math.min(Math.max(mouse.y, s.top), s.bottom - s.sliderHeight);
var val = s.min + (s.bottom - s.sliderHeight - top) * (s.max - s.min) / (s.height - s.sliderHeight);
}
if (s.discrete == true) {
val = roundToNearest(val, s.stepNum);
} else if (s.snap == true) {
val = roundToNearest(val, s.snapNum);
} else {
val = roundToNearest(val, s.inc);
}
s.currNum = val;
eval(s.linkedVar + "=" + val);
eval(s.varChangeListener + "()");
if (s.vert == false) {
var leftSnap = s.left + (val - s.min) * (s.width - s.sliderWidth) / (s.max - s.min);
slider[pageIndex][currSlider].sliderData[100] = leftSnap;
} else {
var topSnap = s.bottom - s.sliderHeight - (val - s.min) * (s.height - s.sliderHeight) / (s.max - s.min);
slider[pageIndex][currSlider].sliderData[101] = topSnap;
}
resize();
}
addListenerStart(slider[pageIndex][currSlider].sliderCanvas, sliderDragStart)
}
var currScroller;
function createScroller(obj) {
var rect = obj.rect;
var z = obj.zIndex || obj.z || 2;
var vis = boolean(obj.visible, boolean(obj.vis, true));
var min = obj.min || 0;
var max = obj.max;
//var colors = obj.colors || ['#3FF','#00F','#333','#CCC','#333'];
var colors = obj.colors || {
buttons: '#3FF',
slider: '#00F',
border: '#333',
back: '#CCC',
buttonsBorder: '#333',
arrows: '#333'
};
var sliderHeight = obj.sliderHeight || (rect[3] - rect[2] * 2) / (max - min);
var inc = obj.inc || 1;
var incDist = (rect[3] - 2 * rect[2] - sliderHeight) / ((max - min) / inc);
var sliderRect = [rect[0], rect[1] + rect[2], rect[2], sliderHeight];
var radius = def([obj.radius, 0]);
var scroller = {
rect: rect,
zIndex: z,
min: min,
max: max,
value: min,
colors: colors,
sliderRect: sliderRect,
sliderHeight: sliderHeight,
inc: inc,
incDist: incDist,
radius: radius
};
if (typeof obj.funcMove == 'function')
scroller.funcMove = obj.funcMove;
if (typeof obj.funcStop == 'function')
scroller.funcStop = obj.funcStop;
scroller.canvas = newctx({
rect: scroller.rect,
pE: true,
z: scroller.zIndex
}).canvas;
scroller.canvas.scroller = scroller;
scroller.ctx = scroller.canvas.ctx;
scroller.slider = {};
scroller.sliderCanvas = newctx({
rect: sliderRect,
pE: true,
z: scroller.zIndex + 1
}).canvas;
scroller.sliderCanvas.scroller = scroller;
scroller.sliderctx = scroller.sliderCanvas.ctx;
scroller.sliderctx.fillStyle = colors[1];
scroller.sliderctx.fillRect(0, 0, sliderRect[2], sliderRect[3]);
scroller.reposition = function (newMax, newRect, sliderHeight) {
if (!un(newMax))
this.max = newMax;
if (!un(newRect))
this.rect = newRect;
this.canvas.data[100] = this.rect[0];
this.canvas.data[101] = this.rect[1];
this.canvas.data[102] = this.rect[2];
this.canvas.data[103] = this.rect[3];
resizeCanvas(this.canvas, this.rect[0], this.rect[1], this.rect[2], this.rect[3]);
this.canvas.width = this.rect[2];
this.canvas.height = this.rect[3];
if (!un(sliderHeight)) {
this.sliderHeight = sliderHeight;
} else {
this.sliderHeight = (this.rect[3] - this.rect[2] * 2) / (this.max - this.min);
}
this.sliderRect = [this.rect[0], this.rect[1] + this.rect[2], this.rect[2], this.sliderHeight];
this.sliderCanvas.data[100] = this.sliderRect[0];
this.sliderCanvas.data[101] = this.sliderRect[1];
this.sliderCanvas.data[102] = this.sliderRect[2];
this.sliderCanvas.data[103] = this.sliderRect[3];
resizeCanvas(this.sliderCanvas, this.sliderRect[0], this.sliderRect[1], this.sliderRect[2], this.sliderRect[3]);
this.sliderCanvas.width = this.sliderRect[2];
this.sliderCanvas.height = this.sliderRect[3];
this.draw();
this.value = 0;
this.sliderRect[1] = this.rect[1] + this.rect[2] + (this.rect[3] - 2 * this.rect[2] - this.sliderHeight) * ((this.value - this.min) / (this.max - this.min));
resizeCanvas(this.sliderCanvas, this.sliderRect[0], this.sliderRect[1]);
}
scroller.draw = function () {
var ctx = this.ctx;
var w = this.rect[2];
var h = this.rect[3];
ctx.clearRect(0, 0, w, h);
var lineWidth = this.lineWidth || 4;
var radius = this.radius;
roundedRect(ctx, lineWidth / 2, lineWidth / 2, w - lineWidth, h - lineWidth, radius, lineWidth, this.colors.border, this.colors.back);
roundedRect(ctx, lineWidth / 2, lineWidth / 2, w - lineWidth, w - lineWidth, radius, lineWidth, this.colors.buttonsBorder, this.colors.buttons);
roundedRect(ctx, lineWidth / 2, h - w, w - lineWidth, w - lineWidth, radius, lineWidth, this.colors.buttonsBorder, this.colors.buttons);
ctx.fillStyle = this.colors.arrows;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(w * 14 / 50, w * 34 / 50);
ctx.lineTo(w * 25 / 50, w * 16 / 50);
ctx.lineTo(w * 36 / 50, w * 34 / 50);
ctx.lineTo(w * 14 / 50, w * 34 / 50);
ctx.fill();
ctx.beginPath();
ctx.moveTo(w * 14 / 50, h - w + w * 16 / 50);
ctx.lineTo(w * 25 / 50, h - w + w * 34 / 50);
ctx.lineTo(w * 36 / 50, h - w + w * 16 / 50);
ctx.lineTo(w * 14 / 50, h - w + w * 16 / 50);
ctx.fill();
this.sliderctx.fillStyle = this.colors.slider;
this.sliderctx.fillRect(0, 0, this.sliderRect[2], this.sliderHeight);
}
scroller.draw();
addListener(scroller.canvas, function (e) {
updateMouse(e);
var scroller = this.scroller;
if (mouse.y < scroller.sliderRect[1]) {
scroller.update(-1);
} else if (mouse.y > scroller.sliderRect[1] + scroller.sliderRect[3]) {
scroller.update(1);
}
});
addListenerStart(scroller.sliderCanvas, scrollerMoveStart);
scroller.update = function (change) {
var newValue = Math.min(this.max, Math.max(this.min, this.value + change));
if (newValue == this.value)
return;
this.value = newValue;
this.sliderRect[1] = this.rect[1] + this.rect[2] + (this.rect[3] - 2 * this.rect[2] - this.sliderHeight) * ((this.value - this.min) / (this.max - this.min));
resizeCanvas(this.sliderCanvas, this.sliderRect[0], this.sliderRect[1]);
if (typeof this.funcStop == 'function') {
this.funcStop(this.value);
} else if (typeof this.funcMove == 'function') {
this.funcMove(this.value);
}
}
return scroller;
}
function updateScrollerDims(s) { // update max
s.sliderHeight = (s.rect[3] - s.rect[2] * 2) / (s.max - s.min);
s.incDist = (s.rect[3] - 2 * s.rect[2] - s.sliderHeight) / ((s.max - s.min) / s.inc);
s.sliderRect[3] = s.sliderHeight;
s.sliderCanvas.data[100] = s.sliderRect[0];
s.sliderCanvas.data[101] = s.sliderRect[1];
s.sliderCanvas.data[102] = s.sliderRect[2];
s.sliderCanvas.data[103] = s.sliderRect[3];
resizeCanvas3(s.sliderCanvas);
setScrollerValue(s, s.value, true);
return s;
}
function scrollerMoveStart(e) {
updateMouse(e);
currScroller = e.target.scroller;
currScroller.dragStartPos = mouse.y;
currScroller.dragStartValue = currScroller.value;
currScroller.dragStartY = currScroller.sliderRect[1];
currScroller.dragOffset = mouse.y - currScroller.sliderRect[1];
currScroller.dragMinY = currScroller.rect[1] + currScroller.rect[2];
currScroller.dragMaxY = currScroller.rect[1] + currScroller.rect[3] - currScroller.rect[2] - currScroller.sliderRect[3];
addListenerMove(window, scrollerMoveMove);
addListenerEnd(window, scrollerMoveStop);
}
function scrollerMoveMove(e) {
updateMouse(e);
var dy = mouse.y - currScroller.dragStartPos;
var newY = Math.min(Math.max(currScroller.dragStartY + dy, currScroller.dragMinY), currScroller.dragMaxY);
if (newY == currScroller.sliderRect[1])
return;
currScroller.sliderRect[1] = newY;
resizeCanvas(currScroller.sliderCanvas, currScroller.sliderRect[0], currScroller.sliderRect[1]);
currScroller.value = currScroller.min + (currScroller.max - currScroller.min) * (currScroller.sliderRect[1] - currScroller.dragMinY) / (currScroller.dragMaxY - currScroller.dragMinY);
if (typeof currScroller.funcMove == 'function')
currScroller.funcMove(currScroller.value);
};
function scrollerMoveStop(e) {
if (typeof currScroller.funcStop == 'function')
currScroller.funcStop(currScroller.value);
currScroller = null;
removeListenerMove(window, scrollerMoveMove);
removeListenerEnd(window, scrollerMoveStop);
};
function setScrollerValue(scroller, value, applyFunc) {
var newValue = Math.min(Math.max(scroller.min, value), scroller.max);
//if (newValue == scroller.value) return;
scroller.value = newValue;
scroller.sliderRect[1] = scroller.rect[1] + scroller.rect[2] + (scroller.rect[3] - 2 * scroller.rect[2] - scroller.sliderHeight) * ((scroller.value - scroller.min) / (scroller.max - scroller.min));
resizeCanvas(scroller.sliderCanvas, scroller.sliderRect[0], scroller.sliderRect[1]);
scroller.update(0);
if (!applyFunc && applyFunc == false)
return;
if (typeof scroller.funcStop == 'function') {
scroller.funcStop(scroller.value);
} else if (typeof scroller.funcMove == 'function') {
scroller.funcMove(scroller.value);
}
}
function moveScroller(scroller, left, top) {
var relTop = scroller.sliderCanvas.data[101] - scroller.canvas.data[100];
scroller.sliderRect[0] = left;
scroller.sliderRect[1] = top + relTop;
scroller.rect[0] = left;
scroller.rect[1] = top;
scroller.canvas.data[100] = left;
scroller.canvas.data[101] = top;
resizeCanvas3(scroller.canvas);
scroller.sliderCanvas.data[100] = left;
scroller.sliderCanvas.data[101] = top + relTop;
resizeCanvas3(scroller.sliderCanvas);
}
function hideScroller(scroller) {
if (un(scroller))
return;
hideObj(scroller.canvas);
hideObj(scroller.sliderCanvas);
}
function showScroller(scroller) {
if (un(scroller))
return;
showObj(scroller.canvas);
showObj(scroller.sliderCanvas);
}
function drawArrow(object) {
var context = object.context || object.ctx;
var startX = object.startX;
var startY = object.startY;
var finX = object.finX || startX;
var finY = object.finY || startY;
var doubleEnded;
if (typeof object.doubleEnded == 'boolean') {
doubleEnded = object.doubleEnded
} else {
doubleEnded = false
}
var arrowLength = object.arrowLength || 30;
var angleBetweenLinesRads = object.angleBetweenLinesRads || 0.5;
var fillArrow = boolean(object.fillArrow, false);
var showLine = boolean(object.showLine, true);
var dash = object.dash || [];
context.save();
context.strokeStyle = object.color || '#000';
context.fillStyle = object.color || '#000';
context.lineWidth = object.lineWidth || 4;
if (showLine == true) {
//draw line
if (typeof context.setLineDash == 'undefined')
context.setLineDash = function () {};
context.setLineDash(dash);
context.beginPath();
context.moveTo(startX, startY);
context.lineTo(finX, finY);
context.stroke();
}
context.lineWidth = object.arrowLineWidth || object.lineWidth || 4;
context.beginPath();
if (typeof context.setLineDash == 'undefined')
context.setLineDash = function () {};
context.setLineDash([]);
context.lineJoin = 'round';
context.lineCap = 'round';
var posX;
var posY;
var otherX;
var otherY;
var endToDraw = "fin";
do {
if (endToDraw == "fin") {
posX = finX;
posY = finY;
otherX = startX;
otherY = startY;
} else if (endToDraw == "start") {
posX = startX;
posY = startY;
otherX = finX;
otherY = finY;
}
var nGradient = (-1 * (posY - otherY)) / (posX - otherX);
var angleToHorizontal = Math.abs(Math.atan(nGradient));
var remainingAngle = Math.PI / 2 - angleBetweenLinesRads - angleToHorizontal;
var narrowX1,
narrowX2,
narrowY1,
narrowY2;
if (nGradient == Infinity) {
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength;
narrowY1 = Math.cos(remainingAngle) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX + narrowX1, posY + narrowY1);
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX + narrowX2, posY - narrowY2);
if (fillArrow == true)
context.lineTo(posX + narrowX1, posY + narrowY1);
} else if (nGradient == -Infinity) {
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength;
narrowY1 = Math.cos(remainingAngle) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX + narrowX1, posY - narrowY1);
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX + narrowX2, posY + narrowY2);
if (fillArrow == true)
context.lineTo(posX + narrowX1, posY - narrowY1);
} else
///case 1 - arrow is pointing up and to the left
if (nGradient < 0 && posY < otherY) {
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength;
narrowY1 = Math.cos(remainingAngle) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX + narrowX1, posY + narrowY1);
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX + narrowX2, posY - narrowY2);
if (fillArrow == true)
context.lineTo(posX + narrowX1, posY + narrowY1);
} else
///case 2 - arrow is pointing up and to the right
if (nGradient > 0 && posY < otherY) {
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength;
narrowY1 = Math.cos(remainingAngle) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX - narrowX1, posY + narrowY1);
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength;
context.moveTo(posX, posY);
context.lineTo(posX - narrowX2, posY - narrowY2);
if (fillArrow == true)
context.lineTo(posX - narrowX1, posY + narrowY1);
} else
//gradient is 0 and pointing right
if (nGradient == 0 && posX > otherX) {
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength
narrowY1 = Math.cos(remainingAngle) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX - narrowX1, posY + narrowY1)
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX - narrowX2, posY - narrowY2)
if (fillArrow == true)
context.lineTo(posX - narrowX1, posY + narrowY1);
} else
// gradient is 0 and pointing left
if (nGradient == 0 && posX < otherX) {
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength
narrowY1 = Math.cos(remainingAngle) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX + narrowX1, posY + narrowY1)
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX + narrowX2, posY - narrowY2)
if (fillArrow == true)
context.lineTo(posX + narrowX1, posY + narrowY1);
} else
///case 3 - arrow is pointing down and to the right
if (nGradient < 0 && posY > otherY) {
angleBetweenLinesRads = Math.PI - angleBetweenLinesRads
remainingAngle = Math.PI / 2 - angleBetweenLinesRads - angleToHorizontal;
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength
narrowY1 = Math.cos(remainingAngle) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX + narrowX1, posY + narrowY1)
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX + narrowX2, posY - narrowY2)
if (fillArrow == true)
context.lineTo(posX + narrowX1, posY + narrowY1);
} else
///case 4 - arrow is pointing down and to the left
if (nGradient > 0 && posY > otherY) {
angleBetweenLinesRads = Math.PI - angleBetweenLinesRads
remainingAngle = Math.PI / 2 - angleBetweenLinesRads - angleToHorizontal;
//first half of arrow
narrowX1 = Math.sin(remainingAngle) * arrowLength
narrowY1 = Math.cos(remainingAngle) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX - narrowX1, posY + narrowY1)
// second half of arrow
narrowX2 = Math.cos(angleBetweenLinesRads - angleToHorizontal) * arrowLength
narrowY2 = Math.sin(angleBetweenLinesRads - angleToHorizontal) * arrowLength
context.moveTo(posX, posY)
context.lineTo(posX - narrowX2, posY - narrowY2)
if (fillArrow == true)
context.lineTo(posX - narrowX1, posY + narrowY1);
}
if (doubleEnded == true && endToDraw == "fin") {
endToDraw = "start";
} else {
endToDraw = "none";
}
} while (endToDraw !== "none");
context.closePath();
context.stroke();
if (fillArrow == true)
context.fill();
context.restore();
}
function drawVector(vectorCtx, lineEndPointX1, lineEndPointY1, lineEndPointX2, lineEndPointY2, arrowLength, angleBetweenLinesInRadians, opt_color, opt_lineWidth) {
if (typeof opt_color == 'undefined') {
vectorCtx.strokeStyle = "#000";
}
if (typeof opt_lineWidth == 'undefined') {
vectorCtx.lineWidth = 5;
}
//draw line
vectorCtx.beginPath();
vectorCtx.moveTo(lineEndPointX1, lineEndPointY1);
vectorCtx.lineTo(lineEndPointX2, lineEndPointY2);
//draw Arrow
var nMidpointX = (lineEndPointX1 + lineEndPointX2) / 2
var nMidpointY = (lineEndPointY1 + lineEndPointY2) / 2
var nGradient = (-1 * (lineEndPointY2 - lineEndPointY1)) / (lineEndPointX2 - lineEndPointX1);
var angleToHorizontal = Math.abs(Math.atan(nGradient));
var remainingAngle = Math.PI / 2 - angleBetweenLinesInRadians - angleToHorizontal;
var narrowX;
var narrowY;
///case 1 - arrow is pointing up and to the left
if (nGradient < 0 && lineEndPointY2 < lineEndPointY1) {
//first half of arrow
narrowX = Math.sin(remainingAngle) * arrowLength
narrowY = Math.cos(remainingAngle) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX + narrowX, nMidpointY + narrowY)
// second half of arrow
narrowX = Math.cos(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
narrowY = Math.sin(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX + narrowX, nMidpointY - narrowY)
}
///case 2 - arrow is pointing up and to the right
if (nGradient > 0 && lineEndPointY2 < lineEndPointY1) {
//first half of arrow
narrowX = Math.sin(remainingAngle) * arrowLength
narrowY = Math.cos(remainingAngle) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX - narrowX, nMidpointY + narrowY)
// second half of arrow
narrowX = Math.cos(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
narrowY = Math.sin(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX - narrowX, nMidpointY - narrowY)
}
//gradient is 0 and pointing right
if (nGradient == 0 && lineEndPointX2 > lineEndPointX1) {
//first half of arrow
narrowX = Math.sin(remainingAngle) * arrowLength
narrowY = Math.cos(remainingAngle) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX - narrowX, nMidpointY + narrowY)
// second half of arrow
narrowX = Math.cos(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
narrowY = Math.sin(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX - narrowX, nMidpointY - narrowY)
}
// gradient is - and pointing left
if (nGradient == 0 && lineEndPointX2 < lineEndPointX1) {
//first half of arrow
narrowX = Math.sin(remainingAngle) * arrowLength
narrowY = Math.cos(remainingAngle) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX + narrowX, nMidpointY + narrowY)
// second half of arrow
narrowX = Math.cos(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
narrowY = Math.sin(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX + narrowX, nMidpointY - narrowY)
}
///case 3 - arrow is pointing down and to the right
if (nGradient < 0 && lineEndPointY2 > lineEndPointY1) {
angleBetweenLinesInRadians = Math.PI - angleBetweenLinesInRadians
remainingAngle = Math.PI / 2 - angleBetweenLinesInRadians - angleToHorizontal;
//first half of arrow
narrowX = Math.sin(remainingAngle) * arrowLength
narrowY = Math.cos(remainingAngle) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX + narrowX, nMidpointY + narrowY)
// second half of arrow
narrowX = Math.cos(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
narrowY = Math.sin(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX + narrowX, nMidpointY - narrowY)
}
///case 4 - arrow is pointing down and to the left
if (nGradient > 0 && lineEndPointY2 > lineEndPointY1) {
angleBetweenLinesInRadians = Math.PI - angleBetweenLinesInRadians
remainingAngle = Math.PI / 2 - angleBetweenLinesInRadians - angleToHorizontal;
//first half of arrow
narrowX = Math.sin(remainingAngle) * arrowLength
narrowY = Math.cos(remainingAngle) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX - narrowX, nMidpointY + narrowY)
// second half of arrow
narrowX = Math.cos(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
narrowY = Math.sin(angleBetweenLinesInRadians - angleToHorizontal) * arrowLength
vectorCtx.moveTo(nMidpointX, nMidpointY)
vectorCtx.lineTo(nMidpointX - narrowX, nMidpointY - narrowY)
}
vectorCtx.closePath();
vectorCtx.stroke();
}
function measureAngle(object) {
// REQUIRED
var aPos = object.a; // array: [x,y]
var bPos = object.b; // array: [x,y]
var cPos = object.c; // array: [x,y]
// OPTIONAL
var angleType = object.angleType || 'radians';
// Work out the angles seperately - measured in radians anti-clockwise from north
var m1 = (bPos[1] - aPos[1]) / (aPos[0] - bPos[0]);
var angle1 = (Math.PI / 2 - Math.atan(m1));
if (aPos[0] <= bPos[0])
angle1 += Math.PI;
if (aPos[0] == bPos[0]) { // if infinite gradient
if (aPos[1] < bPos[1]) {
angle1 = 0;
} else {
angle1 = Math.PI;
}
}
var m2 = (cPos[1] - bPos[1]) / (bPos[0] - cPos[0]);
var angle2 = (Math.PI / 2 - Math.atan(m2));
if (bPos[0] >= cPos[0])
angle2 += Math.PI;
if (bPos[0] == cPos[0]) { // if infinite gradient
if (bPos[1] > cPos[1]) {
angle2 = 0;
} else {
angle2 = Math.PI;
}
}
var angleDiff = angle2 - angle1;
if (angle1 > angle2)
angleDiff = angle2 - (angle1 - 2 * Math.PI);
if (angleType == 'degrees')
angleDiff = angleDiff * 180 / Math.PI;
return angleDiff;
}
function drawAngle(obj) {
// REQUIRED
var ctx = obj.ctx || obj.context;
var aPos = obj.a; // array: [x,y]
var bPos = obj.b; // array: [x,y]
var cPos = obj.c; // array: [x,y]
// OPTIONAL
if (typeof obj.sf !== 'undefined') {
var sf = obj.sf;
} else {
var sf = 1;
}
if (sf !== 1) {
aPos = [aPos[0] * sf, aPos[1] * sf];
bPos = [bPos[0] * sf, bPos[1] * sf];
cPos = [cPos[0] * sf, cPos[1] * sf];
}
var labelCtx = obj.labelCtx || ctx;
var radius = obj.angleRadius * sf || obj.radius * sf || 35 * sf;
var forceRightAngle = boolean(obj.forceRightAngle, false);
var squareForRight = boolean(obj.squareForRight, true);
var labelIfRight = boolean(obj.labelIfRight, false);
var drawLines = boolean(obj.drawLines, false);
var lineWidth = obj.lineWidth * sf || 4 * sf;
var armWidth = obj.armWidth * sf || lineWidth;
var lineColor = obj.lineColor || '#000';
var armColor = obj.armColor || lineColor;
var numOfCurves = obj.numOfCurves || 1;
var curveGap = obj.curveGap || 4 * sf + lineWidth;
var drawCurve = boolean(obj.drawCurve, true);
var curveWidth = obj.curveWidth * sf || lineWidth;
var curveColor = obj.curveColor || lineColor;
var fill = boolean(obj.fill, false);
var fillColor = obj.fillColor || '#CCF';
if (fillColor == 'none')
fill = false;
var label = obj.label || [''];
var labelFont = obj.labelFont || 'Arial';
var labelFontSize = obj.labelFontSize * sf || 30 * sf;
var labelColor = obj.labelColor || '#000';
var labelRadius = obj.labelRadius * sf || radius * 1.1;
var labelBox = obj.labelBox || {
type: 'none'
};
if (boolean(obj.labelBackFill, false) === true) {
labelBox = {
type: 'tight',
color: mainCanvasFillStyle,
borderColor: mainCanvasFillStyle,
padding: 0.1
};
}
var labelMeasure = boolean(obj.labelMeasure, false);
var measureRoundTo = obj.measureRoundTo || 1;
var angleType = obj.angleType || 'degrees';
function lowerCaseTest(string) {
var upperCase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789lkhfdb'.split('');
for (var i = 0; i < upperCase.length; i++) {
if (string.indexOf(upperCase[i]) > -1)
return false;
}
return true;
}
// Work out the angles required - measured in radians anti-clockwise from north
var m1 = (bPos[1] - aPos[1]) / (aPos[0] - bPos[0]);
var angle1 = (Math.PI / 2 - Math.atan(m1));
if (aPos[0] <= bPos[0])
angle1 += Math.PI;
if (aPos[0] == bPos[0]) { // if infinite gradient
if (aPos[1] < bPos[1]) {
angle1 = 0;
} else {
angle1 = Math.PI;
}
}
var m2 = (cPos[1] - bPos[1]) / (bPos[0] - cPos[0]);
var angle2 = (Math.PI / 2 - Math.atan(m2));
if (bPos[0] >= cPos[0])
angle2 += Math.PI;
if (bPos[0] == cPos[0]) { // if infinite gradient
if (bPos[1] > cPos[1]) {
angle2 = 0;
} else {
angle2 = Math.PI;
}
}
var angleDiff = angle2 - angle1;
if (angle1 > angle2) angleDiff = angle2 - (angle1 - 2 * Math.PI);
// test if right-angled
if (forceRightAngle == true || (squareForRight == true && roundToNearest(angleDiff * 180 / Math.PI, measureRoundTo) == 90)) {
var dAngle = angle1 + 0.5 * angleDiff;
if (angle1 > angle2) {
dAngle = (angle1 - 2 * Math.PI) + 0.5 * angleDiff;
}
var dPos = [bPos[0] + radius * Math.cos(dAngle - Math.PI / 2), bPos[1] + radius * Math.sin(dAngle - Math.PI / 2)];
// other vertices of the square
var abLength = Math.sqrt(Math.pow(bPos[0] - aPos[0], 2) + Math.pow(bPos[1] - aPos[1], 2));
var aPos2 = [bPos[0] + (aPos[0] - bPos[0]) * radius / (abLength * Math.sqrt(2)), bPos[1] + (aPos[1] - bPos[1]) * radius / (abLength * Math.sqrt(2))];
var bcLength = Math.sqrt(Math.pow(bPos[0] - cPos[0], 2) + Math.pow(bPos[1] - cPos[1], 2));
var cPos2 = [bPos[0] + (cPos[0] - bPos[0]) * radius / (bcLength * Math.sqrt(2)), bPos[1] + (cPos[1] - bPos[1]) * radius / (bcLength * Math.sqrt(2))];
if (fill == true) {
ctx.fillStyle = fillColor;
ctx.beginPath();
ctx.moveTo(bPos[0], bPos[1]);
ctx.lineTo(aPos2[0], aPos2[1]);
ctx.lineTo(dPos[0], dPos[1]);
ctx.lineTo(cPos2[0], cPos2[1]);
ctx.closePath();
ctx.fill();
}
if (drawLines == true) {
ctx.strokeStyle = armColor;
ctx.lineWidth = armWidth;
ctx.beginPath();
ctx.joinCap = 'round';
ctx.moveTo(aPos[0], aPos[1]);
ctx.lineTo(bPos[0], bPos[1]);
ctx.lineTo(cPos[0], cPos[1]);
ctx.stroke();
}
if (drawCurve == true) {
ctx.strokeStyle = curveColor;
ctx.lineWidth = curveWidth;
ctx.beginPath();
ctx.moveTo(aPos2[0], aPos2[1]);
ctx.lineTo(dPos[0], dPos[1]);
ctx.lineTo(cPos2[0], cPos2[1]);
ctx.stroke();
}
if (labelIfRight == true) {
if (labelMeasure == true) {
var angleDiff2 = angleDiff;
if (angleType == 'degrees')
angleDiff2 = angleDiff2 * 180 / Math.PI;
angleDiff2 = roundToNearest(angleDiff2, measureRoundTo);
label = [String(angleDiff2)];
if (angleType == 'degrees') {
label = [String(angleDiff2) + String.fromCharCode(0x00B0)]
};
}
// work out label position
// d is midPoint of ac
var dAngle = angle1 + 0.5 * angleDiff;
if (angle1 > angle2) {
dAngle = (angle1 - 2 * Math.PI) + 0.5 * angleDiff;
}
var dPos = [bPos[0] + 1.85 * radius * Math.cos(dAngle - Math.PI / 2), bPos[1] + 1.85 * radius * Math.sin(dAngle - Math.PI / 2)];
if (labelCtx == ctx) {
drawMathsText(labelCtx, label, labelFontSize, dPos[0], dPos[1] + 0.5 * labelFontSize, true, [], 'center', 'bottom');
} else {
var labelCanvas = labelCtx.canvas;
var labelWidth = labelCanvas.width;
var labelHeight = labelCanvas.height;
//console.log(taskObject,pageIndex);
//var canvasNum = taskObject.indexOf(labelCanvas);
//var labelData = taskObjectData[canvasNum];
labelCtx.clearRect(0, 0, labelWidth, labelHeight);
drawMathsText(labelCtx, label, labelFontSize, 0.5 * labelWidth, 0.5 * labelHeight + 0.5 * labelFontSize, true, [], 'center', 'bottom');
labelCanvas.data[100] = dPos[0] - 0.5 * labelWidth;
labelCanvas.data[101] = dPos[1] - 0.5 * labelHeight;
resizeCanvas3(labelCanvas);
}
} else if (labelCtx !== ctx) {
var labelCanvas = labelCtx.canvas;
var labelWidth = labelCanvas.width;
var labelHeight = labelCanvas.height;
labelCtx.clearRect(0, 0, labelWidth, labelHeight);
}
} else {
// points at the end of the arc drawn for the angle
var abLength = Math.sqrt(Math.pow(bPos[0] - aPos[0], 2) + Math.pow(bPos[1] - aPos[1], 2));
var aPos2 = [bPos[0] + (aPos[0] - bPos[0]) * radius / abLength, bPos[1] + (aPos[1] - bPos[1]) * radius / abLength];
var bcLength = Math.sqrt(Math.pow(bPos[0] - cPos[0], 2) + Math.pow(bPos[1] - cPos[1], 2));
var cPos2 = [bPos[0] + (cPos[0] - bPos[0]) * radius / bcLength, bPos[1] + (cPos[1] - bPos[1]) * radius / bcLength];
if (labelMeasure == true) {
var angleDiff2 = angleDiff;
if (angleType == 'degrees')
angleDiff2 = angleDiff2 * 180 / Math.PI;
angleDiff2 = roundToNearest(angleDiff2, measureRoundTo);
label = [String(angleDiff2)];
if (angleType == 'degrees') {
label = [String(angleDiff2) + String.fromCharCode(0x00B0)]
};
}
// work out label position
// d is midPoint of ac
var dAngle = angle1 + 0.5 * angleDiff;
if (angle1 > angle2) {
dAngle = (angle1 - 2 * Math.PI) + 0.5 * angleDiff;
}
var labelRadiusFactor = 1.5 + 0.11 * (removeTags(label[0]).length - 1);
var lowerCaseOnly = lowerCaseTest(removeTags(label[0]));
var lcHeightAdjust = 0;
if (lowerCaseOnly == true)
lcHeightAdjust = labelFontSize / 5;
if (dAngle < Math.PI / 6 || dAngle > 11 * Math.PI / 6 || (dAngle > 5 * Math.PI / 6 && dAngle < 7 * Math.PI / 6)) {
labelRadiusFactor = labelRadiusFactor * 0.9;
} else if (dAngle < Math.PI / 4 || dAngle > 7 * Math.PI / 4 || (dAngle > 3 * Math.PI / 4 && dAngle < 5 * Math.PI / 4)) {
labelRadiusFactor = labelRadiusFactor * 0.95;
}
var dPos = [bPos[0] + labelRadiusFactor * labelRadius * Math.cos(dAngle - Math.PI / 2), bPos[1] + labelRadiusFactor * labelRadius * Math.sin(dAngle - Math.PI / 2) - lcHeightAdjust];
/* draw a dot at dPos (for testing angle label position
ctx.fillStyle = '#F00';
ctx.beginPath();
ctx.arc(dPos[0],dPos[1],8,0,2*Math.PI);
ctx.closePath();
ctx.fill();
//*/
label.unshift('<<color:' + labelColor + '>><<fontSize:' + labelFontSize + '>><<font:' + labelFont + '>>');
if (fill == true && boolean(obj.measureOnly, true) == true) {
ctx.fillStyle = fillColor;
ctx.beginPath();
ctx.moveTo(bPos[0], bPos[1]);
ctx.lineTo(aPos2[0], aPos2[1]);
ctx.arc(bPos[0], bPos[1], radius, angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.lineTo(cPos2[0], cPos2[1]);
ctx.closePath();
ctx.fill();
}
if (drawLines == true && boolean(obj.measureOnly, true) == true) {
ctx.strokeStyle = armColor;
ctx.lineWidth = armWidth;
ctx.joinCap = 'round';
ctx.beginPath();
ctx.moveTo(aPos[0], aPos[1]);
ctx.lineTo(bPos[0], bPos[1]);
ctx.lineTo(cPos[0], cPos[1]);
ctx.stroke();
}
if (drawCurve == true && boolean(obj.measureOnly, true) == true) {
ctx.strokeStyle = curveColor;
ctx.lineWidth = curveWidth;
if (numOfCurves == 1) {
ctx.beginPath();
ctx.moveTo(aPos2[0], aPos2[1]);
ctx.arc(bPos[0], bPos[1], Math.abs(radius), angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.stroke();
} else if (numOfCurves == 2) {
ctx.beginPath();
ctx.arc(bPos[0], bPos[1], Math.abs(radius - curveGap / 2), angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(bPos[0], bPos[1], Math.abs(radius + curveGap / 2), angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.stroke();
} else if (numOfCurves == 3) {
ctx.beginPath();
ctx.arc(bPos[0], bPos[1], Math.abs(radius - curveGap), angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(bPos[0], bPos[1], Math.abs(radius), angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(bPos[0], bPos[1], Math.abs(radius + curveGap), angle1 - 0.5 * Math.PI, angle2 - 0.5 * Math.PI);
ctx.stroke();
}
}
if (labelCtx == ctx) {
if (boolean(obj.measureLabelOnly, false) == true) {
var angleLabelPos = [dPos[0] - 57.8 / 2, dPos[1] - 25, 57.8, 50];
} else {
var angleLabelPos = text({
ctx: labelCtx,
left: dPos[0] - 200,
top: dPos[1] - 200,
width: 400,
height: 400,
textArray: label,
align: 'center',
vertAlign: 'middle',
box: labelBox,
minTightWidth: 1,
minTightHeight: 1
}).tightRect;
}
} else {
var labelCanvas = labelCtx.canvas;
var labelWidth = labelCanvas.width;
var labelHeight = labelCanvas.height;
//var canvasNum = taskObject[pageIndex].indexOf(labelCanvas);
//var labelData = taskObjectData[pageIndex][canvasNum];
labelCtx.clearRect(0, 0, labelWidth, labelHeight);
drawMathsText(labelCtx, label, labelFontSize, 0.5 * labelWidth, 0.5 * labelHeight + 0.5 * labelFontSize, true, [], 'center', 'bottom');
labelCanvas.data[100] = dPos[0] - 0.5 * labelWidth;
labelCanvas.data[101] = dPos[1] - 0.5 * labelHeight;
resizeCanvas3(labelCanvas);
//resize();
}
}
return angleLabelPos;
}
function getAngleMidAngle(obj) {
// Work out the angles required - measured in radians anti-clockwise from north
var m1 = (obj.b[1] - obj.a[1]) / (obj.a[0] - obj.b[0]);
var angle1 = (Math.PI / 2 - Math.atan(m1));
if (obj.a[0] <= obj.b[0])
angle1 += Math.PI;
if (obj.a[0] == obj.b[0]) { // if infinite gradient
if (obj.a[1] < obj.b[1]) {
angle1 = 0;
} else {
angle1 = Math.PI;
}
}
var m2 = (obj.c[1] - obj.b[1]) / (obj.b[0] - obj.c[0]);
var angle2 = (Math.PI / 2 - Math.atan(m2));
if (obj.b[0] >= obj.c[0])
angle2 += Math.PI;
if (obj.b[0] == obj.c[0]) { // if infinite gradient
if (obj.b[1] > obj.c[1]) {
angle2 = 0;
} else {
angle2 = Math.PI;
}
}
if (angle1 > angle2) {
var angleDiff = angle2 - (angle1 - 2 * Math.PI);
} else {
var angleDiff = angle2 - angle1;
}
var dAngle = angle1 + 0.5 * angleDiff;
if (angle1 > angle2) {
dAngle = (angle1 - 2 * Math.PI) + 0.5 * angleDiff;
}
return dAngle - Math.PI / 2;
}
function getAngleLabelPos(obj,radius) {
var angle = getAngleMidAngle(obj);
return [obj.b[0]+radius*Math.cos(angle),obj.b[1]+radius*Math.sin(angle)]
}
function drawStar(obj) {
var ctx = obj.ctx;
var c = obj.center || obj.c;
var r = obj.radius || obj.r;
var p = obj.points || obj.p || 5;
var s = obj.step || obj.s || 2;
var vertices = [];
for (var i = 0; i < p; i++) {
var angle = -Math.PI / 2 + i * (2 * Math.PI) / p;
vertices.push([c[0] + r * Math.cos(angle), c[1] + r * Math.sin(angle)]);
}
ctx.moveTo(vertices[0][0], vertices[0][1]);
for (var i = p; i >= 0; i--) {
ctx.lineTo(vertices[(i * s) % p][0], vertices[(i * s) % p][1]);
}
}
function drawAnglesAroundPoint(obj) {
/* eg.
drawAnglesAroundPoint({
ctx:ctx1,
center:[400,300],
points:[[300,300],[400,200],[450,250],[480,320]],
lineColor:'#000',
thickness:4,
angles:[{fill:true,fillColor:"#CFC",lineWidth:2,labelFontSize:25,labelMeasure:true,labelRadius:33,radius:30},{fill:false,fillColor:"#CFC",lineWidth:2,labelFontSize:25,labelMeasure:true,labelRadius:33,radius:30},{fill:true,fillColor:"#CFC",lineWidth:2,labelFontSize:25,labelMeasure:true,labelRadius:33,radius:30},{fill:false,fillColor:"#CFC",lineWidth:2,labelFontSize:25,labelMeasure:true,labelRadius:33,radius:30}
]
});
*/
// required
var ctx = obj.ctx;
var points = obj.points;
var center = obj.center;
// optional
var lineColor = obj.lineColor || obj.color || false;
var lineWidth = obj.lineWidth || obj.thickness || false;
var angles = obj.angles || [];
ctx.save();
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = lineWidth;
ctx.strokeStye = lineColor;
var angleLabelPos = [];
for (var i = 0; i < points.length; i++) {
if (typeof angles[i] == 'object' && angles[i] !== null) {
angles[i].ctx = ctx;
angles[i].b = center;
angles[i].a = points[i];
if (i == points.length - 1) {
angles[i].c = points[0];
} else {
angles[i].c = points[i + 1];
}
angles[i].drawLines = false;
if (typeof angles[i].lineWidth == 'undefined')
angles[i].lineWidth = ctx.lineWidth;
if (typeof angles[i].lineColor == 'undefined')
angles[i].lineColor = ctx.lineWidth;
if (typeof angles[i].labelColor == 'undefined')
angles[i].labelColor = ctx.strokeStyle;
angleLabelPos[i] = drawAngle(angles[i]);
}
}
ctx.lineWidth = lineWidth;
ctx.strokeStye = lineColor;
ctx.beginPath();
for (var p = 0; p < points.length; p++) {
ctx.moveTo(center[0], center[1]);
ctx.lineTo(points[p][0], points[p][1]);
}
ctx.stroke();
ctx.restore();
return angleLabelPos;
}
function drawCylinder(obj) {
var ctx = obj.ctx;
var pos = obj.pos; // position of centre of base
var h = obj.h || obj.height;
var r = obj.r || obj.radius;
var direction = obj.direction || obj.dir || 'vert'; // 'vert', 'horiz1' or 'horiz2'
var angle = obj.angle || Math.PI / 6; // for 'horiz2' only
var lineWidth = obj.lineWidth || obj.thickness || false;
var lineColor = obj.lineColor || obj.color || false;
var lineDash = obj.lineDash || [];
ctx.save();
if (lineWidth !== false)
ctx.lineWidth = lineWidth;
if (lineColor !== false)
ctx.strokeStyle = lineColor;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
var transparent = boolean(obj.transparent, true);
var backLineWidth = obj.lineWidth;
var backLineColor = obj.backLineWidth || getShades(ctx.strokeStyle)[8];
var backLineDash = obj.backLineDash || [7, 10];
var fill = boolean(obj.fill, false);
if (typeof obj.fillColors !== 'undefined') {
var fillColors = obj.fillColors;
} else if (typeof obj.fillColor !== 'undefined') {
var fillColors = [obj.fillColor, obj.fillColor];
} else {
var fillColors = ['#FCC', '#CFC'];
}
ctx.translate(pos[0], pos[1]);
if (direction == 'vert') {
ctx.scale(1, 0.4);
// draw back of bottom ellipse
if (transparent == true && fill == false) {
ctx.save();
ctx.lineWidth = backLineWidth;
ctx.strokeStyle = backLineColor;
ctx.setLineDash(backLineDash);
ctx.beginPath();
ctx.arc(0, 0, r, Math.PI, 2 * Math.PI);
ctx.stroke();
ctx.restore();
}
// draw front
ctx.beginPath();
ctx.moveTo(r, 0);
ctx.arc(0, 0, r, 0, Math.PI);
ctx.lineTo(-r, -h / 0.4);
ctx.arc(0, -h / 0.4, r, Math.PI, 0, true);
ctx.lineTo(r, 0);
if (fill == true) {
ctx.fillStyle = fillColors[0];
ctx.fill();
}
ctx.stroke();
// draw top
ctx.beginPath();
ctx.arc(0, -h / 0.4, r, 0, 2 * Math.PI);
if (fill == true && typeof fillColors[1] !== 'undefined' && fillColors[1] !== 'none' && fillColors[1] !== false) {
ctx.fillStyle = fillColors[1];
ctx.fill();
}
ctx.stroke();
} else if (direction == 'horiz1') {
ctx.scale(0.4, 1);
// draw back of right ellipse
if (transparent == true && fill == false) {
ctx.save();
ctx.lineWidth = backLineWidth;
ctx.strokeStyle = backLineColor;
ctx.setLineDash(backLineDash);
ctx.beginPath();
ctx.arc(0, 0, r, 0.5 * Math.PI, 1.5 * Math.PI);
ctx.stroke();
ctx.restore();
}
// draw front
ctx.beginPath();
ctx.moveTo(0, r);
ctx.arc(0, 0, r, 0.5 * Math.PI, 1.5 * Math.PI, true);
ctx.lineTo(-h / 0.4, -r);
ctx.arc(-h / 0.4, 0, r, 1.5 * Math.PI, 0.5 * Math.PI);
ctx.lineTo(0, r);
if (fill == true) {
ctx.fillStyle = fillColors[0];
ctx.fill();
}
ctx.stroke();
// draw left ellipse
ctx.beginPath();
ctx.arc(-h / 0.4, 0, r, 0, 2 * Math.PI);
if (fill == true && typeof fillColors[1] !== 'undefined' && fillColors[1] !== 'none' && fillColors[1] !== false) {
ctx.fillStyle = fillColors[1];
ctx.fill();
}
ctx.stroke();
} else if (direction == 'horiz2') {
var pos2 = [h * Math.cos(angle), -h * Math.sin(angle)];
var angle1 = Math.PI / 2 - angle;
var angle2 = -angle - 0.5 * Math.PI;
var a = [r * Math.cos(angle1), r * Math.sin(angle1)];
var b = [r * Math.cos(angle2), r * Math.sin(angle2)];
var c = [pos2[0] + r * Math.cos(angle1), pos2[1] + r * Math.sin(angle1)];
var d = [pos2[0] + r * Math.cos(angle2), pos2[1] + r * Math.sin(angle2)];
// draw back of right circle
if (transparent == true && fill == false) {
ctx.save();
ctx.lineWidth = backLineWidth;
ctx.strokeStyle = backLineColor;
ctx.setLineDash(backLineDash);
ctx.beginPath();
ctx.arc(pos2[0], pos2[1], r, angle1, angle2);
ctx.stroke();
ctx.restore();
}
// front
ctx.beginPath();
ctx.moveTo(d[0], d[1]);
ctx.arc(pos2[0], pos2[1], r, angle2, angle1);
ctx.lineTo(a[0], a[1]);
ctx.arc(0, 0, r, angle1, angle2, true);
ctx.lineTo(d[0], d[1]);
if (fill == true) {
ctx.fillStyle = fillColors[0];
ctx.fill();
}
ctx.stroke();
// front circle
ctx.beginPath();
ctx.arc(0, 0, r, 0, 2 * Math.PI);
if (fill == true && typeof fillColors[1] !== 'undefined' && fillColors[1] !== 'none' && fillColors[1] !== false) {
ctx.fillStyle = fillColors[1];
ctx.fill();
}
ctx.stroke();
}
ctx.restore();
}
function drawCuboid(obj) {
var ctx = obj.ctx;
var x = obj.x || obj.pos[0];
var y = obj.y || obj.pos[1];
var z = obj.z || obj.pos[2];
var xd = obj.xd || obj.dims[0];
var yd = obj.yd || obj.dims[1];
var zd = obj.zd || obj.dims[2];
var labels = obj.labels;
var unitBaseVectors = obj.unitBaseVectors || [[Math.sqrt(3) / 2, -1 / 2], [-Math.sqrt(3) / 2, -1 / 2], [0, -1]];
var unitLength = obj.unitLength || 15;
var baseVector = obj.baseVector || obj.baseVectors || [[unitLength * unitBaseVectors[0][0], unitLength * unitBaseVectors[0][1]], [unitLength * unitBaseVectors[1][0], unitLength * unitBaseVectors[1][1]], [unitLength * unitBaseVectors[2][0], unitLength * unitBaseVectors[2][1]]];
var origin = obj.origin || [0, 700];
var mag = obj.mag || 15;
var lineWidth = obj.lineWidth || obj.thickness || false;
var lineColor = obj.lineColor || obj.color || false;
var lineDash = obj.lineDash || [];
var showUnitCubes = boolean(obj.showUnitCubes, false);
ctx.save();
if (lineWidth !== false)
ctx.lineWidth = lineWidth;
if (lineColor !== false)
ctx.strokeStyle = lineColor;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
var transparent = boolean(obj.transparent, true);
var backLineWidth = obj.lineWidth;
var backLineColor = obj.backLineWidth || getShades(ctx.strokeStyle)[8];
var backLineDash = obj.backLineDash || [7, 10];
var fill = boolean(obj.fill, false);
if (typeof obj.fillColors !== 'undefined') {
var fillColors = obj.fillColors
} else if (typeof obj.fillColor !== 'undefined') {
var fillColors = [obj.fillColor, obj.fillColor, obj.fillColor];
} else {
var fillColors = ['#FCC', '#CFC', '#CCF'];
}
var cuboid = [[x, y, z], [x + xd, y, z], [x + xd, y + yd, z], [x, y + yd, z], [x, y, z + zd], [x + xd, y, z + zd], [x + xd, y + yd, z + zd], [x, y + yd, z + zd]];
var cuboidPos = [];
for (var v = 0; v < cuboid.length; v++) {
cuboidPos[v] = [];
cuboidPos[v][0] = origin[0];
cuboidPos[v][0] += cuboid[v][0] * baseVector[0][0];
cuboidPos[v][0] += cuboid[v][1] * baseVector[1][0];
cuboidPos[v][0] += cuboid[v][2] * baseVector[2][0];
cuboidPos[v][1] = origin[1];
cuboidPos[v][1] += cuboid[v][0] * baseVector[0][1];
cuboidPos[v][1] += cuboid[v][1] * baseVector[1][1];
cuboidPos[v][1] += cuboid[v][2] * baseVector[2][1];
}
if (fill == true) {
ctx.fillStyle = fillColors[0];
if (baseVector[0][0] * baseVector[2][1] - baseVector[0][1] * baseVector[2][0] > 0) {
ctx.beginPath();
ctx.moveTo(cuboidPos[3][0], cuboidPos[3][1]);
ctx.lineTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.lineTo(cuboidPos[6][0], cuboidPos[6][1]);
ctx.lineTo(cuboidPos[7][0], cuboidPos[7][1]);
ctx.lineTo(cuboidPos[3][0], cuboidPos[3][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
} else {
ctx.beginPath();
ctx.moveTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.lineTo(cuboidPos[1][0], cuboidPos[1][1]);
ctx.lineTo(cuboidPos[5][0], cuboidPos[5][1]);
ctx.lineTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.lineTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
ctx.fillStyle = fillColors[1];
if (baseVector[0][0] * baseVector[1][1] - baseVector[0][1] * baseVector[1][0] > 0) {
ctx.beginPath();
ctx.moveTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.lineTo(cuboidPos[1][0], cuboidPos[1][1]);
ctx.lineTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.lineTo(cuboidPos[3][0], cuboidPos[3][1]);
ctx.lineTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
} else {
ctx.beginPath();
ctx.moveTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.lineTo(cuboidPos[5][0], cuboidPos[5][1]);
ctx.lineTo(cuboidPos[6][0], cuboidPos[6][1]);
ctx.lineTo(cuboidPos[7][0], cuboidPos[7][1]);
ctx.lineTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
ctx.fillStyle = fillColors[2];
if (baseVector[2][0] * baseVector[1][1] - baseVector[2][1] * baseVector[1][0] > 0) {
ctx.beginPath();
ctx.moveTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.lineTo(cuboidPos[1][0], cuboidPos[1][1]);
ctx.lineTo(cuboidPos[5][0], cuboidPos[5][1]);
ctx.lineTo(cuboidPos[6][0], cuboidPos[6][1]);
ctx.lineTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
} else {
ctx.beginPath();
ctx.moveTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.lineTo(cuboidPos[3][0], cuboidPos[3][1]);
ctx.lineTo(cuboidPos[7][0], cuboidPos[7][1]);
ctx.lineTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.lineTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
}
if (fill == false && transparent == true) {
ctx.save();
ctx.lineWidth = backLineWidth;
ctx.strokeStyle = backLineColor;
ctx.setLineDash(backLineDash);
ctx.beginPath();
ctx.moveTo(cuboidPos[1][0], cuboidPos[1][1]);
ctx.lineTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.moveTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.lineTo(cuboidPos[3][0], cuboidPos[3][1]);
ctx.moveTo(cuboidPos[2][0], cuboidPos[2][1]);
ctx.lineTo(cuboidPos[6][0], cuboidPos[6][1]);
ctx.stroke();
ctx.restore();
}
ctx.beginPath();
//base
ctx.moveTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.lineTo(cuboidPos[1][0], cuboidPos[1][1]);
ctx.moveTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.lineTo(cuboidPos[3][0], cuboidPos[3][1]);
//sides
ctx.moveTo(cuboidPos[0][0], cuboidPos[0][1]);
ctx.lineTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.moveTo(cuboidPos[1][0], cuboidPos[1][1]);
ctx.lineTo(cuboidPos[5][0], cuboidPos[5][1]);
ctx.moveTo(cuboidPos[3][0], cuboidPos[3][1]);
ctx.lineTo(cuboidPos[7][0], cuboidPos[7][1]);
//top
ctx.moveTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.lineTo(cuboidPos[5][0], cuboidPos[5][1]);
ctx.lineTo(cuboidPos[6][0], cuboidPos[6][1]);
ctx.lineTo(cuboidPos[7][0], cuboidPos[7][1]);
ctx.lineTo(cuboidPos[4][0], cuboidPos[4][1]);
ctx.closePath();
ctx.stroke();
/*
ctx.fillStyle = '#FFC';
ctx.fillRect(0.5*(cuboidPos[0][0]+cuboidPos[1][0])+8,0.5*(cuboidPos[0][1]+cuboidPos[1][1])-5,19,40);
ctx.fillRect(0.5*(cuboidPos[0][0]+cuboidPos[3][0])-8,0.5*(cuboidPos[0][1]+cuboidPos[3][1])-5,-19,40);
ctx.fillRect(0.5*(cuboidPos[3][0]+cuboidPos[7][0])-8,0.5*(cuboidPos[3][1]+cuboidPos[7][1])-5,-79,40);
*/
if (showUnitCubes == true) {
ctx.beginPath();
for (var i = 1; i < xd; i++) {
var pos1 = interpolateTwoPoints(cuboidPos[0], cuboidPos[1], i / xd);
var pos2 = interpolateTwoPoints(cuboidPos[4], cuboidPos[5], i / xd);
ctx.moveTo(pos1[0], pos1[1]);
ctx.lineTo(pos2[0], pos2[1]);
var pos1 = interpolateTwoPoints(cuboidPos[4], cuboidPos[5], i / xd);
var pos2 = interpolateTwoPoints(cuboidPos[7], cuboidPos[6], i / xd);
ctx.moveTo(pos1[0], pos1[1]);
ctx.lineTo(pos2[0], pos2[1]);
}
for (var i = 1; i < yd; i++) {
var pos1 = interpolateTwoPoints(cuboidPos[3], cuboidPos[0], i / yd);
var pos2 = interpolateTwoPoints(cuboidPos[7], cuboidPos[4], i / yd);
ctx.moveTo(pos1[0], pos1[1]);
ctx.lineTo(pos2[0], pos2[1]);
var pos1 = interpolateTwoPoints(cuboidPos[6], cuboidPos[5], i / yd);
var pos2 = interpolateTwoPoints(cuboidPos[7], cuboidPos[4], i / yd);
ctx.moveTo(pos1[0], pos1[1]);
ctx.lineTo(pos2[0], pos2[1]);
}
for (var i = 1; i < zd; i++) {
var pos1 = interpolateTwoPoints(cuboidPos[0], cuboidPos[4], i / zd);
var pos2 = interpolateTwoPoints(cuboidPos[1], cuboidPos[5], i / zd);
ctx.moveTo(pos1[0], pos1[1]);
ctx.lineTo(pos2[0], pos2[1]);
var pos1 = interpolateTwoPoints(cuboidPos[0], cuboidPos[4], i / zd);
var pos2 = interpolateTwoPoints(cuboidPos[3], cuboidPos[7], i / zd);
ctx.moveTo(pos1[0], pos1[1]);
ctx.lineTo(pos2[0], pos2[1]);
}
ctx.stroke();
}
if (typeof labels !== 'undefined') {
text({
ctx: ctx,
left: 0.5 * (cuboidPos[0][0] + cuboidPos[1][0]) + 10,
top: 0.5 * (cuboidPos[0][1] + cuboidPos[1][1]) - 5,
width: 200,
textArray: labels[0]
});
text({
ctx: ctx,
left: 0.5 * (cuboidPos[0][0] + cuboidPos[3][0]) - 210,
top: 0.5 * (cuboidPos[0][1] + cuboidPos[3][1]) - 5,
width: 200,
align: 'right',
textArray: labels[1]
});
text({
ctx: ctx,
left: 0.5 * (cuboidPos[3][0] + cuboidPos[7][0]) - 210,
top: 0.5 * (cuboidPos[3][1] + cuboidPos[7][1]) - 5,
width: 200,
align: 'right',
textArray: labels[2]
});
}
ctx.restore();
}
function drawTreeDiagram(obj) {
var ctx = obj.ctx;
var left = obj.left;
var top = obj.top;
var width = obj.width;
var height = obj.height;
var branches = obj.branches || [2, 2];
/*
var endLabelFontSize = obj.endLabelFontSize || obj.fontSize || width/20;
var midLabelFontSize = obj.midLabelFontSize || obj.fontSize || width/20
var labels = obj.labels || [[['win','lose']],[['win','lose],['win','lose']]];
var probabilities = obj.probabilities || [[[[1,2],[1,2]]],[[[1,3],[2,3]],[[3,4],[1,4]]]];
var branchColors = obj.branchColors || [[['#000','#000']],[[['#F00','#00F'],['#000','#000']]]];
var endLabelColors = obj.endLabelColors || [[['#000','#000']],[[['#F00','#00F'],['#000','#000']]]];
var midLabelColors = obj.midLabelColors || [[['#000','#000']],[[['#F00','#00F'],['#000','#000']]]];
var branchLineWidths = obj.branchLineWidths || [[[3]],[[3,3],[3,3]]];
*/
var labels = obj.labels || [['<<fontSize:30>>win'], ['<<fontSize:30>>draw'], ['<<fontSize:30>>lose']]
var probabilities = obj.probabilities || [['<<fontSize:30>>', ['frac', ['1'], ['4']]], ['<<fontSize:30>>', ['frac', ['1'], ['2']]], ['<<fontSize:30>>', ['frac', ['1'], ['4']]]];
var hiddenCanvas = document.createElement('canvas');
var ctx2 = hiddenCanvas.getContext('2d');
var labelTightRects = [];
var maxLabelWidth = 0;
for (var i = 0; i < labels.length; i++) {
labelTightRects[i] = text({
ctx: ctx2,
textArray: labels[i],
measureOnly: true
}).tightRect;
maxLabelWidth = Math.max(maxLabelWidth, labelTightRects[i][2] + 15);
}
var branchWidth = (width - maxLabelWidth * branches.length) / branches.length;
ctx.save();
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
for (var i = 0; i < branches.length; i++) { // each vertical set of branches
var forkPoints = 1;
if (i > 0) {
for (var j = 0; j < i; j++) {
forkPoints = forkPoints * branches[j];
}
}
var startX = left + i * branchWidth + i * maxLabelWidth;
var finX = startX + branchWidth;
var h = height / forkPoints; // height of surrouding rect for this fork point;
for (var j = 0; j < forkPoints; j++) {
var t = top + j * h;
var startY = t + 0.5 * h;
for (var k = 0; k < branches[i]; k++) { // each branch emanating from the fork point
if (branches[i] == 2) {
var finY = t + ((2 * k + 1) / 4) * h;
} else if (branches[i] == 3) {
var finY = t + ((2 * k + 1) / 6) * h;
}
//draw branch
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(finX, finY);
ctx.stroke();
//draw endLabel
text({
ctx: ctx,
textArray: labels[k],
left: finX,
top: finY - labelTightRects[k][3] / 2,
width: maxLabelWidth,
height: labelTightRects[k][3],
align: 'center',
horizAlign: 'middle',
box: {
type: 'none',
borderColor: '#00F',
padding: 0.001
}
});
//draw midLabel
var prob = probabilities[k];
var measure = text({
ctx: ctx2,
textArray: prob,
measureOnly: true
}).tightRect;
var l2 = (startX + finX) / 2 - measure[2] / 2;
if (k < branches[i] - 1) {
var t2 = (startY + finY) / 2 - measure[3] * 1.1;
} else {
var t2 = (startY + finY) / 2; ;
}
text({
ctx: ctx,
textArray: prob,
left: l2,
top: t2,
width: measure[2],
height: measure[3],
align: 'center',
horizAlign: 'middle',
box: {
type: 'none',
borderColor: '#F00',
padding: 0.001
}
});
}
}
}
ctx.restore();
}
function drawSpinner(context, center, radius, sectorAngles, sectorColors, arrowAngle, showArrow) {
if (typeof arrowAngle == 'undefined')
arrowAngle = -Math.PI / 4;
var show = boolean(showArrow, true);
//work out angles in radians;
var vals = [];
var total = 0;
for (var val = 0; val < sectorAngles.length; val++) {
total += sectorAngles[val];
}
var angles = [0]; // cumulative angle array
for (var val = 0; val < sectorAngles.length; val++) {
angles.push(angles[val] + (sectorAngles[val] / total) * 2 * Math.PI);
}
context.lineWidth = 4;
context.strokeStyle = '#000';
for (var val = 0; val < sectorAngles.length; val++) {
// draw a sector
context.fillStyle = sectorColors[val];
context.beginPath();
context.moveTo(center[0], center[1]);
context.arc(center[0], center[1], radius, angles[val], angles[val + 1]);
context.lineTo(center[0], center[1]);
context.closePath();
context.fill();
context.stroke();
}
if (show == true) {
context.strokeStyle = "#000";
context.lineWidth = 4;
context.beginPath();
context.moveTo(center[0], center[1]);
context.lineTo(center[0] + 0.75 * radius * Math.cos(arrowAngle), center[1] + 0.75 * radius * Math.sin(arrowAngle));
context.stroke();
drawArrow({
context: context,
startX: center[0],
startY: center[1],
finX: center[0] + 0.75 * radius * Math.cos(arrowAngle),
finY: center[1] + 0.75 * radius * Math.sin(arrowAngle),
arrowLength: 13,
color: "#000000",
lineWidth: 4,
arrowLineWidth: 4,
fillArrow: true
});
context.fillStyle = "#000";
context.beginPath();
context.arc(center[0], center[1], 5, 0, 2 * Math.PI);
context.fill();
}
}
function vennDiagram(obj) {
var ctx = obj.ctx;
var l = obj.left || obj.l || 0;
var t = obj.top || obj.t || 0;
var w = obj.width || obj.w || 400;
var h = obj.height || obj.h || w * 0.65;
var radius = obj.radius || obj.r || w * 0.25;
var centerA = obj.centerA || [l + w * 0.35, t + h / 2];
var centerB = obj.centerB || [l + w * 0.65, t + h / 2];
var lineWidth = obj.lineWidth || 4;
var lineDash = obj.lineDash || [];
var strokeStyle = obj.strokeStyle || '#000';
var colorA = obj.colorA || strokeStyle;
var colorB = obj.colorB || strokeStyle;
var labelA = obj.labelA || ['<<fontSize:' + (w / 12) + '>>A'];
var labelB = obj.labelB || ['<<fontSize:' + (w / 12) + '>>B'];
var fillStyle = obj.fillStyle || '#FCF';
var shade = obj.shade || [false, false, false, false];
ctx.save();
if (typeof ctx.setLineDash == 'undefined')
ctx.setLineDash = function () {};
ctx.setLineDash(lineDash);
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.strokeRect(l, t, w, h);
ctx.beginPath();
ctx.strokeStyle = colorA;
ctx.arc(centerA[0], centerA[1], radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = colorB;
ctx.arc(centerB[0], centerB[1], radius, 0, 2 * Math.PI);
ctx.stroke();
var xy = [centerA[0] - (radius * 1.25) * Math.cos(Math.PI / 4), centerA[1] - (radius * 1.25) * Math.cos(Math.PI / 4)];
text({
ctx: ctx,
textArray: labelA,
left: xy[0] - 100,
width: 200,
top: xy[1] - 100,
height: 200,
textAlign: 'center',
vertAlign: 'middle',
padding: 0.1, /*box:{type:'tight',color:mainCanvasFillStyle,borderColor:mainCanvasFillStyle}*/
});
var xy = [centerB[0] + (radius * 1.25) * Math.cos(Math.PI / 4), centerB[1] - (radius * 1.25) * Math.cos(Math.PI / 4)];
text({
ctx: ctx,
textArray: labelB,
left: xy[0] - 100,
width: 200,
top: xy[1] - 100,
height: 200,
textAlign: 'center',
vertAlign: 'middle',
padding: 0.1, /*box:{type:'tight',color:mainCanvasFillStyle,borderColor:mainCanvasFillStyle}*/
});
ctx.restore();
return {
ctx: ctx,
left: l,
top: t,
width: w,
height: h,
radius: radius,
centerA: centerA,
centerB: centerB,
lineWidth: lineWidth,
lineDash: lineDash,
strokeStyle: strokeStyle,
labelA: labelA,
labelB: labelB,
fillStyle: fillStyle,
shade: shade
};
}
function vennDiagram3(obj) {
var ctx = obj.ctx;
var l = obj.left || obj.l || 0;
var t = obj.top || obj.t || 0;
var w = obj.width || obj.w || 400;
var h = obj.height || obj.h || w;
var radius = obj.radius || obj.r || w * 0.27;
var centerA = obj.centerA || [l + w * 0.5 + 0.6 * radius * Math.cos(Math.PI * (1 / 2 + 2 / 3)), t + h * 0.47 + 0.6 * radius * Math.sin(Math.PI * (1 / 2 + 2 / 3))];
var centerB = obj.centerB || [l + w * 0.5 + 0.6 * radius * Math.cos(Math.PI * (1 / 2 + 4 / 3)), t + h * 0.47 + 0.6 * radius * Math.sin(Math.PI * (1 / 2 + 4 / 3))];
var centerC = obj.centerC || [l + w * 0.5 + 0.6 * radius * Math.cos(Math.PI * (1 / 2)), t + h * 0.47 + 0.6 * radius * Math.sin(Math.PI * (1 / 2))];
var lineWidth = obj.lineWidth || 4;
var lineDash = obj.lineDash || [];
var strokeStyle = obj.strokeStyle || '#000';
var colorA = obj.colorA || strokeStyle;
var colorB = obj.colorB || strokeStyle;
var colorC = obj.colorC || strokeStyle;
var labelA = obj.labelA || ['<<fontSize:' + (w / 10) + '>>A'];
var labelB = obj.labelB || ['<<fontSize:' + (w / 10) + '>>B'];
var labelC = obj.labelC || ['<<fontSize:' + (w / 10) + '>>C'];
var fillStyle = obj.fillStyle || '#FCF';
var shade = obj.shade || [false, false, false, false, false, false, false, false];
ctx.save();
if (typeof ctx.setLineDash == 'undefined')
ctx.setLineDash = function () {};
ctx.setLineDash(lineDash);
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.strokeRect(l, t, w, h);
ctx.beginPath();
ctx.strokeStyle = colorA;
ctx.arc(centerA[0], centerA[1], radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = colorB;
ctx.arc(centerB[0], centerB[1], radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = colorC;
ctx.arc(centerC[0], centerC[1], radius, 0, 2 * Math.PI);
ctx.stroke();
var xy = [centerA[0] - (radius * 1.25) * Math.cos(Math.PI / 4), centerA[1] - (radius * 1.25) * Math.cos(Math.PI / 4)];
text({
ctx: ctx,
textArray: labelA,
left: xy[0] - 100,
width: 200,
top: xy[1] - 100,
height: 200,
textAlign: 'center',
vertAlign: 'middle',
padding: 0.1, /*box:{type:'tight',color:mainCanvasFillStyle,borderColor:mainCanvasFillStyle}*/
});
var xy = [centerB[0] + (radius * 1.25) * Math.cos(Math.PI / 4), centerB[1] - (radius * 1.25) * Math.cos(Math.PI / 4)];
text({
ctx: ctx,
textArray: labelB,
left: xy[0] - 100,
width: 200,
top: xy[1] - 100,
height: 200,
textAlign: 'center',
vertAlign: 'middle',
padding: 0.1, /*box:{type:'tight',color:mainCanvasFillStyle,borderColor:mainCanvasFillStyle}*/
});
var xy = [centerC[0] + (radius * 1.25) * Math.cos(Math.PI / 4), centerC[1] + (radius * 1.25) * Math.cos(Math.PI / 4)];
text({
ctx: ctx,
textArray: labelC,
left: xy[0] - 100,
width: 200,
top: xy[1] - 100,
height: 200,
textAlign: 'center',
vertAlign: 'middle',
padding: 0.1, /*box:{type:'tight',color:mainCanvasFillStyle,borderColor:mainCanvasFillStyle}*/
});
ctx.restore();
return {
ctx: ctx,
left: l,
top: t,
width: w,
height: h,
radius: radius,
centerA: centerA,
centerB: centerB,
centerC: centerC,
lineWidth: lineWidth,
lineDash: lineDash,
strokeStyle: strokeStyle,
labelA: labelA,
labelB: labelB,
labelC: labelC,
fillStyle: fillStyle,
shade: shade
};
}
function drawIsometricDotty(object) {
// required
var ctx = object.ctx || object.context;
// options
var left = object.left || object.l || 0;
var top = object.top || object.t || 100;
var width = object.width || object.w || 1200;
var height = object.height || object.h || 700;
var spacingFactor = object.spacingFactor || 15;
var color = object.color || '#AAA';
var origin = object.origin || [left + 0.5 * width, top + 0.5 * height];
var radius = object.radius || 5;
if (object.direction == 1) {
var baseVector = [
[-5 * (1 / 2),5 * (Math.sqrt(3) / 2)],
[-5 * (1 / 2),-5 * (Math.sqrt(3) / 2)],
[5, 0]
];
} else {
var baseVector = [
[5 * (Math.sqrt(3) / 2), -5 * (1 / 2)],
[-5 * (Math.sqrt(3) / 2), -5 * (1 / 2)],
[0, -5]
];
}
var points = [];
var x,
y;
for (var i = 0; i < 2; i++) {
x = origin[0];
y = origin[1];
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
x += spacingFactor * baseVector[i][0];
y += spacingFactor * baseVector[i][1];
}
}
for (var i = 0, lim = points.length; i < lim; i++) {
x = points[i][0];
y = points[i][1];
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
x += spacingFactor * baseVector[2][0];
y += spacingFactor * baseVector[2][1];
}
x = points[i][0];
y = points[i][1];
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
x -= spacingFactor * baseVector[2][0];
y -= spacingFactor * baseVector[2][1];
}
}
// remove out-of-range and duplicate points
for (var i = points.length - 1; i >= 0; i--) {
for (var j = i - 1; j >= 0; j--) {
if (arraysEqual(points[i], points[j]) == true) {
points.splice(i, 1);
break;
}
}
}
ctx.save();
ctx.fillStyle = color;
for (var i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i][0], points[i][1], radius, 0, 2 * Math.PI);
ctx.fill();
}
ctx.restore();
return points;
}
function drawSquareDotty(object) {
// required
var ctx = object.ctx || object.context;
// options
var left = object.left || object.l || 0;
var top = object.top || object.t || 100;
var width = object.width || object.w || 1200;
var height = object.height || object.h || 700;
var spacingFactor = object.spacingFactor || 80;
var color = object.color || '#AAA';
var origin = object.origin || [left + 0.5 * width, top + 0.5 * height];
var radius = object.radius || 5;
var points = [];
var x,
y;
x = origin[0];
y = origin[1];
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
x += spacingFactor;
}
x = origin[0] - spacingFactor;
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
x -= spacingFactor;
}
for (var i = 0, lim = points.length; i < lim; i++) {
x = points[i][0];
y = points[i][1] + spacingFactor;
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
y += spacingFactor;
}
y = points[i][1] - spacingFactor;
while (x >= left && y >= top && x <= left + width && y <= top + height) {
points.push([x, y]);
y -= spacingFactor;
}
}
// remove duplicate points
for (var i = points.length - 1; i >= 0; i--) {
for (var j = i - 1; j >= 0; j--) {
if (arraysEqual(points[i], points[j]) == true) {
points.splice(i, 1);
break;
}
}
}
ctx.save();
ctx.fillStyle = color;
for (var i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i][0], points[i][1], radius, 0, 2 * Math.PI);
ctx.fill();
}
ctx.restore();
return points;
}
function drawNumberLine(object) {
var context = object.context;
var left = object.left;
var top = object.top;
var width = object.width;
var height = object.height;
var min = object.min;
var max = object.max;
var majorStep = object.majorStep;
var minorStep = object.minorStep;
var minorYPos = [0.5, 0.75];
if (typeof object.minorYPos == 'object')
minorYPos = object.minorYPos;
var majorYPos = [0.25, 0.75];
if (typeof object.majorYPos == 'object')
majorYPos = object.majorYPos;
var minorWidth = object.minorWidth || 1.2;
var majorWidth = object.majorWidth || 2;
var minorColor = object.minorColor || '#CCC';
var majorColor = object.majorColor || '#000';
var lineWidth = object.lineWidth || 4;
var lineColor = object.lineColor || '#000';
var autoLabel = boolean(object.autoLabel, true);
var labels = object.labels;
var font = object.font || 'Arial';
var fontSize = object.fontSize || 24;
var textColor = object.textColor || majorColor;
var minorSpacing = (width * minorStep) / (max - min);
var majorSpacing = (width * majorStep) / (max - min);
var x0 = left - (min * width) / (max - min);
// draw minor markings
context.strokeStyle = minorColor;
context.lineWidth = minorWidth;
context.beginPath();
var startValue = Math.abs(min % minorStep);
var axisPos = left;
while (axisPos - (left + width) <= 0.1) {
context.moveTo(axisPos, top + minorYPos[0] * height);
context.lineTo(axisPos, top + minorYPos[1] * height);
axisPos += minorSpacing;
}
context.stroke();
// draw major markings
context.strokeStyle = majorColor;
context.lineWidth = majorWidth;
var startValue = Math.abs(min % majorStep);
var axisPos = left + startValue * majorSpacing;
var num = min + startValue;
var count = 0;
while (axisPos - (left + width) <= 0.1) {
context.moveTo(axisPos, top + majorYPos[0] * height);
context.lineTo(axisPos, top + majorYPos[1] * height);
if (autoLabel == true) {
text({
context: context,
left: axisPos - 100,
width: 200,
top: top + 0.75 * height,
textArray: ['<<align:center>><<font:' + font + '>><<fontSize:' + fontSize + '>><<color:' + textColor + '>>' + num]
})
} else if (typeof labels == 'object' && typeof labels[count] !== 'undefined') {
text({
context: context,
left: axisPos - 100,
width: 200,
top: top + 0.75 * height,
textArray: ['<<align:center>><<font:' + font + '>><<fontSize:' + fontSize + '>><<color:' + textColor + '>>' + labels[count]]
})
}
count++;
num += majorStep;
axisPos += majorSpacing;
}
context.stroke();
context.strokeStyle = lineColor;
context.lineWidth = lineWidth;
context.moveTo(left, top + 0.5 * height);
context.lineTo(left + width, top + 0.5 * height);
context.stroke();
}
function drawNumberLine2(obj) {
var ctx = obj.ctx || obj.context;
ctx.save();
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
var vertical = boolean(obj.vertical, false);
if (un(obj.rect))
obj.rect = [];
var left = obj.left || obj.rect[0];
var top = obj.top || obj.rect[1];
var width = obj.width || obj.rect[2];
var height = obj.height || obj.rect[3];
if (vertical == true) {
if (width > height) {
var temp = width;
width = height;
height = temp;
}
}
var min = Math.min(obj.min, obj.max);
var max = Math.max(obj.min, obj.max);
if (min >= max) {
console.log('Check numberline min & max.');
return;
}
var minorStep = obj.minorStep;
var majorStep = obj.majorStep;
var scaleOffset = obj.scaleOffset || 15;
var minorYPos = [0.5, 0.75];
if (typeof obj.minorYPos == 'object')
minorYPos = obj.minorYPos;
var majorYPos = [0.25, 0.75];
if (typeof obj.majorYPos == 'object')
majorYPos = obj.majorYPos;
var minorWidth = obj.minorWidth || 1.2;
var majorWidth = obj.majorWidth || 2;
var minorColor = obj.minorColor || '#CCC';
var majorColor = obj.majorColor || '#000';
var lineWidth = obj.lineWidth || 4;
var lineColor = obj.lineColor || '#000';
var backColor = obj.backColor || mainCanvasFillStyle;
var autoLabel = boolean(obj.autoLabel, true);
var labels = obj.labels;
var font = obj.font || 'Arial';
var fontSize = obj.fontSize || 24;
var textColor = obj.textColor || majorColor;
if (vertical == true) {
drawNumberlineVertical();
} else {
drawNumberlineHorizontal();
}
ctx.restore();
function drawNumberlineHorizontal() {
if (typeof obj.arrows == 'number') {
left += obj.arrows;
width -= 2 * obj.arrows;
}
var minorSpacing = (width * minorStep) / (max - min);
var majorSpacing = (width * majorStep) / (max - min);
var x0 = left - (min * width) / (max - min);
if (boolean(obj.showMinorPos, true) == true) {
ctx.strokeStyle = minorColor;
ctx.lineWidth = minorWidth;
ctx.beginPath();
var xAxisPoint = x0 + minorSpacing;
while (Math.round(xAxisPoint) <= Math.round(left + width)) {
if (Math.round(xAxisPoint) >= Math.round(left)) {
ctx.moveTo(xAxisPoint, top + minorYPos[0] * height);
ctx.lineTo(xAxisPoint, top + minorYPos[1] * height);
//console.log(xAxisPoint);
}
xAxisPoint += minorSpacing;
}
var xAxisPoint = x0 - minorSpacing;
while (Math.round(xAxisPoint) >= Math.round(left)) {
if (Math.round(xAxisPoint) <= Math.round(left + width)) {
ctx.moveTo(xAxisPoint, top + minorYPos[0] * height);
ctx.lineTo(xAxisPoint, top + minorYPos[1] * height);
//console.log(xAxisPoint);
}
xAxisPoint -= minorSpacing;
}
ctx.closePath();
ctx.stroke();
}
// draw major lines
ctx.strokeStyle = majorColor;
ctx.lineWidth = majorWidth;
ctx.beginPath();
var xAxisPoint = x0;
while (Math.round(xAxisPoint) <= Math.round(left + width)) {
if (Math.round(xAxisPoint) >= Math.round(left)) {
ctx.moveTo(xAxisPoint, top + majorYPos[0] * height);
ctx.lineTo(xAxisPoint, top + majorYPos[1] * height);
}
xAxisPoint += majorSpacing;
}
var xAxisPoint = x0 - majorSpacing;
while (Math.round(xAxisPoint) >= Math.round(left)) {
if (Math.round(xAxisPoint) <= Math.round(left + width)) {
ctx.moveTo(xAxisPoint, top + majorYPos[0] * height);
ctx.lineTo(xAxisPoint, top + majorYPos[1] * height);
}
xAxisPoint -= majorSpacing;
}
ctx.closePath();
ctx.stroke();
if (boolean(obj.showScales, true) == true) {
// draw axes numbers
ctx.font = fontSize + 'px Arial';
ctx.textAlign = "center";
ctx.textBaseline = "top";
var xAxisPoint = x0;
var major = 0;
var placeValue = Math.pow(10, Math.floor(Math.log(majorStep) / Math.log(10)));
while (roundToNearest(xAxisPoint, 0.001) <= roundToNearest(left + width, 0.001)) {
if (xAxisPoint >= left) {
var value = roundToNearest(major * majorStep, placeValue);
var axisValue = [String(value)];
var textWidth = ctx.measureText(String(axisValue)).width;
ctx.fillStyle = backColor;
ctx.fillRect(xAxisPoint - textWidth / 2, top + 0.5 * height + scaleOffset - 1, textWidth, fontSize * 1.1);
var labelText = drawMathsText(ctx, axisValue, fontSize, xAxisPoint, top + 0.5 * height + scaleOffset + 0.5 * fontSize, true, [], 'center', 'middle', majorColor);
}
major += 1;
xAxisPoint += majorSpacing;
}
var xAxisPoint = x0 - majorSpacing;
var major = -1;
while (roundToNearest(xAxisPoint, 0.001) >= roundToNearest(left, 0.001)) {
if (xAxisPoint < left + width) {
var value = roundToNearest(major * majorStep, placeValue);
var axisValue = [String(value)];
var textWidth = ctx.measureText(String(axisValue)).width;
ctx.fillStyle = backColor;
ctx.fillRect(xAxisPoint - textWidth / 2, top + 0.5 * height + scaleOffset - 1, textWidth, fontSize * 1.1);
var labelText = drawMathsText(ctx, axisValue, fontSize, xAxisPoint, top + 0.5 * height + scaleOffset + 0.5 * fontSize, true, [], 'center', 'middle', majorColor);
}
major -= 1;
xAxisPoint -= majorSpacing;
}
}
if (typeof obj.arrows == 'number') {
drawArrow({
ctx: ctx,
startX: left - obj.arrows,
startY: top + 0.5 * height,
finX: left + width + obj.arrows,
finY: top + 0.5 * height,
doubleEnded: true,
color: lineColor,
lineWidth: lineWidth,
fillArrow: true,
arrowLength: 12
});
} else {
// draw line
ctx.beginPath();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.moveTo(left, top + 0.5 * height);
ctx.lineTo(left + width, top + 0.5 * height);
ctx.closePath();
ctx.stroke();
}
}
function drawNumberlineVertical() {
if (typeof obj.arrows == 'number') {
top += obj.arrows;
height -= 2 * obj.arrows;
}
var minorSpacing = (height * minorStep) / (max - min);
var majorSpacing = (height * majorStep) / (max - min);
var y0 = top - (min * height) / (max - min);
if (boolean(obj.showMinorPos, true) == true) {
ctx.strokeStyle = minorColor;
ctx.lineWidth = minorWidth;
ctx.beginPath();
var yAxisPoint = y0 + minorSpacing;
while (Math.round(yAxisPoint) <= Math.round(top + height)) {
if (Math.round(yAxisPoint) >= Math.round(top)) {
ctx.moveTo(left + minorYPos[0] * width, yAxisPoint);
ctx.lineTo(left + minorYPos[1] * width, yAxisPoint);
}
yAxisPoint += minorSpacing;
}
var yAxisPoint = y0 - minorSpacing;
while (Math.round(yAxisPoint) >= Math.round(top)) {
if (Math.round(yAxisPoint) <= Math.round(top + height)) {
ctx.moveTo(left + minorYPos[0] * width, yAxisPoint);
ctx.lineTo(left + minorYPos[1] * width, yAxisPoint);
}
yAxisPoint -= minorSpacing;
}
ctx.closePath();
ctx.stroke();
}
// draw major lines
ctx.strokeStyle = majorColor;
ctx.lineWidth = majorWidth;
ctx.beginPath();
var yAxisPoint = y0;
while (Math.round(yAxisPoint) <= Math.round(top + height)) {
if (Math.round(yAxisPoint) >= Math.round(top)) {
ctx.moveTo(left + majorYPos[0] * width, yAxisPoint);
ctx.lineTo(left + majorYPos[1] * width, yAxisPoint);
}
yAxisPoint += majorSpacing;
}
var yAxisPoint = y0 - majorSpacing;
while (Math.round(yAxisPoint) >= Math.round(top)) {
if (Math.round(yAxisPoint) <= Math.round(top + height)) {
ctx.moveTo(left + majorYPos[0] * width, yAxisPoint);
ctx.lineTo(left + majorYPos[1] * width, yAxisPoint);
}
yAxisPoint -= majorSpacing;
}
ctx.closePath();
ctx.stroke();
if (boolean(obj.showScales, true) == true) {
ctx.font = fontSize + 'px Arial';
ctx.textBaseline = "middle";
ctx.textAlign = "right";
// positive y numbers
var yAxisPoint = y0;
var major = 0;
while (roundToNearest(yAxisPoint, 0.001) >= roundToNearest(top, 0.001)) {
if (yAxisPoint <= top + height) {
var axisValue = Number(roundToNearest(major * majorStep, 0.00001));
var textWidth = ctx.measureText(String(axisValue)).width
var labelText = drawMathsText(ctx, String(axisValue), fontSize, left + majorYPos[0] * width - 10, yAxisPoint - 2, true, [], 'right', 'middle', '#000');
}
major += 1;
yAxisPoint -= majorSpacing;
}
// negative y numbers
var yAxisPoint = y0 + majorSpacing;
var major = -1;
while (roundToNearest(yAxisPoint, 0.001) <= roundToNearest(top + height, 0.001)) {
if (yAxisPoint >= top) {
var axisValue = Number(roundToNearest(major * majorStep, 0.00001));
var textWidth = ctx.measureText(String(axisValue)).width
var labelText = drawMathsText(ctx, String(axisValue), fontSize, left + majorYPos[0] * width - 10, yAxisPoint - 2, true, [], 'right', 'middle', '#000');
}
major -= 1;
yAxisPoint += majorSpacing;
}
}
if (typeof obj.arrows == 'number') {
drawArrow({
ctx: ctx,
startX: left + 0.5 * width,
startY: top - obj.arrows,
finX: left + 0.5 * width,
finY: top + height + obj.arrows,
doubleEnded: true,
color: lineColor,
lineWidth: lineWidth,
fillArrow: true,
arrowLength: 12
});
} else {
// draw line
ctx.beginPath();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.moveTo(left + 0.5 * width, top);
ctx.lineTo(left + 0.5 * width, top + height);
ctx.closePath();
ctx.stroke();
}
}
}
function isPointBetweenAngles(center, p1, p2, p3, cw) {
var a1 = posToAngle(p1[0], p1[1], center[0], center[1]);
var a2 = posToAngle(p2[0], p2[1], center[0], center[1]);
var a3 = posToAngle(p3[0], p3[1], center[0], center[1]);
if (anglesInOrder(a1, a2, a3), cw) {
return false;
} else {
return true;
}
}
function anglesInOrder(a1, a2, a3, cw) { // test if three angles are in order
while (a1 < 0)
a1 += 2 * Math.PI;
while (a2 < 0)
a2 += 2 * Math.PI;
while (a3 < 0)
a3 += 2 * Math.PI;
while (a1 > 2 * Math.PI)
a1 -= 2 * Math.PI;
while (a2 > 2 * Math.PI)
a2 -= 2 * Math.PI;
while (a3 > 2 * Math.PI)
a3 -= 2 * Math.PI;
if (boolean(cw, true) == true) {
if ((a1 <= a2 && a2 <= a3) || //123
(a2 <= a3 && a3 <= a1) || //231
(a3 <= a1 && a1 <= a2)) { //312
return true;
} else {
return false;
}
} else {
if ((a3 <= a2 && a2 <= a1) || //321
(a2 <= a1 && a1 <= a3) || //213
(a1 <= a3 && a3 <= a2)) { //132
return true;
} else {
return false;
}
}
}
function roundedRect(context, left, top, width, height, roundingSize, lineThickness, lineColor, fillColor, dash) {
context.save();
if (lineThickness) {
context.lineWidth = lineThickness
};
if (lineColor) {
context.strokeStyle = lineColor
};
if (fillColor) {
context.fillStyle = fillColor
};
if (typeof dash == 'undefined')
dash = [];
if (typeof dash == 'object') {
if (!context.setLineDash) {
context.setLineDash = function () {}
}
context.setLineDash(dash);
}
context.beginPath();
context.moveTo(left + roundingSize, top);
context.lineTo(left + width - roundingSize, top);
context.arc(left + width - roundingSize, top + roundingSize, roundingSize, 1.5 * Math.PI, 2 * Math.PI);
context.lineTo(left + width, top + height - roundingSize);
context.arc(left + width - roundingSize, top + height - roundingSize, roundingSize, 0, 0.5 * Math.PI);
context.lineTo(left + roundingSize, top + height);
context.arc(left + roundingSize, top + height - roundingSize, roundingSize, 0.5 * Math.PI, Math.PI);
context.lineTo(left, top + roundingSize);
context.arc(left + roundingSize, top + roundingSize, roundingSize, Math.PI, 1.5 * Math.PI);
context.closePath();
if (typeof lineColor == 'string' && lineColor !== 'none')
context.stroke();
if (typeof fillColor == 'string' && fillColor !== 'none')
context.fill();
context.restore();
}
function roundedRect2(context, left, top, width, height, roundingSize, lineThickness, lineColor, fillColor, dash) {
context.save();
if (lineThickness)
context.lineWidth = lineThickness;
if (lineColor)
context.strokeStyle = lineColor;
if (fillColor)
context.fillStyle = fillColor;
if (typeof dash == 'undefined')
dash = [];
if (typeof dash == 'object') {
if (!context.setLineDash) {
context.setLineDash = function () {}
}
context.setLineDash(dash);
}
context.beginPath();
context.moveTo(left + roundingSize, top);
context.lineTo(left + width - roundingSize, top);
context.arc(left + width - roundingSize, top + roundingSize, roundingSize, 1.5 * Math.PI, 2 * Math.PI);
context.lineTo(left + width, top + height - roundingSize);
context.arc(left + width - roundingSize, top + height - roundingSize, roundingSize, 0, 0.5 * Math.PI);
context.lineTo(left + roundingSize, top + height);
context.arc(left + roundingSize, top + height - roundingSize, roundingSize, 0.5 * Math.PI, Math.PI);
context.lineTo(left, top + roundingSize);
context.arc(left + roundingSize, top + roundingSize, roundingSize, Math.PI, 1.5 * Math.PI);
context.closePath();
if (typeof fillColor == 'string' && fillColor !== 'none')
context.fill();
if (typeof lineColor == 'string' && lineColor !== 'none')
context.stroke();
context.restore();
}
function roundedRect3(context, left, top, width, height, roundingSize, lineThickness, lineColor, fillColor, dash) {
context.save();
if (lineThickness) {
context.lineWidth = lineThickness
};
if (lineColor) {
context.strokeStyle = lineColor
};
if (fillColor) {
context.fillStyle = fillColor
};
if (typeof dash == 'undefined')
dash = [];
if (typeof dash == 'object') {
if (!context.setLineDash) {
context.setLineDash = function () {}
}
context.setLineDash(dash);
}
context.beginPath();
context.moveTo(left + roundingSize[0], top);
context.lineTo(left + width - roundingSize[1], top);
context.arc(left + width - roundingSize[1], top + roundingSize[1], roundingSize[1], 1.5 * Math.PI, 2 * Math.PI);
context.lineTo(left + width, top + height - roundingSize[2]);
context.arc(left + width - roundingSize[2], top + height - roundingSize[2], roundingSize[2], 0, 0.5 * Math.PI);
context.lineTo(left + roundingSize[3], top + height);
context.arc(left + roundingSize[3], top + height - roundingSize[3], roundingSize[3], 0.5 * Math.PI, Math.PI);
context.lineTo(left, top + roundingSize[0]);
context.arc(left + roundingSize[0], top + roundingSize[0], roundingSize[0], Math.PI, 1.5 * Math.PI);
context.closePath();
if (typeof fillColor == 'string' && fillColor !== 'none') {
context.fill()
};
context.stroke();
context.restore();
}
function drawPath(object) {
var ctx = object.ctx;
var path = object.path;
ctx.save();
if (typeof object.lineColor !== 'undefined') {
ctx.strokeStyle = object.lineColor
};
if (typeof object.lineWidth !== 'undefined') {
ctx.lineWidth = object.lineWidth
};
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(path[0][0], path[0][1]);
for (var i = 1; i < path.length; i++) {
ctx.lineTo(path[i][0], path[i][1]);
}
if (typeof object.fillColor !== 'undefined' || object.closed == true) {
ctx.closePath()
};
if (typeof object.fillColor !== 'undefined') {
ctx.fillStyle = object.fillColor;
ctx.fill();
}
ctx.stroke();
ctx.restore();
}
function dashedLine(object) {
var context = object.context;
var startX = object.startX;
var startY = object.startY;
var finX = object.finX;
var finY = object.finY;
if (startX > finX) {
var x1 = startX;
var y1 = startY;
var x2 = finX;
var y2 = finY;
startX = x2;
startY = y2;
finX = x1;
finY = y1;
}
var dashSize = object.dashSize || 20;
var gapSize = object.gapSize || 10;
var color = object.color || '#000';
var lineWidth = object.lineWidth || 2;
var totalLength = Math.sqrt(Math.pow((finX - startX), 2) + Math.pow((finY - startY), 2));
var numOfIncs = totalLength / (dashSize + gapSize);
var dx = (finX - startX) / numOfIncs;
var dxDash = dx * dashSize / (dashSize + gapSize);
var dxGap = dx * gapSize / (dashSize + gapSize);
var dy = (finY - startY) / numOfIncs;
var dyDash = dy * dashSize / (dashSize + gapSize);
var dyGap = dy * gapSize / (dashSize + gapSize);
var xPos = startX;
var yPos = startY;
var incCount = 0;
context.save();
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.beginPath();
do {
context.moveTo(xPos, yPos);
xPos += dxDash;
yPos += dyDash;
context.lineTo(xPos, yPos);
xPos += dxGap;
yPos += dyGap;
incCount++;
} while (incCount <= numOfIncs - 1)
if ((startX < finX && xPos < finX) || (startX > finX && xPos > finX) || (startY < finY && yPos < finY) || (startY > finY && yPos > finY)) {
context.moveTo(xPos, yPos);
context.lineTo(finX, finY);
}
context.closePath();
context.stroke();
context.restore();
}
function drawParallelArrow(object) {
// required
var context = object.context || object.ctx;
var startX = object.startX;
var startY = object.startY;
var finX = object.finX;
var finY = object.finY;
// optional
var numOfArrows = object.numOfArrows || 1;
var arrowLength = object.arrowLength || 25;
var arrowAngle = object.arrowAngle || 0.5;
var color = object.color || '#000';
var lineWidth = object.lineWidth || 2;
var fillArrow = boolean(object.fillArrow, false);
context.save();
if (numOfArrows == 1) {
var fracPos = 0.5 + 0.5 * arrowLength * Math.cos(arrowAngle) / Math.sqrt(Math.pow((finX - startX), 2) + Math.pow((finY - startY), 2))
drawArrow({
context: context,
startX: startX,
startY: startY,
finX: startX + fracPos * (finX - startX),
finY: startY + fracPos * (finY - startY),
arrowLength: arrowLength,
angleBetweenLinesRads: arrowAngle,
color: color,
lineWidth: 0.1,
arrowLineWidth: lineWidth
});
} else if (numOfArrows == 2) {
var fracPos1 = 0.5 + 0.75 * arrowLength * (Math.cos(arrowAngle) + 0.5) / Math.sqrt(Math.pow((finX - startX), 2) + Math.pow((finY - startY), 2))
var fracPos2 = fracPos1 - 1 * arrowLength / Math.sqrt(Math.pow((finX - startX), 2) + Math.pow((finY - startY), 2))
drawArrow({
context: context,
startX: startX,
startY: startY,
finX: startX + fracPos1 * (finX - startX),
finY: startY + fracPos1 * (finY - startY),
arrowLength: arrowLength,
angleBetweenLinesRads: arrowAngle,
color: color,
lineWidth: 0.1,
showLine: false,
arrowLineWidth: lineWidth
});
drawArrow({
context: context,
startX: startX,
startY: startY,
finX: startX + fracPos2 * (finX - startX),
finY: startY + fracPos2 * (finY - startY),
arrowLength: arrowLength,
angleBetweenLinesRads: arrowAngle,
color: color,
lineWidth: 0.1,
showLine: false,
arrowLineWidth: lineWidth
});
}
context.restore();
}
function labelLine(posA, posB, obj) {
var ctx = obj.ctx;
if (typeof hiddenCanvas == 'undefined')
hiddenCanvas = newcanvas({
vis: false
});
obj.ctx = hiddenCanvas.ctx;
obj.measureOnly = true;
var textDims = text(obj);
var w = textDims.tightRect[2];
var h = textDims.tightRect[3];
if (typeof obj.box == 'undefined' || obj.box.type !== 'tight')
w += 20;
var x1 = posA[0],
y1 = posA[1],
x2 = posB[0],
y2 = posB[1],
x4,
y4;
var x3 = (x1 + x2) / 2;
var y3 = (y1 + y2) / 2;
if (y1 == y2) {
x4 = x3 - w / 2;
if (x1 < x2) {
y4 = y3;
} else {
y4 = y3 - h;
}
} else if (x1 == x2) {
y4 = y3 - h / 2;
if (y1 < y2) {
x4 = x3 - w;
} else {
x4 = x3;
}
} else if (x1 < x2) {
if (y1 < y2) {
x4 = x3 - w;
y4 = y3;
} else {
x4 = x3;
y4 = y3;
}
} else if (x3 > x2) {
if (y3 < y2) {
x4 = x3 - w;
y4 = y3 - h;
} else {
x4 = x3;
y4 = y3 - h;
}
}
obj.ctx = ctx;
obj.measureOnly = false;
obj.left = x4 - 10;
obj.top = y4 - 10;
obj.width = w + 20;
obj.height = h + 20;
obj.align = 'center';
obj.vertAlign = 'middle';
text(obj);
}
function drawDash(context, x1, y1, x2, y2, length) {
if (typeof length !== 'number')
length = 10;
var grad = - (x2 - x1) / (y2 - y1);
var midX = (x1 + x2) / 2;
var midY = (y1 + y2) / 2;
var end1X = midX - length * Math.cos(Math.atan(grad));
var end1Y = midY - length * Math.sin(Math.atan(grad));
var end2X = midX + length * Math.cos(Math.atan(grad));
var end2Y = midY + length * Math.sin(Math.atan(grad));
context.beginPath();
context.moveTo(end1X, end1Y);
context.lineTo(end2X, end2Y);
context.stroke();
}
function drawDoubleDash(context, x1, y1, x2, y2, length, separation) {
if (typeof length !== 'number')
length = 10;
var sep = separation / 2 || context.lineWidth + 2 || 4;
var grad = - (x2 - x1) / (y2 - y1);
// [unitX,unitY] is a unit vector in the direction of the line
var unitX = (x2 - x1) / Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
var unitY = (y2 - y1) / Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
var midX = (x1 + x2) / 2;
var midY = (y1 + y2) / 2;
var dashX1 = midX - sep * unitX;
var dashX2 = midX + sep * unitX;
var dashY1 = midY - sep * unitY;
var dashY2 = midY + sep * unitY;
var end1X = dashX1 - length * Math.cos(Math.atan(grad));
var end1Y = dashY1 - length * Math.sin(Math.atan(grad));
var end2X = dashX1 + length * Math.cos(Math.atan(grad));
var end2Y = dashY1 + length * Math.sin(Math.atan(grad));
var end3X = dashX2 - length * Math.cos(Math.atan(grad));
var end3Y = dashY2 - length * Math.sin(Math.atan(grad));
var end4X = dashX2 + length * Math.cos(Math.atan(grad));
var end4Y = dashY2 + length * Math.sin(Math.atan(grad));
context.beginPath();
context.moveTo(end1X, end1Y);
context.lineTo(end2X, end2Y);
context.moveTo(end3X, end3Y);
context.lineTo(end4X, end4Y);
context.stroke();
}
function drawPrintIcon(ctx,rect,backColor) {
var w = rect[2];
ctx.save();
ctx.translate(rect[0],rect[1]);
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#000';
ctx.fillStyle = '#000';
ctx.lineWidth = 0.1*w;
roundedRect(ctx,0,0.3*w,w,0.45*w,0.1*w,0.1*w,'#000','#000');
ctx.beginPath();
ctx.fillStyle = backColor;
ctx.fillRect(0.22*w,0.65*w,0.56*w,0.15*w);
roundedRect(ctx,0.22*w,0,0.56*w,w,0.1*w,0.1*w,'#000');
roundedRect(ctx,0.35*w,0.70*w,0.3*w,0.05*w,0,0.01*w,'#000','#000');
roundedRect(ctx,0.35*w,0.82*w,0.2*w,0.05*w,0,0.01*w,'#000','#000');
ctx.beginPath();
ctx.fillStyle = backColor;
ctx.arc(0.86*w,0.4*w,0.085*w,0,2*Math.PI);
ctx.fill();
ctx.translate(-rect[0],-rect[1]);
ctx.restore();
}
CanvasRenderingContext2D.prototype.setStroke = function (obj) {
var lineWidth = obj.lineWidth || obj.width || obj.w || obj.thickness || this.lineWidth;
var strokeStyle = obj.color || obj.strokeStyle || obj.style || this.strokeStyle;
var dash = obj.dash || obj.lineDash || this.getLineDash();
var lineCap = obj.lineCap || obj.cap || 'round';
var lineJoin = obj.lineJoin || obj.join || obj.cap || 'round';
this.lineWidth = lineWidth;
this.strokeStyle = strokeStyle;
this.setLineDash(dash);
this.lineCap = lineCap;
this.lineJoin = lineJoin;
}
CanvasRenderingContext2D.prototype.setFill = function (obj) {
var color = obj.color || this.fillStyle;
this.fillStyle = obj.color;
}
CanvasRenderingContext2D.prototype.path = function (pathArray, close, obj) {
if (typeof obj == 'undefined')
obj = {};
this.beginPath();
this.moveTo(pathArray[0][0], pathArray[0][1]);
for (var i = 1; i < pathArray.length; i++) {
this.lineTo(pathArray[i][0], pathArray[i][1]);
}
if (boolean(close, false) === true) {
this.lineTo(pathArray[0][0], pathArray[0][1]);
}
if (typeof obj.fill !== 'undefined') {
this.setFill(obj.fill);
this.fill();
}
if (typeof obj.lineDec !== 'undefined') {}
if (typeof obj.intAngles !== 'undefined') {
if (typeof obj.intAngles.show == 'undefined') {
obj.intAngles.show = [];
for (var i = 0; i < pathArray.length; i++)
obj.intAngles.show.push(true);
}
var angle = {
ctx: this
};
angle.radius = obj.intAngles.radius || obj.intAngles.r || undefined;
angle.squareForRight = boolean(obj.intAngles.squareForRight, true);
angle.labelIfRight = boolean(obj.intAngles.labelIfRight, false);
angle.drawLines = boolean(obj.intAngles.drawLines, false);
angle.lineWidth = obj.intAngles.lineWidth || obj.intAngles.width || obj.intAngles.w || undefined;
angle.lineColor = obj.intAngles.lineColor || obj.intAngles.color || undefined;
angle.drawCurve = boolean(obj.intAngles.drawCurve, true);
angle.curveWidth = obj.intAngles.curveWidth || angle.lineWidth;
angle.curveColor = obj.intAngles.curveColor || angle.lineColor;
if (typeof obj.intAngles.fill == 'string') {
angle.fill = true;
angle.fillColor = obj.intAngles.fill;
} else {
angle.fill = boolean(obj.intAngles.fill, false);
angle.fillColor = obj.intAngles.fillColor || undefined;
}
angle.label = obj.intAngles.label || obj.intAngles.text || undefined;
angle.labelFont = obj.intAngles.labelFont || obj.intAngles.font || undefined;
angle.labelFontSize = obj.intAngles.labelFontSize || obj.intAngles.fontSize || undefined;
angle.labelColor = obj.intAngles.labelColor || angle.lineColor || undefined;
angle.labelRadius = obj.intAngles.labelRadius || undefined;
angle.labelMeasure = boolean(obj.intAngles.labelMeasure, false);
angle.measureRoundTo = obj.intAngles.measureRoundTo || obj.intAngles.roundTo || undefined;
angle.angleType = obj.intAngles.angleType || undefined;
for (var i = 0; i < pathArray.length; i++) {
if (obj.intAngles.show[i] === true) {
if (i === 0) {
angle.a = pathArray[pathArray.length - 1]
} else {
angle.a = pathArray[i - 1]
};
angle.b = pathArray[i];
if (i === pathArray.length - 1) {
angle.c = pathArray[0]
} else {
angle.c = pathArray[i + 1]
};
if (typeof obj.intAngles.r == 'object')
angle.radius = obj.intAngles.r[i];
if (typeof obj.intAngles.radius == 'object')
angle.radius = obj.intAngles.radius[i];
if (typeof obj.intAngles.squareForRight == 'object')
angle.squareForRight = boolean(obj.intAngles.squareForRight[i], true);
if (typeof obj.intAngles.labelIfRight == 'object')
angle.labelIfRight = boolean(obj.intAngles.labelIfRight[i], false);
if (typeof obj.intAngles.drawLines == 'object')
angle.drawLines = boolean(obj.intAngles.drawLines[i], false);
if (typeof obj.intAngles.lineWidth == 'object')
angle.lineWidth = obj.intAngles.lineWidth[i];
if (typeof obj.intAngles.width == 'object')
angle.lineWidth = obj.intAngles.width[i];
if (typeof obj.intAngles.w == 'object')
angle.lineWidth = obj.intAngles.w[i];
if (typeof obj.intAngles.lineColor == 'object')
angle.lineColor = obj.intAngles.lineColor[i];
if (typeof obj.intAngles.color == 'object')
angle.lineWidth = obj.intAngles.color[i];
if (typeof obj.intAngles.drawCurve == 'object')
angle.drawCurve = boolean(obj.intAngles.drawCurve[i], true);
if (typeof obj.intAngles.lineWidth == 'object')
angle.curveWidth = obj.intAngles.lineWidth[i];
if (typeof obj.intAngles.curveWidth == 'object')
angle.curveWidth = obj.intAngles.curveWidth[i];
if (typeof obj.intAngles.curveColor == 'object')
angle.curveColor = obj.intAngles.curveColor[i];
if (typeof obj.intAngles.fill == 'object') {
if (typeof obj.intAngles.fill[i] == 'string') {
angle.fill = true;
angle.fillColor = obj.intAngles.fill[i];
} else {
angle.fill = boolean(obj.intAngles.fill[i], false);
if (typeof obj.intAngles.fillColor == 'object')
angle.fillColor = obj.intAngles.fillColor[i];
}
}
if (typeof obj.intAngles.label == 'object')
angle.label = obj.intAngles.label[i];
if (typeof obj.intAngles.text == 'object')
angle.label = obj.intAngles.text[i];
if (typeof obj.intAngles.labelFont == 'object')
angle.labelFont = obj.intAngles.curveColor[i];
if (typeof obj.intAngles.font == 'object')
angle.labelFont = obj.intAngles.font[i];
if (typeof obj.intAngles.labelFontSize == 'object')
angle.labelFontSize = obj.intAngles.labelFontSize[i];
if (typeof obj.intAngles.fontSize == 'object')
angle.labelFontSize = obj.intAngles.fontSize[i];
if (typeof obj.intAngles.lineColor == 'object')
angle.labelColor = obj.intAngles.lineColor[i];
if (typeof obj.intAngles.labelColor == 'object')
angle.labelColor = obj.intAngles.labelColor[i];
if (typeof obj.intAngles.labelRadius == 'object')
angle.labelRadius = obj.intAngles.labelRadius[i];
if (typeof obj.intAngles.labelMeasure == 'object')
angle.labelMeasure = boolean(obj.intAngles.labelMeasure[i], false);
if (typeof obj.intAngles.measureRoundTo == 'object')
angle.measureRoundTo = obj.intAngles.measureRoundTo[i];
if (typeof obj.intAngles.roundTo == 'object')
angle.measureRoundTo = obj.intAngles.roundTo[i];
drawAngle(angle);
}
}
}
if (typeof obj.vertexLabels !== 'undefined') {
if (typeof obj.vertexLabels.show == 'undefined') {
obj.vertexLabels.show = [];
for (var i = 0; i < pathArray.length; i++)
obj.vertexLabels.show.push(true);
}
var angle = {
ctx: this
};
angle.labelIfRight = true;
angle.drawLines = false;
angle.drawCurve = false;
angle.fill = false;
angle.label = obj.vertexLabels.label || obj.vertexLabels.text || undefined;
angle.labelFont = obj.vertexLabels.labelFont || obj.vertexLabels.font || undefined;
angle.labelFontSize = obj.vertexLabels.labelFontSize || obj.vertexLabels.fontSize || undefined;
angle.labelColor = obj.vertexLabels.labelColor || angle.lineColor || undefined;
angle.labelRadius = obj.vertexLabels.labelRadius || obj.vertexLabels.radius || obj.vertexLabels.r || undefined;
angle.labelMeasure = false;
for (var i = 0; i < pathArray.length; i++) {
if (obj.vertexLabels.show[i] === true) {
if (i === 0) {
angle.c = pathArray[pathArray.length - 1]
} else {
angle.c = pathArray[i - 1]
};
angle.b = pathArray[i];
if (i === pathArray.length - 1) {
angle.a = pathArray[0]
} else {
angle.a = pathArray[i + 1]
};
if (typeof obj.vertexLabels.label == 'object')
angle.label = obj.vertexLabels.label[i];
if (typeof obj.vertexLabels.text == 'object')
angle.label = obj.vertexLabels.text[i];
if (typeof obj.vertexLabels.labelFont == 'object')
angle.labelFont = obj.vertexLabels.curveColor[i];
if (typeof obj.vertexLabels.font == 'object')
angle.labelFont = obj.vertexLabels.font[i];
if (typeof obj.vertexLabels.labelFontSize == 'object')
angle.labelFontSize = obj.vertexLabels.labelFontSize[i];
if (typeof obj.vertexLabels.fontSize == 'object')
angle.labelFontSize = obj.vertexLabels.fontSize[i];
if (typeof obj.vertexLabels.lineColor == 'object')
angle.labelColor = obj.vertexLabels.lineColor[i];
if (typeof obj.vertexLabels.labelColor == 'object')
angle.labelColor = obj.vertexLabels.labelColor[i];
if (typeof obj.vertexLabels.radius == 'object')
angle.labelRadius = obj.vertexLabels.radius[i];
if (typeof obj.vertexLabels.r == 'object')
angle.labelRadius = obj.vertexLabels.r[i];
if (typeof obj.vertexLabels.labelRadius == 'object')
angle.labelRadius = obj.vertexLabels.labelRadius[i];
drawAngle(angle);
}
}
}
if (typeof obj.edgeLabels !== 'undefined') {
if (typeof obj.edgeLabels.show == 'undefined') {
obj.edgeLabels.show = [];
for (var i = 0; i < pathArray.length; i++)
obj.edgeLabels.show.push(true);
}
var label = {
ctx: this
};
label.font = obj.edgeLabels.font || undefined;
label.fontSize = obj.edgeLabels.fontSize || undefined;
label.width = 1200;
for (var i = 0; i < pathArray.length; i++) {
if (obj.edgeLabels.show[i] === true) {
var a = pathArray[i];
var b = pathArray[(i + 1) % pathArray.length];
label.textArray = obj.edgeLabels.text[i];
labelLine(a, b, label);
}
}
}
if (typeof obj.stroke !== 'undefined') {
this.beginPath();
this.moveTo(pathArray[0][0], pathArray[0][1]);
for (var i = 1; i < pathArray.length; i++) {
this.lineTo(pathArray[i][0], pathArray[i][1]);
}
if (boolean(close, false) === true) {
this.lineTo(pathArray[0][0], pathArray[0][1]);
}
this.setStroke(obj.stroke);
this.stroke();
}
}
CanvasRenderingContext2D.prototype.rect2 = function (obj) {
var obj = clone(obj);
if (un(obj.sf))
obj.sf = 1;
this.save();
var line = true;
this.lineWidth = def([obj.lineWidth, obj.thickness, this.lineWidth]) * obj.sf;
this.strokeStyle = def([obj.lineColor, obj.color, obj.strokeStyle, this.strokeStyle]);
if (obj.lineColor == 'none' || obj.color == 'none' || obj.strokeStyle == 'none')
line = false;
var fill = false;
if (!un(obj.fillColor) && obj.fillColor !== 'none') {
fill = true;
this.fillStyle = obj.fillColor;
} else if (!un(obj.fillStyle) && obj.fillStyle !== 'none') {
fill = true;
this.fillStyle = obj.fillStyle;
}
var dash = enlargeDash(def([obj.dash, this.getLineDash(), []]), this.sf);
if (!this.setLineDash) {
this.setLineDash = function () {}
}
this.setLineDash(dash);
if (!un(obj.rect)) {
obj.left = obj.rect[0];
obj.top = obj.rect[1];
obj.width = obj.rect[2];
obj.height = obj.rect[3];
}
var left = (obj.left || obj.l) * obj.sf;
var top = (obj.top || obj.t) * obj.sf;
var width = (obj.width || obj.w) * obj.sf;
var height = (obj.height || obj.h) * obj.sf;
this.beginPath();
if (!un(obj.radius)) {
var radius = obj.radius * obj.sf;
this.moveTo(left + radius, top);
this.lineTo(left + width - radius, top);
this.arc(left + width - radius, top + radius, radius, 1.5 * Math.PI, 2 * Math.PI);
this.lineTo(left + width, top + height - radius);
this.arc(left + width - radius, top + height - radius, radius, 0, 0.5 * Math.PI);
this.lineTo(left + radius, top + height);
this.arc(left + radius, top + height - radius, radius, 0.5 * Math.PI, Math.PI);
this.lineTo(left, top + radius);
this.arc(left + radius, top + radius, radius, Math.PI, 1.5 * Math.PI);
this.closePath();
if (fill == true)
this.fill();
if (line == true)
this.stroke();
} else {
if (fill == true)
this.fillRect(left, top, width, height);
if (line == true)
this.strokeRect(left, top, width, height);
}
this.restore();
}
CanvasRenderingContext2D.prototype.clear = function () {
this.clearRect(0, 0, this.data[102], this.data[103]);
}
HTMLCanvasElement.prototype.setLeft = function (left) {
this.data[100] = left;
resizeCanvas2(this, this.data[100], this.data[101]);
}
HTMLCanvasElement.prototype.setTop = function (top) {
this.data[101] = top;
resizeCanvas2(this, this.data[100], this.data[101]);
}
HTMLCanvasElement.prototype.setPos = function (left, top) {
this.data[100] = left;
this.data[101] = top;
resizeCanvas2(this, this.data[100], this.data[101]);
}
HTMLCanvasElement.prototype.setWidth = function (width) {
this.data[102] = width;
resizeCanvas(this, this.data[100], this.data[101], this.data[102], this.data[103]);
}
HTMLCanvasElement.prototype.setHeight = function (height) {
this.data[103] = height;
resizeCanvas(this, this.data[100], this.data[101], this.data[102], this.data[103]);
}
HTMLCanvasElement.prototype.setDims = function (width, height) {
this.data[102] = width;
this.data[103] = height;
resizeCanvas(this, this.data[100], this.data[101], this.data[102], this.data[103]);
}
HTMLCanvasElement.prototype.setVis = function (vis) {
if (typeof vis == 'undefined') {
this.data[104] = !this.data[104];
} else {
this.data[104] = vis;
}
if (this.data[104] == true) {
showObj(this);
} else {
hideObj(this);
}
}
HTMLCanvasElement.prototype.setPE = function (point) {
if (typeof point == 'undefined') {
this.data[106] = !this.data[106];
} else {
this.data[106] = point;
}
if (this.data[106] == true) {
this.style.pointerEvents = 'auto';
} else {
this.style.pointerEvents = 'none';
}
}
HTMLCanvasElement.prototype.setZ = function (z) {
this.data[107] = z;
this.style.zIndex = z;
}
HTMLCanvasElement.prototype.setCursor = function (cursor) {
this.style.cursor = cursor || 'pointer';
}
HTMLCanvasElement.prototype.setOpacity = function (opacity) {
this.style.opacity = opacity;
}
function playButton(left, top, width, func, options) {
//visible,zIndex,fillColor,lineColor,lineWidth,radiusx
if (typeof options == 'undefined')
var options = {};
var zIndex = options.zIndex || 2;
var button = createCanvas(left, top, width, width, boolean(options.visible, true), false, true, zIndex);
button.lineColor = options.lineColor || '#000';
button.lineWidth = options.lineWidth || 4;
button.fillColor = options.fillColor || '#3FF';
button.radius = options.radius || 8;
button.direction = 'right';
if (options.dir == 'left')
button.direction = 'left';
button.width = width;
button.left = left;
button.top = top;
button.draw = function () {
var ctx = this.ctx;
roundedRect2(ctx, this.lineWidth / 2, this.lineWidth / 2, this.width - this.lineWidth, this.width - this.lineWidth, this.radius, this.lineWidth, this.lineColor, this.fillColor);
ctx.fillStyle = this.lineColor;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
if (this.direction == 'right') {
ctx.moveTo(this.width * 16 / 50, this.width * 14 / 50);
ctx.lineTo(this.width * 34 / 50, this.width * 25 / 50);
ctx.lineTo(this.width * 16 / 50, this.width * 36 / 50);
ctx.lineTo(this.width * 16 / 50, this.width * 14 / 50);
} else {
ctx.moveTo(this.width * 34 / 50, this.width * 14 / 50);
ctx.lineTo(this.width * 16 / 50, this.width * 25 / 50);
ctx.lineTo(this.width * 34 / 50, this.width * 36 / 50);
ctx.lineTo(this.width * 34 / 50, this.width * 14 / 50);
}
ctx.fill();
}
button.draw();
if (typeof func !== 'undefined') {
addListener(button, func);
}
return button;
}
function drawCalcAllowedButton(ctx, l, t, size, allowed, backColor) {
var w = size || 20;
var h = size || 20;
var color = backColor || '#FFC';
ctx.save();
ctx.strokeStyle = '#000';
ctx.fillStyle = '#000';
roundedRect(ctx, l, t, w, h, 2, 3, '#000', color);
roundedRect(ctx, l + 0.3 * w, t + 0.2 * h, w * 0.4, h * 0.6, 1, 3, '#000', '#000');
roundedRect(ctx, l + 0.35 * w, t + 0.25 * h, w * 0.3, h * 0.15, 0.01, 3, '#000', color);
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
roundedRect(ctx, l + 0.35 * w + 0.12 * j * w, t + 0.45 * h + 0.12 * i * h, w * 0.08, h * 0.08, 0.01, 3, '#000', color);
}
}
if (boolean(allowed, true) == false) {
ctx.lineWidth = 0.1 * w;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.strokeStyle = '#F00';
ctx.moveTo(l + 0.85 * w, t + 0.15 * h);
ctx.lineTo(l + 0.15 * w, t + 0.85 * h);
ctx.stroke();
}
ctx.restore();
}
function drawCalcAllowedButton2(ctx, l, t, size, allowed, backColor, calcColor) {
var w = size || 20;
var h = size || 20;
var color = backColor || '#FFC';
var color2 = calcColor || '#333';
ctx.save();
ctx.strokeStyle = color2;
ctx.fillStyle = color2;
roundedRect(ctx, l, t, w, h, 2, 3, '#000', color);
roundedRect(ctx, l + 0.3 * w, t + 0.2 * h, w * 0.4, h * 0.6, 1, 3, color2, color2);
roundedRect(ctx, l + 0.35 * w, t + 0.25 * h, w * 0.3, h * 0.15, 0.01, 3, color2, color);
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
roundedRect(ctx, l + 0.35 * w + 0.12 * j * w, t + 0.45 * h + 0.12 * i * h, w * 0.08, h * 0.08, 0.01, 3, color2, color);
}
}
if (boolean(allowed, true) == false) {
ctx.lineWidth = 0.08 * w;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.strokeStyle = '#900';
ctx.arc(l + 0.5 * w, t + 0.5 * h, 0.45 * w, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 0.12 * w;
ctx.moveTo(l + 0.5 * w + (1 / Math.sqrt(2)) * 0.42 * w, t + 0.5 * h - (1 / Math.sqrt(2)) * 0.42 * h);
ctx.lineTo(l + 0.5 * w - (1 / Math.sqrt(2)) * 0.42 * w, t + 0.5 * h + (1 / Math.sqrt(2)) * 0.42 * h);
//ctx.moveTo(l+0.15*w,t+0.15*h);
//ctx.lineTo(l+0.85*w,t+0.85*h);
ctx.stroke();
}
ctx.restore();
}
function drawRefreshButton(ctx, l, t, size, backColor) {
var w = size || 20;
var h = size || 20;
var color = backColor || '#FFC';
ctx.save();
ctx.strokeStyle = '#000';
ctx.fillStyle = '#000';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
roundedRect(ctx, l, t, w, h, 8, 8, '#000', color);
ctx.lineWidth = 0.08 * w;
ctx.beginPath();
ctx.arc(l + 0.5 * w, t + 0.5 * h, 0.22 * w, -1.8 * Math.PI, -0.2 * Math.PI);
ctx.stroke();
ctx.beginPath();
var l2 = l + 0.77 * w;
var t2 = t + 0.47 * h;
var arrowLength = 0.28 * w;
ctx.moveTo(l2, t2);
ctx.lineTo(l2 + arrowLength * Math.sin(1.06 * Math.PI), t2 + arrowLength * Math.cos(1.06 * Math.PI));
ctx.lineTo(l2 + arrowLength * Math.cos(1.03 * Math.PI), t2 - arrowLength * Math.sin(1.03 * Math.PI));
ctx.lineTo(l2, t2);
ctx.fill();
ctx.restore();
}
function drawRangeLabel(ctx, l, t, w, h, direction) {
var dir = 'bottom'; // default;
if (typeof direction == 'string')
dir = direction;
switch (dir) {
case 'bottom':
var p1 = [l, t];
var p2 = [l + w / 2, t + h];
var p3 = [l + w, t];
var c1 = [l + w * (1 / 12), t + h * 1.3];
var c2 = [l + w * (13 / 40), t + h * (-0.7)];
var c3 = [l + w * (27 / 40), t + h * (-0.7)];
var c4 = [l + w * (11 / 12), t + h * 1.3];
break;
case 'top':
var p1 = [l, t + h];
var p2 = [l + w / 2, t];
var p3 = [l + w, t + h];
var c1 = [l + w * (1 / 12), t + h - h * 1.3];
var c2 = [l + w * (13 / 40), t + h - h * (-0.7)];
var c3 = [l + w * (27 / 40), t + h - h * (-0.7)];
var c4 = [l + w * (11 / 12), t + h - h * 1.3];
break;
case 'right':
var p1 = [l, t];
var p2 = [l + w, t + h / 2];
var p3 = [l, t + h];
var c1 = [l + w * 1.3, t + h * (1 / 12)];
var c2 = [l + w * (-0.7), t + h * (13 / 40)];
var c3 = [l + w * (-0.7), t + h * (27 / 40)];
var c4 = [l + w * 1.3, t + h * (11 / 12)];
break;
case 'left':
var p1 = [l + w, t];
var p2 = [l, t + h / 2];
var p3 = [l + w, t + h];
var c1 = [l + w - w * 1.3, t + h * (1 / 12)];
var c2 = [l + w - w * (-0.7), t + h * (13 / 40)];
var c3 = [l + w - w * (-0.7), t + h * (27 / 40)];
var c4 = [l + w - w * 1.3, t + h * (11 / 12)];
break;
}
ctx.beginPath();
ctx.moveTo(p1[0], p1[1]);
ctx.bezierCurveTo(c1[0], c1[1], c2[0], c2[1], p2[0], p2[1]);
ctx.bezierCurveTo(c3[0], c3[1], c4[0], c4[1], p3[0], p3[1]);
ctx.stroke();
}
var JSONfn = {};
(function () {
JSONfn.stringify = function (obj) {
return JSON.stringify(obj, function (key, value) {
return (typeof value === 'function') ? value.toString() : value;
});
}
JSONfn.parse = function (str) {
return JSON.parse(str, function (key, value) {
if (typeof value != 'string')
return value;
return (value.substring(0, 8) == 'function') ? eval('(' + value + ')') : value;
});
}
}
());
function replaceAll(string, find, replace) {
return string.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function removeSpaces(string, opt_mathsInput) {
string = string.replace(/\s/g, "");
if (typeof opt_mathsInput !== 'undefined')
setMathsInputText(opt_mathsInput, string, 0);
return string;
}
function stringTo2dArray(string, elemType) {
if (!elemType)
elemType = 'string';
var start = string.indexOf("[");
// replace commas with semi-colons inside array (level 1)
var bracket = 0;
for (j = start; j < string.length; j++) {
if (string.charAt(j) == "[")
bracket++;
if (string.charAt(j) == "]")
bracket--;
if (string.charAt(j) == "," && bracket == 1) {
string = string.slice(0, j) + ";" + string.slice(j + 1);
}
}
var bracket = 0;
var fin;
for (j = start; j < string.length; j++) {
if (string.charAt(j) == "[")
bracket++;
if (string.charAt(j) == "]")
bracket--;
if (bracket == 0) {
fin = j + 1;
break;
}
}
string = string.slice(start + 1, fin - 1);
var array = string.split(";");
for (j = 0; j < array.length; j++) {
var str = array[j];
array[j] = array[j].slice(1, -1);
array[j] = array[j].split(',');
for (k = 0; k < array[j].length; k++) {
if (elemType == 'number')
array[j][k] = Number(array[j][k]);
}
}
return array;
}
function textArrayReplace(textArray, findStr, replaceStr) {
if (typeof replaceStr == 'object') {
return textArrayReplace2(textArray, findStr, replaceStr);
}
replaceStr = String(replaceStr);
for (var i = 0; i < textArray.length; i++) {
if (typeof textArray[i] == 'string') {
textArray[i] = textStringReplace(textArray[i], findStr, replaceStr);
} else if (typeof textArray[i] == 'object') {
textArray[i] = textArrayReplace(textArray[i], findStr, replaceStr);
}
}
return textArray;
}
function textStringReplace(string, findStr, replaceStr) {
var re = new RegExp(findStr, "gi");
return string.replace(re, replaceStr);
}
function textArrayReplace2(textArray, findStr, replacement) {
// replace string with array
for (var i = 0; i < textArray.length; i++) {
if (typeof textArray[i] == 'string') {
var pos = textArray[i].indexOf(findStr);
if (pos > -1) {
var newElems = clone(replacement);
newElems.unshift(textArray[i].slice(0, pos));
newElems.push(textArray[i].slice(pos + findStr.length));
textArray.splice(i, 1);
textArray.splice.apply(textArray, [i, 0].concat(newElems));
break;
}
} else if (typeof textArray[i] == 'object') {
textArray[i] = textArrayReplace2(textArray[i], findStr, replacement);
}
}
return textArray;
}
function getArrayCount(testArray, testValue) {
var count = 0;
for (var j = 0; j < testArray.length; j++) {
if (testArray[j] == testValue) {
count++;
}
}
return count;
}
function getArrayLessThanCount(testArray, testValue) {
var count = 0;
for (var j = 0; j < testArray.length; j++) {
if (testArray[j] < testValue) {
count++;
}
}
return count;
}
function shuffleArray(array) {
var newArray = [];
do {
var randomPos = Math.floor(Math.random() * array.length);
newArray.push(array[randomPos]);
array.splice(randomPos, 1);
} while (array.length > 0);
return newArray;
}
function buildArray(array, dim0, dim1, dim2, dim3, dim4, dim5) {
if ((typeof dim0 !== 'undefined') && (typeof array[dim0] == 'undefined')) {
array[dim0] = [];
}
if ((typeof dim1 !== 'undefined') && (typeof array[dim0][dim1] == 'undefined')) {
array[dim0][dim1] = [];
}
if ((typeof dim2 !== 'undefined') && (typeof array[dim0][dim1][dim2] == 'undefined')) {
array[dim0][dim1][dim2] = [];
}
if ((typeof dim3 !== 'undefined') && (typeof array[dim0][dim1][dim2][dim3] == 'undefined')) {
array[dim0][dim1][dim2][dim3] = [];
}
if ((typeof dim4 !== 'undefined') && (typeof array[dim0][dim1][dim2][dim3][dim4] == 'undefined')) {
array[dim0][dim1][dim2][dim3][dim4] = [];
}
if ((typeof dim5 !== 'undefined') && (typeof array[dim0][dim1][dim2][dim3][dim4][dim5] == 'undefined')) {
array[dim0][dim1][dim2][dim3][dim4][dim5] = [];
}
}
function arraySum(array) {
var sum = 0;
if (!un(array)) {
for (var a = 0, aMax = array.length; a < aMax; a++) {
sum += Number(array[a]);
}
}
return sum;
}
Array.prototype.alphanumSort = function (caseInsensitive) {
for (var z = 0, t; t = this[z]; z++) {
this[z] = [],
x = 0,
y = -1,
n = 0,
i,
j;
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
var m = (i == 46 || (i >= 48 && i <= 57));
if (m !== n) {
this[z][++y] = "";
n = m;
}
this[z][y] += j;
}
}
this.sort(function (a, b) {
for (var x = 0, aa, bb; (aa = a[x]) && (bb = b[x]); x++) {
if (caseInsensitive) {
aa = aa.toLowerCase();
bb = bb.toLowerCase();
}
if (aa !== bb) {
var c = Number(aa),
d = Number(bb);
if (c == aa && d == bb) {
return c - d;
} else
return (aa > bb) ? 1 : -1;
}
}
return a.length - b.length;
});
for (var z = 0; z < this.length; z++)
this[z] = this[z].join("");
}
function compareVersion(data0, data1, levels) {
function getVersionHash(version) {
var value = 0;
version = version.split(".").map(function (a) {
var n = parseInt(a);
var letter = a.replace(n, "");
if (letter) {
return n + letter[0].charCodeAt() / 0xFF;
} else {
return n;
}
});
for (var i = 0; i < version.length; ++i) {
if (levels === i)
break;
value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
}
return value;
};
var v1 = getVersionHash(data0);
var v2 = getVersionHash(data1);
return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
};
function mouseHitRect(l, t, w, h) {
var x = mouse.x;
var y = mouse.y;
if (x >= l && x <= (l + w) && y >= t && y <= (t + h)) {
return true;
} else {
return false;
}
}
function hitTestMouseOver(obj) {
if (un(obj))
return;
var objBoundingRect = obj.getBoundingClientRect();
var x = xCanvasToWindow(mouse.x);
var y = yCanvasToWindow(mouse.y);
if (x > objBoundingRect.left && x < objBoundingRect.right && y > objBoundingRect.top && y < objBoundingRect.bottom) {
return true;
} else {
return false;
}
}
function hitTestTwoObjects(obj1, obj2) {
var obj1BoundingRect = obj1.getBoundingClientRect();
var obj2BoundingRect = obj2.getBoundingClientRect();
if (obj1BoundingRect.left < obj2BoundingRect.right && obj1BoundingRect.top < obj2BoundingRect.bottom && obj1BoundingRect.right > obj2BoundingRect.left && obj1BoundingRect.bottom > obj2BoundingRect.top) {
return true
} else {
if (obj1BoundingRect.right > obj2BoundingRect.left && obj1BoundingRect.top < obj2BoundingRect.bottom && obj1BoundingRect.left < obj2BoundingRect.right && obj1BoundingRect.bottom > obj2BoundingRect.top) {
return true;
} else {
if (obj2BoundingRect.left < obj1BoundingRect.right && obj2BoundingRect.top < obj1BoundingRect.bottom && obj2BoundingRect.right > obj1BoundingRect.left && obj2BoundingRect.bottom > obj1BoundingRect.top) {
return true
} else {
if (obj2BoundingRect.right > obj1BoundingRect.left && obj2BoundingRect.top < obj1BoundingRect.bottom && obj2BoundingRect.left < obj1BoundingRect.right && obj2BoundingRect.bottom > obj1BoundingRect.top) {
return true;
} else {
return false
}
}
}
}
}
function hitTestRect(obj, left, top, width, height) {
var objBoundingRect = obj.getBoundingClientRect();
var right = left + width;
var bottom = top + height;
left = xCanvasToWindow(left);
right = xCanvasToWindow(right);
top = yCanvasToWindow(top);
bottom = yCanvasToWindow(bottom);
if (objBoundingRect.left < right && objBoundingRect.top < bottom && objBoundingRect.right > left && objBoundingRect.bottom > top) {
return true;
} else {
if (objBoundingRect.right > left && objBoundingRect.top < bottom && objBoundingRect.left < right && objBoundingRect.bottom > top) {
return true;
} else {
if (left < objBoundingRect.right && top < objBoundingRect.bottom && right > objBoundingRect.left && bottom > objBoundingRect.top) {
return true;
} else {
if (right > objBoundingRect.left && top < objBoundingRect.bottom && left < objBoundingRect.right && bottom > objBoundingRect.top) {
return true;
} else {
return false;
}
}
}
}
}
function hitTestRect2(obj, left, top, width, height) { // tests if center of obj is in rect
var objBoundingRect = obj.getBoundingClientRect();
var objX = objBoundingRect.left + 0.5 * (objBoundingRect.right - objBoundingRect.left);
var objY = objBoundingRect.top + 0.5 * (objBoundingRect.bottom - objBoundingRect.top);
var right = left + width;
var bottom = top + height;
left = xCanvasToWindow(left);
right = xCanvasToWindow(right);
top = yCanvasToWindow(top);
bottom = yCanvasToWindow(bottom);
if (objX < right && objY < bottom && objX > left && objY > top) {
return true;
} else {
return false;
}
}
function hitTestTwoRects(rect1, rect2) {
var xHit = false;
var yHit = false;
if (
(rect2[0] <= rect1[0] && rect2[0] + rect2[2] >= rect1[0]) ||
(rect2[0] <= rect1[0] + rect1[2] && rect2[0] + rect2[2] >= rect1[0] + rect1[2]) ||
(rect2[0] >= rect1[0] && rect2[0] + rect2[2] <= rect1[0] + rect1[2])) {
xHit = true;
};
if (
(rect2[1] <= rect1[1] && rect2[1] + rect2[3] >= rect1[1]) ||
(rect2[1] <= rect1[1] + rect1[3] && rect2[1] + rect2[3] >= rect1[1] + rect1[3]) ||
(rect2[1] >= rect1[1] && rect2[1] + rect2[3] <= rect1[1] + rect1[3])) {
yHit = true;
};
return (xHit && yHit);
}
function hitTestMouseOverRect(left, top, width, height) { // tests if mouse is in rect - REQUIRES mouse coords to have been updated
if (mouse.x < left + width && mouse.y < top + height && mouse.x > left && mouse.y > top) {
return true;
} else {
return false;
}
}
function hitTestCircle(obj, centreX, centreY, radius) {
var objBoundingRect = obj.getBoundingClientRect();
centreX = xCanvasToWindow(centreX);
centreY = yCanvasToWindow(centreY);
if ((window.innerWidth / window.innerHeight) > (12 / 7)) {
radius = (radius / canvas.height) * window.innerHeight;
} else {
radius = (radius / canvas.width) * window.innerWidth;
}
var testPoint = [];
testPoint[0] = Math.pow((objBoundingRect.left - centreX), 2) + Math.pow((objBoundingRect.top - centreY), 2);
testPoint[1] = Math.pow((objBoundingRect.left - centreX), 2) + Math.pow((objBoundingRect.bottom - centreY), 2);
testPoint[2] = Math.pow((objBoundingRect.right - centreX), 2) + Math.pow((objBoundingRect.top - centreY), 2);
testPoint[3] = Math.pow((objBoundingRect.right - centreX), 2) + Math.pow((objBoundingRect.bottom - centreY), 2);
testPoint[4] = Math.pow((objBoundingRect.left - centreX), 2) + Math.pow(((objBoundingRect.top + objBoundingRect.bottom) / 2 - centreY), 2);
testPoint[5] = Math.pow((objBoundingRect.right - centreX), 2) + Math.pow(((objBoundingRect.top + objBoundingRect.bottom) / 2 - centreY), 2);
testPoint[6] = Math.pow(((objBoundingRect.left + objBoundingRect.right) / 2 - centreX), 2) + Math.pow((objBoundingRect.top - centreY), 2);
testPoint[7] = Math.pow(((objBoundingRect.left + objBoundingRect.right) / 2 - centreX), 2) + Math.pow((objBoundingRect.bottom - centreY), 2);
if (getArrayLessThanCount(testPoint, Math.pow(radius, 2)) > 0) {
return true
} else {
return false
}
}
function hitTestCircle2(obj, centreX, centreY, radius) { // this one just requires the center of the object to be within the circle
var objBoundingRect = obj.getBoundingClientRect();
centreX = xCanvasToWindow(centreX);
centreY = yCanvasToWindow(centreY);
if ((window.innerWidth / window.innerHeight) > (12 / 7)) {
radius = (radius / canvas.height) * window.innerHeight;
} else {
radius = (radius / canvas.width) * window.innerWidth;
}
var objX = objBoundingRect.left + 0.5 * objBoundingRect.width;
var objY = objBoundingRect.top + 0.5 * objBoundingRect.height;
if (Math.pow((objX - centreX), 2) + Math.pow((objY - centreY), 2) <= Math.pow(radius, 2)) {
return true
} else {
return false
}
}
function hitTestMouseOverPolygon(verticesArray) {
var x = mouse.x;
var y = mouse.y;
// split n-agon into (n-2) triangles and test if (x,y) is in each triangle
var x1 = verticesArray[0][0];
var y1 = verticesArray[0][1];
for (var i = 1; i <= verticesArray.length - 2; i++) {
var x2 = verticesArray[i][0];
var y2 = verticesArray[i][1];
var x3 = verticesArray[i + 1][0];
var y3 = verticesArray[i + 1][1];
// work out the barycentric coordinates for the triangle
// iff all are positive, (x, y) is in the triangle
var alpha = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
var beta = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
var gamma = 1 - alpha - beta;
if (alpha > 0 && beta > 0 && gamma > 0) {
return true;
}
}
return false;
}
function hitTestPolygon(point, verticesArray, includePerimeter) {
var x = point[0];
var y = point[1];
// split n-agon into (n-2) triangles and test if (x,y) is in each triangle
var x1 = verticesArray[0][0];
var y1 = verticesArray[0][1];
for (var i = 1; i <= verticesArray.length - 2; i++) {
var x2 = verticesArray[i][0];
var y2 = verticesArray[i][1];
var x3 = verticesArray[i + 1][0];
var y3 = verticesArray[i + 1][1];
// work out the barycentric coordinates for the triangle
// iff all are positive, (x, y) is in the triangle
var alpha = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
var beta = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
var gamma = 1 - alpha - beta;
if (boolean(includePerimeter, true) == true) {
if (alpha >= 0 && beta >= 0 && gamma >= 0)
return true;
} else {
if (alpha > 0 && beta > 0 && gamma > 0)
return true;
}
}
return false;
}
function hitTestPolygon2(point,pos) {
//https://stackoverflow.com/questions/8721406/how-to-determine-if-a-point-is-inside-a-2d-convex-polygon/23223947#23223947
var result = false;
for (var i = 0, j = pos.length - 1; i < pos.length; j = i++) {
if ((pos[i][1] > point[1]) != (pos[j][1] > point[1]) &&
(point[0] < (pos[j][0] - pos[i][0]) * (point[1] - pos[i][1]) / (pos[j][1]-pos[i][1]) + pos[i][0])) {
result = !result;
}
}
return result;
}
function hitTestPolygonBoundary(point,pos,tol) {
if (un(tol)) tol = 0.01;
for (var p = 0; p < pos.length; p++) {
var pos1 = pos[p];
var pos2 = pos[(p+1)%pos.length];
if (isPointOnLineSegment(point, pos1, pos2, tol) == true) return true;
}
return false;
}
function motionPath(canvasData, startX, startY, finX, finY, rateType, rate) { // eg. rateType='time', rate=2000 (time in ms to complete motion) or rateType='speed', rate=200 (speed of motion in (canvas) pixels per second)
var xDistance = finX - startX;
var yDistance = finY - startY;
var distance = Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
var time;
if (rateType == 'speed') {
time = 1000 * distance / rate
};
if (rateType == 'time') {
time = rate
};
var dx = xDistance * 40 / time;
var dy = yDistance * 40 / time;
var frameCount = 0;
var totalFrames = Math.floor(time / 40);
var motion = setInterval(function () {
canvasData[100] += dx;
canvasData[101] += dy;
resize();
frameCount++;
if (frameCount >= totalFrames) {
canvasData[100] = finX;
canvasData[101] = finY;
resize();
clearInterval(motion)
};
}, 40);
}
function motionResize(canvasData, newWidth, newHeight, time, coeX, coeY) {
//if not present, the coe will default to the centre of the object
if (!coeX) {
coeX = canvasData[100] + 0.5 * canvasData[102]
};
if (!coeY) {
coeY = canvasData[101] + 0.5 * canvasData[103]
};
var dw = (newWidth - canvasData[102]) * 40 / time;
var dh = (newHeight - canvasData[103]) * 40 / time;
var dx = 0.5 * (canvasData[102] - newWidth) * 40 / time;
var dy = 0.5 * (canvasData[103] - newHeight) * 40 / time;
var finX = canvasData[100] + 0.5 * (canvasData[102] - newWidth);
var finY = canvasData[101] + 0.5 * (canvasData[103] - newHeight);
var frameCount = 0;
var totalFrames = Math.floor(time / 40);
var motion = setInterval(function () {
canvasData[100] += dx;
canvasData[101] += dy;
canvasData[102] += dw;
canvasData[103] += dh;
resize();
frameCount++;
if (frameCount >= totalFrames) {
canvasData[100] = finX;
canvasData[101] = finY;
canvasData[102] = newWidth;
canvasData[103] = newHeight;
resize();
clearInterval(motion)
};
}, 40);
}
function ceiling(number, toNearest) {
// get the place value of toNearest
var decPointPos;
if (String(toNearest).indexOf('.') !== -1) {
decPointPos = String(toNearest).indexOf('.');
} else {
decPointPos = String(toNearest).length - 1;
}
var placeValue;
for (ii = 0; ii < String(toNearest).length; ii++) {
if (String(toNearest).charAt(ii) !== "0" && String(toNearest).charAt(ii) !== ".") {
placeValue = decPointPos - ii;
}
}
// divide number and toNearesr by 10^placevalue
number = number / (Math.pow(10, placeValue));
toNearest = toNearest / (Math.pow(10, placeValue));
// divide number by toNearest
number = number / toNearest;
number = Math.ceil(number);
number = number * toNearest;
number = number * (Math.pow(10, placeValue));
number = Math.round(number * 1000000000) / 1000000000;
return number;
}
function truncate(number, decPlaces) {
return + (Math.floor(number * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces)).toFixed(decPlaces);
}
function round2(number, decPlaces) {
return + (Math.round(number * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces)).toFixed(decPlaces);
}
function roundToNearest(number, toNearest) {
var testLog = Math.log(toNearest) / Math.log(10);
if (Math.abs(testLog - Math.round(testLog)) < 0.000001) { // powers of 10
return round(number, toNearest);
} else {
// get the place value of toNearest
var decPointPos;
if (String(toNearest).indexOf('.') !== -1) {
decPointPos = String(toNearest).indexOf('.');
} else {
decPointPos = String(toNearest).length - 1;
}
var placeValue;
for (var i = 0; i < String(toNearest).length; i++) {
if (String(toNearest).charAt(i) !== "0" && String(toNearest).charAt(i) !== ".") {
placeValue = decPointPos - i;
}
}
number = number / (Math.pow(10, placeValue));
toNearest = toNearest / (Math.pow(10, placeValue));
number = number / toNearest;
number = Math.round(number);
number = number * toNearest;
number = number * (Math.pow(10, placeValue));
number = Math.round(number * 1000000000) / 1000000000;
return number;
}
}
function roundSF(number, sigFigs, showAllZeros) {
if (un(sigFigs)) sigFigs = 1;
var negative = number < 0 ? true : false;
number = Math.abs(number);
var pv = Math.floor(Math.log(number)/Math.log(10)) - (sigFigs-1);
var toNearest = Math.pow(10,pv);
var rounded = String(roundToNearest(number,toNearest));
var sfCount = 0; // check number of sf in answer
var dpPassed = false;
for (var d = 0; d < rounded.length; d++) {
if (rounded[d] == '-') continue;
if (rounded[d] == '.') {
if (sfCount >= sigFigs) {
rounded = rounded.slice(0,d);
break;
} else {
dpPassed = true;
continue;
}
}
if (sfCount == 0 && rounded[d] == '0') continue;
sfCount++;
if (dpPassed == true && sfCount == sigFigs) {
rounded = rounded.slice(0,d+1);
break;
}
}
if (negative == true) rounded = '-'+rounded;
return rounded;
}
function round(number, toNearest, returnAsString) { // toNearest must be a power of 10
var sign = number < 0 ? '-' : '';
var str = String(Math.abs(number));
var decPos = str.indexOf('.') > -1 ? str.indexOf('.') : str.length;
var digits = [];
for (var n = 0; n < str.length; n++) {
if (isNaN(Number(str[n])))
continue;
digits.push(Number(str[n]));
}
var roundPV = Math.round(Math.log(toNearest) / Math.log(10));
var roundPos = Math.round(decPos - roundPV - 1);
if (roundPos >= digits.length) {
var addDigits = roundPos - digits.length;
for (var p = 0; p <= addDigits; p++)
digits.push(0);
} else {
if (roundPos == -1) {
if (digits[0] >= 5) {
digits.unshift(1);
roundPos++;
decPos++;
} else {
return 0;
}
} else if (digits[roundPos + 1] >= 5) {
if (digits[roundPos] < 9) {
digits[roundPos]++;
} else {
digits[roundPos] = 0;
var done = false;
var pos = roundPos - 1;
var count = 0;
while (done == false && count < 50) {
count++
if (pos == -1) {
digits.unshift(1);
roundPos++;
decPos++;
done = true;
} else if (digits[pos] < 9) {
digits[pos]++;
done = true;
} else {
digits[pos] = 0;
pos--;
}
}
}
}
for (var p = roundPos + 1; p < digits.length; p++)
digits[p] = 0;
}
var str2 = sign;
for (var p = 0; p < digits.length; p++) {
if (p == decPos) {
if (roundPV < 0) {
str2 += '.';
} else {
break;
}
}
if (p == roundPos + 1 && p > decPos)
break;
str2 += String(digits[p]);
}
if (Number(str2) == 0)
str2 = '0';
if (boolean(returnAsString, false) == true) {
return str2;
} else {
return Number(str2);
}
//console.log(sign,str,toNearest,decPos,digits,roundPV,roundPos,str2);
}
function nthroot(x, n) {
try {
var negate = n % 2 == 1 && x < 0;
if (negate)
x = -x;
var possible = Math.pow(x, 1 / n);
n = Math.pow(possible, n);
if (Math.abs(x - n) < 1 && (x > 0 == n > 0))
return negate ? -possible : possible;
} catch (e) {}
}
function hcf(x, y) {
x = Math.abs(x);
y = Math.abs(y);
// Apply Euclid's algorithm to the two numbers.
while (Math.max(x, y) % Math.min(x, y) != 0) {
if (x > y) {
x %= y;
} else {
y %= x;
}
}
// When the while loop finishes the minimum of x and y is the HCF.
return Math.min(x, y);
}
function ran(minNum, maxNum) {
return Math.floor((maxNum - minNum + 1) * Math.random()) + minNum;
}
function clickToHide(canvas, canvasctx, canvasData, font, textColor) {
if (!font) {
font = '12px Arial'
};
if (!textColor) {
textColor = '#333'
};
canvasctx.font = font;
canvasctx.fillStyle = textColor;
canvasctx.textBaseline = 'bottom';
canvasctx.textAlign = 'center';
canvasctx.fillText('Click to close', canvasData[2] / 2, canvasData[3] - 3);
canvas.style.pointerEvents = 'auto';
canvas.onclick = function () {
hideObj(canvas, canvasData);
}
}
// rotates coordinates about the origin (or a given center) and rounds to nearest integer
function rotateCoords(x, y, degrees, opt_direction, opt_centerX, opt_centerY, opt_round) {
var round = true;
if (typeof opt_round == 'boolean') {
round = opt_round
}
var direction = opt_direction || 'anti-cw'; // use 'cw' or 'anti-cw'
var centerX = opt_centerX || 0; //optional centre of rotation
var centerY = opt_centerY || 0;
// convert to anti-cw and radians
if (direction == 'cw')
degrees = 360 - degrees;
var angleRads = degrees / 180 * Math.PI;
// transpose to origin
x -= centerX;
y -= centerY;
// apply matrix
if (round == true) {
var newX = Math.round(Math.cos(angleRads) * x - Math.sin(angleRads) * y);
var newY = Math.round(Math.sin(angleRads) * x + Math.cos(angleRads) * y);
} else {
var newX = Math.cos(angleRads) * x - Math.sin(angleRads) * y;
var newY = Math.sin(angleRads) * x + Math.cos(angleRads) * y;
}
// un-transpose
newX += centerX;
newY += centerY;
return {
x: newX,
y: newY
};
}
var variArray = ['a', 'b', 'c', 'd', 'g', 'h', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'y', 'y'];
function equivExpressions(string1, string2) { // checks if two algebraic expressions (as strings in x only) are equivalent
if (string1 == '' || string2 == '') {
return false;
}
var equivalent = true;
// select 100 * 2 random numbers of different magnitudes
for (mag = 0; mag < 10; mag++) {
for (num = 0; num < 10; num++) {
var x = Math.random() * Math.pow(10, mag);
var val1 = eval(string1);
var val2 = eval(string2);
// get percentage error between values
var pError = 0;
if (val1 !== 0) {
pError = Math.abs((val1 - val2) / val1);
} else if (val2 !== 0) {
pError = Math.abs((val1 - val2) / val2);
}
if (pError > 0.00001) {
equivalent = false;
}
x = -1 * x;
val1 = eval(string1);
val2 = eval(string2);
pError = 0;
if (val1 !== 0) {
pError = Math.abs((val1 - val2) / val1);
} else if (val2 !== 0) {
pError = Math.abs((val1 - val2) / val2);
}
if (pError > 0.00001) {
equivalent = false;
}
}
}
return equivalent
}
function buildQuadraticExp(d, e, f, g, h, vari) {
if (un(h))
h = 1;
var a = h * d * f;
var b = h * (d * g + e * f);
var c = h * e * g;
return buildQuadratic(a, b, c, d, e, f, g, h, vari);
}
function buildQuadratic(a, b, c, d, e, f, g, h, vari) {
// ax^2 + bx + c = h(dx + e)(fx + g)
if (!vari) {
var variNum = ran(0, 50);
vari = 'x';
if (variNum < 19)
vari = variArray[variNum];
}
var p,
q,
r,
exp,
fac,
compSq,
disc,
qText,
rText,
root1Text,
root2Text,
vertexText,
expJS,
factors = [],
factorisedForms = [],
expandedTerms = [],
expandedForms = [];
// create the expanded form text
var aSign = '';
var aFirstSign = '';
var aTerm = String(Math.abs(a)) + vari;
var aTermJS = String(Math.abs(a)) + '*Math.pow(' + vari + ',2)';
if (a > 0)
aSign = ' + ';
if (a < 0)
aSign = ' - ';
if (a < 0)
aFirstSign = '-';
if (a == 1 || a == -1)
aTerm = vari;
if (a == 1)
aTermJS = 'Math.pow(' + vari + ',2)';
if (a == -1)
aTermJS = '-Math.pow(' + vari + ',2)';
if (a == 0)
aTerm = '';
var bSign = '';
var bFirstSign = '';
var bTerm = String((Math.abs(b))) + vari;
var bTermJS = String((Math.abs(b))) + '*' + vari;
if (b > 0)
bSign = ' + ';
if (b < 0)
bSign = ' - ';
if (b < 0)
bFirstSign = '-';
if (b == 1 || b == -1)
bTerm = vari;
if (b == 1 || b == -1)
bTermJS = vari;
if (b == -1)
bTermJS = vari;
if (b == 0)
bTerm = '';
if (b == 0)
bTermJS = '';
var cSign = '';
var cFirstSign = '';
var cTerm = String(Math.abs(c));
var cTermJS = String(Math.abs(c));
if (c > 0)
cSign = ' + ';
if (c < 0)
cSign = ' - ';
if (c < 0)
cFirstSign = '-';
if (c == 0)
cTermJS = '';
if (c == 0)
cTerm = '';
var expTermsMathsText = [[aFirstSign, aTerm], [bFirstSign, bTerm], [cFirstSign, cTerm]];
var expFormsMathsText = [
[aFirstSign + aTerm, ['power', false, '2'], bSign, bTerm, cSign, cTerm],
[aFirstSign + aTerm, ['power', false, '2'], cSign, cTerm, bSign, bTerm],
[bFirstSign + bTerm + aSign + aTerm, ['power', false, '2'], cSign + cTerm],
[bFirstSign + bTerm + cSign + cTerm + aSign + aTerm, ['power', false, '2']],
[cFirstSign + cTerm + aSign + aTerm, ['power', false, '2'], bSign + bTerm],
[cFirstSign + cTerm + bSign + bTerm + aSign + aTerm, ['power', false, '2']]
];
var expTermsJS = [aTermJS, bTermJS, cTermJS];
var expFormsJS = [
aFirstSign + aTermJS + bSign + bTermJS + cSign + cTermJS,
aFirstSign + aTermJS + cSign + cTermJS + bSign + bTermJS,
bFirstSign + bTermJS + aSign + aTermJS + cSign + cTermJS,
bFirstSign + bTermJS + cSign + cTermJS + aSign + aTermJS,
cFirstSign + cTermJS + aSign + aTermJS + bSign + bTermJS,
cFirstSign + cTermJS + bSign + bTermJS + aSign + aTermJS,
]
if (a > 0 || (a < 0 && b < 0 && c < 0)) {
exp = expFormsMathsText[0];
expJS = expFormsJS[0];
} else if (c > 0) {
exp = expFormsMathsText[5];
expJS = expFormsJS[5];
} else {
exp = expFormsMathsText[3];
expJS = expFormsJS[3];
}
// create the factorised forms
var factors = constructFactors(h, d, e, f, g, vari);
var factorisedForms = constructFactorisedForms(factors);
var hFactors = [];
for (var hF = h; hF > 1; hF--) {
if (h % hF == 0)
hFactors.push(hF);
}
var compositeFactors = [];
var partiallyFactorisedForms = [];
//construct sets of factors;
if (typeof d !== 'undefined' && typeof e !== 'undefined' && typeof f !== 'undefined' && typeof g !== 'undefined') {
if (d < 0) {
bracket1 = '(' + e + " - ";
var dText = Math.abs(d);
if (d == -1)
dText = '';
bracket1 += dText + vari + ')'
} else {
var eSign = " + ";
if (e < 0) {
eSign = " - "
};
var dText = d;
if (d == 1)
dText = "";
if (d == -1)
dText = "-";
bracket1 = "(" + dText + vari + eSign + Math.abs(e) + ")";
}
if (f < 0) {
bracket2 = '(' + g + " - ";
var fText = Math.abs(f);
if (f == -1)
fText = '';
bracket2 += fText + vari + ')'
} else {
var gSign = " + ";
if (g < 0) {
gSign = " - "
};
var fText = f;
if (f == 1)
fText = "";
if (f == -1)
fText = "-";
bracket2 = "(" + fText + vari + gSign + Math.abs(g) + ")";
}
fac = bracket1 + bracket2;
if (Math.random() < 0.5)
fac = bracket2 + bracket1;
if (bracket1 == bracket2) {
fac = bracket1 + String.fromCharCode(0x00B2)
};
if (e == 0) {
var dText = d;
if (d == 1)
dText = '';
if (d == -1)
dText = '-';
bracket1 = dText + vari;
}
if (g == 0) {
var fText = f;
if (f == 1)
fText = '';
if (f == -1)
fText = '-';
bracket2 = fText + vari;
fac = bracket2 + bracket1;
}
if (typeof h !== 'undefined') {
var hTerm = h;
if (h == 1)
hTerm = '';
if (h == -1)
hTerm = '-';
fac = hTerm + fac;
}
}
// create the completed square form text
p = a;
q = b / (2 * a);
r = c - (b * b) / (4 * a);
compSq = '';
if (p > 0 || p < 0 && r <= 0) {
var pTerm = String(p);
if (p == 1)
pTerm = '';
if (p == -1)
pTerm = '-';
var qSign = " + ";
if (q < 0)
qSign = " - ";
var qTerm = toMathsText(b, 2 * a);
var rSign = " + ";
if (r < 0)
rSign = " - ";
var rTerm = toMathsText(4 * a * c - b * b, 4 * a);
if (r == 0) {
rSign = '';
rTerm = '';
}
compSq = [pTerm + '(' + vari + qSign, qTerm, ')', ['pow', '', '2'], rSign, rTerm];
} else if (p < 0 && r > 0) { // r - p(x +- q)^2
var rTerm = toMathsText(4 * a * c - b * b, 4 * a);
var qSign = " + ";
if (q < 0)
qSign = " - ";
var qTerm = toMathsText(b, 2 * a);
var pTerm = toMathsText(p);
if (p == -1)
pTerm = '';
compSq = [rTerm, ' - ' + pTerm + '(' + vari + qSign, qTerm, ')', ['pow', '', '2']];
}
// makes qText, rText and vertexText
if (q < 0) {
qText = ["-", qTerm];
if (r < 0) {
rText = ["-", rTerm];
vertexText = ['(', qTerm, ', -', rTerm, ')'];
} else {
rText = rTerm;
vertexText = ['(', qTerm, ', ', rTerm, ')'];
}
} else {
qText = qTerm;
if (r < 0) {
rText = ["-", rTerm];
vertexText = ['(-', qTerm, ', -', rTerm, ')'];
} else {
rText = rTerm;
vertexText = ['(-', qTerm, ', ', rTerm, ')'];
}
}
disc = b * b - 4 * a * c;
// solves ax^2 + bx + c = 0
var root1 = (-1 * b + Math.pow(b * b - 4 * a * c, 0.5)) / (2 * a);
var root2 = (-1 * b - Math.pow(b * b - 4 * a * c, 0.5)) / (2 * a);
// makes root1text and root2text
// if non-surd solutions
if (Math.pow(b * b - 4 * a * c, 0.5) == Math.round(Math.pow(b * b - 4 * a * c, 0.5))) {
var root1sign = '';
if ((-1 * b + Math.pow(b * b - 4 * a * c, 0.5)) / (2 * a) < 0)
root1sign = '-';
root1text = [root1sign, toMathsText((-1 * b + Math.pow(b * b - 4 * a * c, 0.5)), (2 * a))];
var root2sign = '';
if ((-1 * b - Math.pow(b * b - 4 * a * c, 0.5)) / (2 * a) < 0)
root2sign = '-';
root2text = [root2sign, toMathsText((-1 * b - Math.pow(b * b - 4 * a * c, 0.5)), (2 * a))];
} else {
var surd = simpSurd(b * b - 4 * a * c, 0.5);
root1text = [toMathsText((-1 * b), (2 * a)), ' + ', toMathsText(1, (2 * a)), surd.mathsText];
root2text = [toMathsText((-1 * b), (2 * a)), ' - ', toMathsText(1, (2 * a)), surd.mathsText];
}
return {
exp: exp,
fac: fac,
compSq: compSq,
disc: disc,
a: a,
b: b,
c: c,
d: d,
e: e,
f: f,
g: g,
h: h,
p: p,
q: q,
r: r,
vari: vari,
root1: root1,
root2: root2,
qText: qText,
rText: rText,
vertexText: vertexText,
root1text: root1text,
root2text: root2text,
factors: factors,
factorisedForms: factorisedForms,
expTermsMathsText: expTermsMathsText,
expFormsMathsText: expFormsMathsText,
expTermsJS: expTermsJS,
expFormsJS: expFormsJS,
expJS: expJS
}
}
function quadratic(level, vari) {
if (!vari) {
var variNum = ran(0, 50);
vari = 'x';
if (variNum < 19)
vari = variArray[variNum];
}
// quadratic is (dx + e)(fx + g) = ax^2 + bx + c
var a,
b,
c,
d,
e,
f,
g,
h,
p,
q,
r,
exp,
fac,
compSq,
disc,
qText,
rText,
root1Text,
root2Text,
vertexText,
expJS;
var factors = [];
var factorisedForms = [];
var expandedTerms = [];
var expandedForms = [];
switch (level) {
case 1:
// (x +- e)(x +- g) || dx(fx +- g)
do {
d = 1;
e = ran(-10, 10);
f = 1;
g = ran(-10, 10);
} while (g == 0)
if (e == 0) {
d = ran(1, 3);
do {
f = ran(1, 3);
} while (hcf(f, g) !== 1)
}
a = d * f;
b = d * g + e * f;
c = e * g;
h = 1;
break;
case 2:
// h(x +- e)(x +- g), where h > 1
do {
d = 1;
e = ran(-10, 10);
f = 1;
g = ran(-10, 10);
} while (e == 0 || g == 0)
a = d * f;
b = d * g + e * f;
c = e * g;
h = [2, 2, 3, 4, 5, 10][ran(0, 5)];
break;
case 3:
// h(dx +- e)(fx +- g) where d = 1 or f = 1 but not both
do {
d = ran(1, 6);
e = ran(-10, 10);
f = ran(1, 6);
g = ran(-10, 10);
h = Math.max(1, ran(-15, 5));
} while (e == 0 || g == 0 || (d == 1 && f == 1) || (d !== 1 && f !== 1) || hcf(d, e) > 1 || hcf(f, g) > 1)
a = h * d * f;
b = h * (d * g + e * f);
c = h * e * g;
break;
case 4:
// h(dx +- e)(fx +- g) where d >= 1 and f >= 1
do {
d = ran(1, 6);
e = ran(-10, 10);
f = ran(1, 6);
g = ran(-10, 10);
h = Math.max(1, ran(-15, 5));
} while (e == 0 || g == 0 || (d == 1 && f == 1) || hcf(d, e) > 1 || hcf(f, g) > 1)
a = h * d * f;
b = h * (d * g + e * f);
c = h * e * g;
break;
case 5:
// h(e - dx)(fx +- g)
do {
d = ran(-6, -1);
e = ran(1, 10);
f = ran(1, 6);
g = ran(-10, 10);
h = Math.max(1, ran(-15, 5));
} while (g == 0 || hcf(d, e) > 1 || hcf(f, g) > 1)
a = h * d * f;
b = h * (d * g + e * f);
c = h * e * g;
break;
case 6:
// +-h(+-dx +- e)(+-fx +- g) where d >= 1 and f >= 1
do {
d = ran(-3, 3);
e = ran(-10, 10);
f = ran(-3, 3);
g = ran(-10, 10);
h = ran(-3, 3);
} while (e == 0 || g == 0 || d == 0 || f == 0 || h == 0 || (d == 1 && f == 1) || hcf(d, e) > 1 || hcf(f, g) > 1)
if (d < 0 && e < 0) {
d = -d;
e = -e;
h = -h;
}
if (f < 0 && g < 0) {
f = -f;
g = -g;
h = -h;
}
a = h * d * f;
b = h * (d * g + e * f);
c = h * e * g;
break;
case 7:
// generates a quadratic ax^2 +- bx +- c that doesn't factorise
do {
a = ran(1, 5);
b = ran(-100, 100);
c = ran(-100, 100);
var disc = Math.pow(b * b - 4 * a * c, 0.5);
} while (disc == Math.round(disc));
h = 1;
break;
case 8:
// generates a quadratic +-ax^2 +- bx +- c that doesn't factorise
do {
a = ran(-10, 10);
b = ran(-100, 100);
c = ran(-100, 100);
var disc = Math.pow(b * b - 4 * a * c, 0.5);
} while (a == 0 || disc == Math.round(disc) || (b == 0 && c == 0));
h = 1;
break;
}
return buildQuadratic(a, b, c, d, e, f, g, h, vari);
}
function sketchQuad(context, quad, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax, yIntLabel, xIntLabel, vertexLabel, opt_fontSize) {
// draws a sketch of the graph of a quadratic
if (typeof yIntLabel !== 'boolean')
yIntLabel = false;
if (typeof xIntLabel !== 'boolean')
xIntLabel = false;
if (typeof vertexLabel !== 'boolean')
vertexLabel = false;
if (quad.q == 0 && vertexLabel == true)
yIntLabel = true;
var fontSize = opt_fontSize || (Math.min(gridWidth, gridHeight) / 13);
if (quad.a > 0) {
// need to solve ax^2 + bx + c = yMax
var maxVal = solveQuad(quad.a, quad.b, quad.c - yMax);
} else {
// need to solve ax^2 + bx + c = yMin
var maxVal = solveQuad(quad.a, quad.b, quad.c - yMin);
}
var xStart = Math.max(xMin, maxVal.x2);
var yStart = quad.a * xStart * xStart + quad.b * xStart + quad.c;
var xFin = Math.min(xMax, maxVal.x1);
var yFin = quad.a * xFin * xFin + quad.b * xFin + quad.c;
var controlPoint = getBezierCurveForQuad(quad.a, quad.b, quad.c, xStart, yStart, xFin, yFin);
var minPoint = {
x: -1 * quad.q,
y: quad.r
}
var gridSta = convertGridCoordsToCanvas(xStart, yStart, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
var gridFin = convertGridCoordsToCanvas(xFin, yFin, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
var gridCon = convertGridCoordsToCanvas(controlPoint.x, controlPoint.y, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
var gridMin = convertGridCoordsToCanvas(minPoint.x, minPoint.y, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
var gridRoot1 = convertGridCoordsToCanvas(quad.root1, 0, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
var gridRoot2 = convertGridCoordsToCanvas(quad.root2, 0, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
var gridYInt = convertGridCoordsToCanvas(0, quad.c, gridLeft, gridTop, gridWidth, gridHeight, xMin, xMax, yMin, yMax);
context.lineWidth = 1;
context.strokeStyle = '#666';
context.beginPath();
context.moveTo(gridLeft, gridTop + 0.5 * gridHeight);
context.lineTo(gridLeft + gridWidth, gridTop + 0.5 * gridHeight);
context.moveTo(gridLeft + gridWidth * 0.5, gridTop);
context.lineTo(gridLeft + gridWidth * 0.5, gridTop + gridHeight);
context.stroke();
context.strokeStyle = '#000';
context.beginPath()
context.moveTo(gridSta.x, gridSta.y);
context.quadraticCurveTo(gridCon.x, gridCon.y, gridFin.x, gridFin.y);
if (vertexLabel == true) {
context.moveTo(gridMin.x - 5, gridMin.y - 5);
context.lineTo(gridMin.x + 5, gridMin.y + 5);
context.moveTo(gridMin.x - 5, gridMin.y + 5);
context.lineTo(gridMin.x + 5, gridMin.y - 5);
}
context.stroke();
context.font = "14px Arial";
context.textBaseline = "middle";
context.fillStyle = "#000";
context.textAlign = 'center';
if (quad.disc == 0) {
if (xIntLabel == true || vertexLabel == true)
drawMathsText(context, quad.root1text, fontSize, gridRoot1.x, gridRoot1.y + 15, true, '', 'center', 'middle', '#000', 'draw', '#000');
if (yIntLabel == true)
drawMathsText(context, [String(quad.c)], fontSize, gridYInt.x + 5, gridYInt.y, true, '', 'left', 'middle', '#000', 'draw', '#000');
} else if (quad.a > 0) {
if (xIntLabel == true)
drawMathsText(context, quad.root1text, fontSize, gridRoot1.x + 4, gridRoot1.y + 12, true, '', 'left', 'middle', '#000', 'draw');
if (xIntLabel == true)
drawMathsText(context, quad.root2text, fontSize, gridRoot2.x - 4, gridRoot1.y + 12, true, '', 'right', 'middle', '#000', 'draw');
if (quad.b !== 0) {
var vertexY = gridMin.y + 20;
if (xIntLabel == true && vertexY - (gridTop + 0.5 * gridHeight) < 40)
vertexY = gridTop + 0.5 * gridHeight + 40;
if (vertexLabel == true)
drawMathsText(context, quad.vertexText, fontSize, gridMin.x, vertexY, true, '', 'center', 'middle', '#000', 'draw');
}
if (quad.c !== 0) {
if (quad.q <= 0) {
if (quad.b !== 0) {
if (yIntLabel == true)
drawMathsText(context, [String(quad.c)], fontSize, gridYInt.x - 5, gridYInt.y, true, '', 'right', 'middle', '#000', 'draw');
} else {
if (yIntLabel == true)
drawMathsText(context, [String(quad.c)], fontSize, gridYInt.x - 5, gridYInt.y + 15, true, '', 'right', 'middle', '#000', 'draw');
}
} else {
if (quad.b !== 0) {
if (yIntLabel == true)
drawMathsText(context, [String(quad.c)], fontSize, gridYInt.x + 5, gridYInt.y, true, '', 'left', 'middle', '#000', 'draw');
} else {
if (yIntLabel == true)
drawMathsText(context, [String(quad.c)], fontSize, gridYInt.x + 5, gridYInt.y + 15, true, '', 'left', 'middle', '#000', 'draw');
}
}
}
} else {
// quads with a < 0
}
}
function constructFactors(z0, z1, z2, z3, z4, vari) {
// z0 is h
var hSign = '+';
var hOppSign = '-';
var hFirstSign = '';
var hOppFirstSign = '-';
if (z0 < 0) {
hSign = '-';
hOppSign = '+';
hFirstSign = '-';
hOppFirstSign = '';
}
var hTerm = String(Math.abs(z0));
if (z0 == 1 || z0 == -1)
hTerm = '';
// z1 is d (ie.dx)
var dSign = '+';
var dOppSign = '-';
var dFirstSign = '';
var dOppFirstSign = '-';
if (z1 < 0) {
dSign = '-';
dOppSign = '+';
dFirstSign = '-';
dOppFirstSign = '';
}
var dTerm = String(Math.abs(z1)) + vari;
if (z1 == 1 || z1 == -1)
dTerm = vari;
// z2 is e
var eSign = '+';
var eOppSign = '-';
var eFirstSign = '';
var eOppFirstSign = '-';
if (z2 < 0) {
eSign = '-';
eOppSign = '+';
eFirstSign = '-';
eOppFirstSign = '';
}
var eTerm = String(Math.abs(z2));
if (z2 == 0) {
eSign = '';
eOppSign = '';
eFirstSign = '';
eOppFirstSign = '';
eTerm = '';
}
// z3 is f (ie.fx)
var fSign = '+';
var fOppSign = '-';
var fFirstSign = '';
var fOppFirstSign = '-';
if (z3 < 0) {
fSign = '-';
fOppSign = '+';
fFirstSign = '-';
fOppFirstSign = '';
}
var fTerm = String(Math.abs(z3)) + vari;
if (z3 == 1 || z3 == -1)
fTerm = vari;
// z4 is g
var gSign = '+';
var gOppSign = '-';
var gFirstSign = '';
var gOppFirstSign = '-';
if (z4 < 0) {
gSign = '-';
gOppSign = '+';
gFirstSign = '-';
gOppFirstSign = '';
}
var gTerm = String(Math.abs(z4));
if (z4 == 0) {
gSign = '';
gOppSign = '';
gFirstSign = '';
gOppFirstSign = '';
gTerm = '';
}
return [
[hFirstSign + hTerm, dFirstSign + dTerm + eSign + eTerm, fFirstSign + fTerm + gSign + gTerm],
[hFirstSign + hTerm, eFirstSign + eTerm + dSign + dTerm, fFirstSign + fTerm + gSign + gTerm],
[hFirstSign + hTerm, dFirstSign + dTerm + eSign + eTerm, gFirstSign + gTerm + fSign + fTerm],
[hFirstSign + hTerm, eFirstSign + eTerm + dSign + dTerm, gFirstSign + gTerm + fSign + fTerm],
[hOppFirstSign + hTerm, dOppFirstSign + dTerm + eOppSign + eTerm, fFirstSign + fTerm + gSign + gTerm],
[hOppFirstSign + hTerm, eOppFirstSign + eTerm + dOppSign + dTerm, fFirstSign + fTerm + gSign + gTerm],
[hOppFirstSign + hTerm, dOppFirstSign + dTerm + eOppSign + eTerm, gFirstSign + gTerm + fSign + fTerm],
[hOppFirstSign + hTerm, eOppFirstSign + eTerm + dOppSign + dTerm, gFirstSign + gTerm + fSign + fTerm],
[hOppFirstSign + hTerm, dFirstSign + dTerm + eSign + eTerm, fOppFirstSign + fTerm + gOppSign + gTerm],
[hOppFirstSign + hTerm, eFirstSign + eTerm + dSign + dTerm, fOppFirstSign + fTerm + gOppSign + gTerm],
[hOppFirstSign + hTerm, dFirstSign + dTerm + eSign + eTerm, gOppFirstSign + gTerm + fOppSign + fTerm],
[hOppFirstSign + hTerm, eFirstSign + eTerm + dSign + dTerm, gOppFirstSign + gTerm + fOppSign + fTerm],
[hFirstSign + hTerm, dOppFirstSign + dTerm + eOppSign + eTerm, fOppFirstSign + fTerm + gOppSign + gTerm],
[hFirstSign + hTerm, eOppFirstSign + eTerm + dOppSign + dTerm, fOppFirstSign + fTerm + gOppSign + gTerm],
[hFirstSign + hTerm, dOppFirstSign + dTerm + eOppSign + eTerm, gOppFirstSign + gTerm + fOppSign + fTerm],
[hFirstSign + hTerm, eOppFirstSign + eTerm + dOppSign + dTerm, gOppFirstSign + gTerm + fOppSign + fTerm],
]
}
function constructFactorisedForms(factorsArray) {
var returnArray = [];
for (var set = 0; set < factorsArray.length; set++) {
var fac = [factorsArray[set][0], factorsArray[set][1], factorsArray[set][2]];
var facBrac = ['(' + fac[0] + ')', '(' + fac[1] + ')', '(' + fac[2] + ')'];
var bracReqFirst = [false, true, true];
var bracReq = [true, true, true];
for (br = 0; br < fac.length; br++) {
if (fac[br] == '-')
facBrac[br] = '(-1)';
// if the factor doesn't contain + or -, no bracket required
if (fac[br].indexOf('-') == -1 && fac[br].indexOf('+') == -1)
bracReq[br] = false;
}
var first;
var second;
var third;
for (br1 = 0; br1 < 3; br1++) {
first = facBrac[br1];
if (bracReqFirst[br1] == false)
first = fac[br1];
for (br2 = 0; br2 < 3; br2++) {
if (br2 !== br1) {
second = facBrac[br2];
if (bracReq[br2] == false)
second = fac[br2];
for (br3 = 0; br3 < 3; br3++) {
if (br3 !== br1 && br3 !== br2) {
third = facBrac[br3];
if (bracReq[br3] == false)
third = fac[br3];
if (returnArray.indexOf(first + second + third) == -1) {
returnArray.push(first + second + third);
}
if (first == second && second == third && first !== '') {
if (returnArray.indexOf('Math.pow(' + first + ',3)') == -1)
returnArray.push('Math.pow(' + first + ',3)');
}
if (first == second && first !== '') {
if (returnArray.indexOf('Math.pow(' + first + ',2)' + second) == -1)
returnArray.push('Math.pow(' + first + ',2)' + second);
}
if (second == third && second !== '') {
if (returnArray.indexOf(first + 'Math.pow(' + second + ',2)') == -1)
returnArray.push(first + 'Math.pow(' + second + ',2)');
}
}
}
}
}
}
}
return returnArray;
}
function toMathsText(num, num2) {
if (typeof num2 == 'undefined') {
num = Math.abs(num);
if (num == Math.round(num)) {
return String(num);
} else {
// if num is negative?
var frac = decToFrac(num);
return ['frac', String(frac.num), String(frac.denom)];
}
} else {
if (num == 0)
return '0';
var pos = true;
if (num / num2 < 0)
pos = false;
num = Math.abs(num)
num2 = Math.abs(num2);
var divisor = hcf(num, num2);
num = num / divisor;
num2 = num2 / divisor;
if (num2 == 1) {
if (pos == true) {
return String(num);
} else {
return "-" + String(num);
}
} else {
if (pos == true) {
return ['frac', [String(num)], [String(num2)]];
} else {
return ['-', ['frac', [String(num)], [String(num2)]]];
}
}
}
}
function addFracs(num1, denom1, num2, denom2) {
var denom = denom1 * denom2 / hcf(denom1, denom2);
var num = num1 * (denom / denom1) + num2 * (denom / denom2);
//console.log(num,denom);
return toMathsText(num, denom);
}
function simplifyFrac(object, textArrayOrObject) {
var textArray = boolean(textArrayOrObject, true);
var positive = true;
if (object.num / object.denom < 0)
positive = false;
if (object.num == 0) {
if (textArray == true) {
return ["0"];
} else {
return {
num: 0,
denom: 1
};
}
}
while (Math.round(object.num) !== object.num || Math.round(object.denom) !== object.denom) {
object.num = object.num * 10;
object.denom = object.denom * 10;
}
var num = Math.abs(object.num);
var denom = Math.abs(object.denom);
var multOfPi = false;
if (typeof object.multOfPi == 'boolean')
multOfPi = object.multOfPi;
var newNum = num / hcf(num, denom);
var newDenom = denom / hcf(num, denom);
if (textArray == true) { // if returning a textArray
if (multOfPi == false) {
if (newDenom == 1) {
if (positive == true) {
return String(newNum);
} else {
return "-" + String(newNum);
}
} else {
if (positive == true) {
return ['frac', [String(newNum)], [String(newDenom)]]
} else {
return ['-', ['frac', [String(newNum)], [String(newDenom)]]]
}
}
} else {
if (newDenom == 1) {
if (positive == true) {
if (newNum == 1) {
return String.fromCharCode(0x03C0);
} else {
return String(newNum) + String.fromCharCode(0x03C0);
}
} else {
if (newNum == 1) {
return "-" + String.fromCharCode(0x03C0);
} else {
return "-" + String(newNum) + String.fromCharCode(0x03C0);
}
}
} else {
if (positive == true) {
if (newNum == 1) {
return ['frac', [String.fromCharCode(0x03C0)], [String(newDenom)]];
} else {
return ['frac', [String(newNum) + String.fromCharCode(0x03C0)], [String(newDenom)]];
}
} else {
if (newNum == 1) {
return ["-", ['frac', [String.fromCharCode(0x03C0)], [String(newDenom)]]];
} else {
return ["-", ['frac', [String(newNum) + String.fromCharCode(0x03C0)], [String(newDenom)]]];
}
}
}
}
} else { // if returning an object
if (positive == true) {
return {
num: newNum,
denom: newDenom
};
} else {
return {
num: -1 * newNum,
denom: newDenom
};
}
}
}
function simplifyFrac2(frac) {
var positive = true;
if (frac[0] / frac[1] < 0)
positive = false;
if (frac[0] == 0)
return [0, 1];
while (Math.round(frac[0]) !== frac[0] || Math.round(frac[1]) !== frac[1]) {
frac[0] = frac[0] * 10;
frac[1] = frac[1] * 10;
}
var num = Math.abs(frac[0]);
var denom = Math.abs(frac[1]);
var newNum = num / hcf(num, denom);
var newDenom = denom / hcf(num, denom);
if (positive == true) {
return [newNum, newDenom];
} else {
return [-1 * newNum, newDenom];
}
}
function addFracs2(frac1, frac2) {
var denom = frac1[1] * frac2[1] / hcf(frac1[1], frac2[1]);
var num = frac1[0] * (denom / frac1[1]) + frac2[0] * (denom / frac2[1]);
return [num, denom];
}
function simpSurd(c) {
//takes sqrt(c) and returns simplified: a*sqrt(b)
var a,
b,
mathsTextSurd;
for (fac = 1; fac <= c; fac++) {
if (Math.sqrt(c / fac) == Math.round(Math.sqrt(c / fac))) {
a = Math.sqrt(c / fac);
b = fac;
break;
}
}
var aTerm = String(a);
mathsTextSurd = [String(a), ['sqrt', [String(b)]]];
if (a == 1 && b > 1)
mathsTextSurd = [['sqrt', [String(b)]]]
if (b == 1)
mathsTextSurd = [String(a)];
return {
a: a,
b: b,
mathsText: mathsTextSurd
};
}
function primeFactors(num) {
var primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131];
if (num == 1)
return ['1'];
if (num > 131)
return ['']; // too big!
if (primes.indexOf(num) > -1)
return [String(num)];
var primeFactors = [];
do {
for (var i = 0; i < primes.length; i++) {
if (num % primes[i] == 0) {
primeFactors.push(primes[i]);
num = num / primes[i];
break;
}
}
} while (num > 1);
var returnArray = [];
for (var i = 0; i < primeFactors.length; i++) {
var repeated = 1;
for (var j = i + 1; j < primeFactors.length; j++) {
if (primeFactors[i] == primeFactors[j])
repeated++;
}
if (i > 0)
returnArray.push(String.fromCharCode(0x00D7))
returnArray.push(String(primeFactors[i]));
if (repeated > 1) {
returnArray.push(['power', false, [String(repeated)]]);
i += repeated - 1;
}
}
return returnArray;
}
function cuberoot(x) {
var y = Math.pow(Math.abs(x), 1 / 3);
return x < 0 ? -y : y;
}
function solveCubic(a, b, c, d) {
if (Math.abs(a) < 1e-8) { // Quadratic case, ax^2+bx+c=0
a = b;
b = c;
c = d;
if (Math.abs(a) < 1e-8) { // Linear case, ax+b=0
a = b;
b = c;
if (Math.abs(a) < 1e-8) // Degenerate case
return [];
return [-b / a];
}
var D = b * b - 4 * a * c;
if (Math.abs(D) < 1e-8)
return [-b / (2 * a)];
else if (D > 0)
return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)];
return [];
}
// Convert to depressed cubic t^3+pt+q = 0 (subst x = t - b/3a)
var p = (3 * a * c - b * b) / (3 * a * a);
var q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a);
var roots;
if (Math.abs(p) < 1e-8) { // p = 0 -> t^3 = -q -> t = -q^1/3
roots = [cuberoot(-q)];
} else if (Math.abs(q) < 1e-8) { // q = 0 -> t^3 + pt = 0 -> t(t^2+p)=0
roots = [0].concat(p < 0 ? [Math.sqrt(-p), -Math.sqrt(-p)] : []);
} else {
var D = q * q / 4 + p * p * p / 27;
if (Math.abs(D) < 1e-8) { // D = 0 -> two roots
roots = [-1.5 * q / p, 3 * q / p];
} else if (D > 0) { // Only one real root
var u = cuberoot(-q / 2 - Math.sqrt(D));
roots = [u - p / (3 * u)];
} else { // D < 0, three roots, but needs to use complex numbers/trigonometric solution
var u = 2 * Math.sqrt(-p / 3);
var t = Math.acos(3 * q / p / u) / 3; // D < 0 implies p < 0 and acos argument in [-1..1]
var k = 2 * Math.PI / 3;
roots = [u * Math.cos(t), u * Math.cos(t - k), u * Math.cos(t - 2 * k)];
}
}
// Convert back from depressed cubic
for (var i = 0; i < roots.length; i++)
roots[i] -= b / (3 * a);
return roots;
}
function hitAndAlign(object, firstArray, secondArray, offsetX, offsetY) {
//this function aligns a dragged object to the x and y position of another object
//first array is draggable buttons
//second array is the things they are going to hit
//this works for buttons
if (typeof offsetX == 'undefined') {
offsetX = 0
} else {
offsetX = Number(offsetX)
}
if (typeof offsetY == 'undefined') {
offsetY = 0
} else {
offsetY = Number(offsetY)
}
var alreadyHitMe = false
for (var j = 0; j < firstArray.length; j++) {
if (hitTestTwoObjects(object, firstArray[j]) == true && object !== firstArray[j]) {
alreadyHitMe = true
}
}
for (var j = 0; j < secondArray.length; j++) {
if (hitTestTwoObjects(object, secondArray[j]) == true && alreadyHitMe == false) {
var buttonArray = eval(String(taskTag) + 'button')
var buttonArrayData = eval(String(taskTag) + 'buttonData')
var butNum = buttonArray.indexOf(object)
var butNum2 = buttonArray.indexOf(secondArray[j])
buttonArrayData[butNum][100] = buttonArrayData[butNum2][100] + Number(offsetX)
buttonArrayData[butNum][101] = buttonArrayData[butNum2][101] + Number(offsetY)
resize()
}
}
}
function hitAndAlignImages(object, firstArray, secondArray, offsetX, offsetY) {
if (typeof offsetX == 'undefined') {
offsetX = 0
} else {
offsetX = Number(offsetX)
}
if (typeof offsetY == 'undefined') {
offsetY = 0
} else {
offsetY = Number(offsetY)
}
var alreadyHitMe = false
for (var j = 0; j < firstArray.length; j++) {
if (hitTestTwoObjects(object, firstArray[j]) == true && object !== firstArray[j]) {
alreadyHitMe = true
}
}
for (var j = 0; j < secondArray.length; j++) {
if (hitTestTwoObjects(object, secondArray[j]) == true && alreadyHitMe == false) {
var buttonArray = eval(String(taskTag) + 'imageCanvas')
var buttonArrayData = eval(String(taskTag) + 'imageCanvasData')
var butNum = buttonArray.indexOf(object)
var butNum2 = buttonArray.indexOf(secondArray[j])
buttonArrayData[butNum][100] = buttonArrayData[butNum2][100] + Number(offsetX)
buttonArrayData[butNum][101] = buttonArrayData[butNum2][101] + Number(offsetY)
resize()
}
}
}
function shuffleImagePositions(arrayOfObjects) {
var buttonArray = eval(String(taskTag) + 'imageCanvas')
var buttonArrayData = eval(String(taskTag) + 'imageCanvasData')
currentX = []
currentY = []
//get current x and y positions of all objects in array
for (n201i = 0; n201i < arrayOfObjects.length; n201i++) {
var object = arrayOfObjects[n201i]
var butNum = buttonArray.indexOf(object)
currentX[n201i] = buttonArrayData[butNum][100]
currentY[n201i] = buttonArrayData[butNum][101]
}
var mixedNumbers = []
for (n201i = 0; n201i < arrayOfObjects.length; n201i++) {
mixedNumbers[n201i] = n201i
}
mixedNumbers = shuffleArray(mixedNumbers)
for (n201i = 0; n201i < arrayOfObjects.length; n201i++) {
//arrayOfObjects = shuffleArray(arrayOfObjects)
//choose random object from array
var objectN = arrayOfObjects[mixedNumbers[n201i]]
var butNumN = buttonArray.indexOf(objectN)
buttonArrayData[butNumN][100] = currentX[n201i]
buttonArrayData[butNumN][101] = currentY[n201i]
//arrayOfObjects = arrayOfObjects.splice(ran,1)
}
resize()
}
function shuffleObjectPositions(arrayOfObjects) {
var buttonArray = eval(String(taskTag) + 'button')
var buttonArrayData = eval(String(taskTag) + 'buttonData')
currentX = []
currentY = []
//get current x and y positions of all objects in array
for (n201i = 0; n201i < arrayOfObjects.length; n201i++) {
var object = arrayOfObjects[n201i]
var butNum = buttonArray.indexOf(object)
currentX[n201i] = buttonArrayData[butNum][100]
currentY[n201i] = buttonArrayData[butNum][101]
}
var mixedNumbers = []
for (n201i = 0; n201i < arrayOfObjects.length; n201i++) {
mixedNumbers[n201i] = n201i
}
mixedNumbers = shuffleArray(mixedNumbers)
for (n201i = 0; n201i < arrayOfObjects.length; n201i++) {
//arrayOfObjects = shuffleArray(arrayOfObjects)
//choose random object from array
var objectN = arrayOfObjects[mixedNumbers[n201i]]
var butNumN = buttonArray.indexOf(objectN)
buttonArrayData[butNumN][100] = currentX[n201i]
buttonArrayData[butNumN][101] = currentY[n201i]
//arrayOfObjects = arrayOfObjects.splice(ran,1)
}
resize()
}
// array sorting function for key values
// use is: array = array.sort(keySort('/*key*/'));
function keySort(key, desc) {
return function (a, b) {
return desc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]);
}
}
function arrayMin(array) {
var min = array[0];
for (var i = 1; i < array.length; i++) {
min = Math.min(min, array[i]);
}
return min;
}
function arrayMax(array) {
var max = array[0];
for (var i = 1; i < array.length; i++) {
max = Math.max(max, array[i]);
}
return max;
}
function cloneArray(array) {
var newArray = [];
for (var i = 0; i < array.length; i++) {
newArray[i] = array[i].slice(0);
}
return newArray
}
function hexToRgb(color) {
if (color.indexOf('rgba') > -1) { // if an rgba string
var result = color.substring(5, color.length - 1).replace(/ /g, '').split(',');
return result ? {
r: parseInt(result[0]),
g: parseInt(result[1]),
b: parseInt(result[2])
}
: null;
} else if (color.indexOf('rgb') > -1) { // if an rgb string
var result = color.substring(5, color.length - 1).replace(/ /g, '').split(',');
return result ? {
r: parseInt(result[0]),
g: parseInt(result[1]),
b: parseInt(result[2])
}
: null;
} else if (color.indexOf('#') > -1) { // if a hex string
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
color = color.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null;
} else {
return color;
}
}
function colorA(color, alpha) {
var colorRGB = hexToRgb(color);
return "rgba(" + colorRGB.r + "," + colorRGB.g + "," + colorRGB.b + "," + alpha + ")";
}
function invertColor(color) {
if (/^(#)((?:[A-Fa-f0-9]{3}){1,2})$/i.test(color) == true)
color = hexToRgb(color); // if hex, change to rgba
color.r = 255 - color.r;
color.g = 255 - color.g;
color.b = 255 - color.b;
var a = 1;
if (typeof color.a == 'number')
a = color.a;
return "rgba(" + color.r + "," + color.g + "," + color.b + "," + a + ")";
}
function getShades(color, invert) {
var invert = boolean(invert, false);
if (invert == true) {
var color2 = hexToRgb(invertColor(color));
} else {
var color2 = hexToRgb(color);
}
var r = color2.r;
var g = color2.g;
var b = color2.b;
//var rgb = [];
var hex = [];
if (r == 0 && g == 0 && b == 0) { // if the color is black, switch it to white
r = 255;
g = 255;
b = 255;
}
// get brightest shade of rgb
if (Math.max(r, g, b) < 255) {
var m = 255 / Math.max(r, g, b);
r = r * m;
g = g * m;
b = b * m;
}
for (var i = 0; i < 16; i++) {
var r2 = roundToNearest((i / 15) * r, 1);
var g2 = roundToNearest((i / 15) * g, 1);
var b2 = roundToNearest((i / 15) * b, 1);
//rgb[i] = "rgb("+String(r2)+","+String(g2)+","+String(b2)+")";
var r3 = decToHex2Digits(r2);
var g3 = decToHex2Digits(g2);
var b3 = decToHex2Digits(b2);
hex[i] = "#" + r3 + g3 + b3;
}
return hex;
}
function decToHex2Digits(num) {
num2 = num.toString(16);
if (num2.length == 1) {
num2 = "0" + num2;
} else if (num2.indexOf('.') == 1) {
num2 = "0" + num2.slice(0, 1);
} else {
num2 = num2.slice(0, 2);
}
return num2;
}
function testShades(ctx, color, invert) {
var colors = getShades(color, invert);
var ctx = draw.drawctx;
ctx.strokeStyle = '#000';
for (var i = 0; i < 16; i++) {
ctx.fillStyle = colors[i];
ctx.fillRect(50 + i * 50, 50, 50, 50);
ctx.strokeRect(50 + i * 50, 50, 50, 50);
}
}
function getColor(r, g, b, a) {
if (un(a))
a = 1;
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
}
function getScaleColor(value,max,saturation) {
if (un(saturation)) saturation = 1;
var perc = Math.round((value / max) * 100);
var h = Math.floor(perc * 1.2);
var s = saturation;
var v = 1;
return hsv2rgb(h, s, v);
}
function hsv2rgb (h, s, v) {
// adapted from http://schinckel.net/2012/01/10/hsv-to-rgb-in-javascript/
var rgb, i, data = [];
if (s === 0) {
rgb = [v,v,v];
} else {
h = h / 60;
i = Math.floor(h);
data = [v*(1-s), v*(1-s*(h-i)), v*(1-s*(1-(h-i)))];
switch(i) {
case 0:
rgb = [v, data[2], data[0]];
break;
case 1:
rgb = [data[1], v, data[0]];
break;
case 2:
rgb = [data[0], v, data[2]];
break;
case 3:
rgb = [data[0], data[1], v];
break;
case 4:
rgb = [data[2], data[0], v];
break;
default:
rgb = [v, data[0], data[1]];
break;
}
}
return '#' + rgb.map(function(x){
return ("0" + Math.round(x*255).toString(16)).slice(-2);
}).join('');
};
function collapseLines(lineArray) {
// collapses lines in an array such as:
// [ [[x1,y1],[x2,y2]], [[x3,y3],[x4,y4]], [[x5,y5],[x6,y6]] ]
do {
var joinFound = false;
for (var i = 0; i < lineArray.length; i++) {
if (joinFound == false) {
for (var j = i + 1; j < lineArray.length; j++) {
var x1 = lineArray[i][0][0];
var y1 = lineArray[i][0][1];
var x2 = lineArray[i][1][0];
var y2 = lineArray[i][1][1];
var x3 = lineArray[j][0][0];
var y3 = lineArray[j][0][1];
var x4 = lineArray[j][1][0];
var y4 = lineArray[j][1][1];
var m1 = (y2 - y1) / (x2 - x1);
var m2 = (y4 - y3) / (x4 - x3);
// if gradients are equal and point from line 1 is on line 2
//console.log('x1,y1,x2,y2,x3,y3,x4,y4');
//console.log('grad/grad: ',mMax/mMin);
//console.log('pointOnLine: ',isPointOnLine([x1,y1],[x3,y3],[x4,y4],3.5));
if (((Math.abs(m1) == 'Infinity' && Math.abs(m2) == 'Infinity') || Math.abs(m1 - m2) < 0.0001) && isPointOnLine([x1, y1], [x3, y3], [x4, y4], 0.25) == true) {
// if one of the points is between the two points on the other line
if ((x1 >= Math.min(x3, x4) && x1 <= Math.max(x3, x4) && y1 >= Math.min(y3, y4) && y1 <= Math.max(y3, y4)) || (x2 >= Math.min(x3, x4) && x2 <= Math.max(x3, x4) && y2 >= Math.min(y3, y4) && y2 <= Math.max(y3, y4)) || (x3 >= Math.min(x1, x2) && x3 <= Math.max(x1, x2) && y3 >= Math.min(y1, y2) && y3 <= Math.max(y1, y2)) || (x4 >= Math.min(x1, x2) && x4 <= Math.max(x1, x2) && y4 >= Math.min(y1, y2) && y4 <= Math.max(y1, y2))) {
var xMin = Math.min(x1, x2, x3, x4);
var xMax = Math.max(x1, x2, x3, x4);
if (xMin == x1)
var yMin = y1;
if (xMin == x2)
var yMin = y2;
if (xMin == x3)
var yMin = y3;
if (xMin == x4)
var yMin = y4;
if (xMax == x1)
var yMax = y1;
if (xMax == x2)
var yMax = y2;
if (xMax == x3)
var yMax = y3;
if (xMax == x4)
var yMax = y4;
if (xMin == xMax) {
var yMin = Math.min(y1, y2, y3, y4);
var yMax = Math.max(y1, y2, y3, y4);
}
joinFound = true;
lineArray[i][0] = [xMin, yMin];
lineArray[i][1] = [xMax, yMax];
lineArray.splice(j, 1);
break;
}
}
}
}
}
} while (joinFound == true);
return lineArray;
}
function drawBarChart(object) {
/* EXAMPLE USAGE:
drawBarChart({
ctx:j324buttonctx[0],
left:200,
top:240,
width:500,
height:370,
data:[{value:['0'], freq:17},{value:['1'], freq:6},{value:['2'], freq:3},{value:['3'], freq:1},{value:['4'], freq:2},{value:['5'], freq:0},{value:['6'], freq:1},
],
barWidth:[2,1], // bar width to gap width ratio
barColor:'#FCF',
barOutlineColor:'#000',
barOutlineWidth:4,
xLabel:['number of days'],
yLabel:['frequency'],
title:['Number of days absent in a month for a class'],
titlePos:[450,260,200,90],
yMinor:{show:true,step:1,color:'#AAA',width:1,dash:[3,5]},
yMajor:{show:true,step:2,color:'#888',width:2,dash:[]},
yMin:0,
yMax:18,
});
*/
var ctx = object.ctx || object.context;
var left = object.left;
var top = object.top;
var width = object.width;
var height = object.height;
var data = object.data;
var barWidth = object.barWidth || [2, 1];
var barColor = object.barColor || '#FCF';
var barOutlineColor = object.barOutlineColor || '#000';
var barOutlineWidth = object.barOutlineWidth || 2;
var xLabel = object.xLabel || [''];
var yLabel = object.yLabel || [''];
var title = object.title || [''];
var titlePos = object.titlePos || [left + 0.5 * width, top + 0.03 * height, 0.4 * width, 0.2 * height];
if (typeof object.yMinor == 'object') {
var yMinorShow = boolean(object.yMinor.show, false);
var yMinorStep = object.yMinor.step || 1;
var yMinorColor = object.yMinor.color || '#AAA';
var yMinorWidth = object.yMinor.width || 1;
var yMinorDash = object.yMinor.dash || [];
} else {
var yMinorShow = false;
var yMinorStep = 1;
var yMinorColor = '#AAA';
var yMinorWidth = 1;
var yMinorDash = [];
}
if (typeof object.yMajor == 'object') {
var yMajorShow = boolean(object.yMajor.show, false);
var yMajorStep = object.yMajor.step || 1;
var yMajorColor = object.yMajor.color || '#888';
var yMajorWidth = object.yMajor.width || 2;
var yMajorDash = object.yMajor.dash || [];
} else {
var yMajorShow = false;
var yMajorStep = 1;
var yMajorColor = '#888';
var yMajorWidth = 2;
var yMajorDash = [];
}
if (object.yMin == 'number') {
var yMin = object.yMin;
} else {
var yMin = 0;
}
if (object.yMax == 'number') {
var yMax = object.yMax;
} else {
var yMax = 0;
for (var i = 0; i < data.length; i++) {
yMax = Math.max(yMax, data[i].freq + 1);
}
var steps = Math.floor(yMax / yMajorStep);
while (yMax % yMajorStep > 0 || yMax / yMajorStep < steps + 1) {
yMax++;
}
}
// work out the spacing for minor and major steps
var yMinorSpacing = (height * yMinorStep) / (yMax - yMin);
var yMajorSpacing = (height * yMajorStep) / (yMax - yMin);
// work out the coordinates of the origin
var x0 = left;
var y0 = top + (yMax * height) / (yMax - yMin);
// work out the actual display position of the origin (ie. at the edge if it is off the grid)
var x0DisplayPos = left;
var y0DisplayPos = top + height;
ctx.save();
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
if (yMinorShow == true) {
ctx.beginPath();
ctx.strokeStyle = yMinorColor;
ctx.lineWidth = yMinorWidth;
if (!ctx.setLineDash) {
ctx.setLineDash = function () {}
}
ctx.setLineDash(yMinorDash);
var yAxisPoint = y0 - yMinorSpacing;
while (yAxisPoint > top) {
if (yAxisPoint < top + height) {
ctx.moveTo(left, yAxisPoint);
ctx.lineTo(left + width, yAxisPoint);
}
yAxisPoint -= yMinorSpacing;
}
var yAxisPoint = y0 + yMinorSpacing;
while (yAxisPoint <= top + height) {
if (yAxisPoint >= top) {
ctx.moveTo(left, yAxisPoint);
ctx.lineTo(left + width, yAxisPoint);
}
yAxisPoint += yMinorSpacing;
}
ctx.stroke();
}
if (yMajorShow == true) {
ctx.beginPath();
ctx.strokeStyle = yMajorColor;
ctx.lineWidth = yMajorWidth;
if (!ctx.setLineDash) {
ctx.setLineDash = function () {}
}
ctx.setLineDash(yMajorDash);
var yAxisPoint = y0 - yMajorSpacing;
while (yAxisPoint > top - 0.00001) {
if (yAxisPoint < (top + height + 0.00001)) {
ctx.moveTo(left, yAxisPoint);
ctx.lineTo(left + width, yAxisPoint);
}
yAxisPoint -= yMajorSpacing;
}
var yAxisPoint = y0 + yMajorSpacing;
while (yAxisPoint < top + height + 0.00001) {
if (yAxisPoint > top - 0.00001) {
ctx.moveTo(left, yAxisPoint);
ctx.lineTo(left + width, yAxisPoint);
}
yAxisPoint += yMajorSpacing;
}
ctx.stroke();
}
// draw axes
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
if (!ctx.setLineDash) {
ctx.setLineDash = function () {}
}
ctx.setLineDash([]);
// if neccesary, draw x-Axis
if (y0 >= top && y0 <= top + height) {
ctx.moveTo(left, y0);
ctx.lineTo(left + width, y0);
}
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
// if neccesary, draw y-Axis
if (x0 >= left && x0 <= left + width) {
ctx.moveTo(x0, top);
ctx.lineTo(x0, top + height);
}
ctx.stroke();
// draw yAxes numbers
ctx.font = '24px Arial';
ctx.textAlign = "center";
ctx.textBaseline = "top";
ctx.lineWidth = 2;
ctx.strokeStyle = yMajorColor;
ctx.textBaseline = "middle";
ctx.textAlign = "right";
ctx.fillStyle = '#000';
var fontSize = 24;
// positive y numbers
var yAxisPoint = y0;
var major = 0;
var placeValue = Math.pow(10, Math.floor(Math.log(yMajorStep) / Math.log(10)));
while (yAxisPoint >= top - 0.0001) {
if (yAxisPoint <= top + height) {
var axisValue = Number(roundSF(major * yMajorStep, 5));
var textWidth = ctx.measureText(String(axisValue)).width
ctx.clearRect(x0DisplayPos - textWidth - 16, yAxisPoint - fontSize * 0.5, textWidth + 3, fontSize);
ctx.beginPath();
ctx.moveTo(x0DisplayPos, yAxisPoint);
ctx.lineTo(x0DisplayPos - 8, yAxisPoint);
ctx.stroke();
wrapText(ctx, String(axisValue), x0DisplayPos - 7, yAxisPoint - 2, 50, 40, fontSize + 'px Arial');
}
major += 1;
yAxisPoint -= yMajorSpacing;
}
// negative y numbers
var yAxisPoint = y0 + yMajorSpacing;
var major = -1;
while (yAxisPoint <= top + height + 0.0001) {
if (yAxisPoint >= top) {
var axisValue = Number(roundSF(major * yMajorStep, 5));
var textWidth = ctx.measureText(String(axisValue)).width
ctx.clearRect(x0DisplayPos - textWidth - 16, yAxisPoint - fontSize * 0.5, textWidth + 3, fontSize);
ctx.beginPath();
ctx.moveTo(x0DisplayPos, yAxisPoint);
ctx.lineTo(x0DisplayPos - 8, yAxisPoint);
ctx.stroke();
wrapText(ctx, String(axisValue), x0DisplayPos - 7, yAxisPoint - 2, 50, 40, fontSize + 'px Arial');
}
major -= 1;
yAxisPoint += yMajorSpacing;
}
// draw axes
ctx.strokeStyle = '#000';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(left, top);
ctx.lineTo(left, top + height);
ctx.lineTo(left + width, top + height);
ctx.stroke();
var ySpacing = height / (yMax - yMin);
var xSpacing = width / (data.length * barWidth[0] + (data.length + 1) * barWidth[1]);
var l = [];
ctx.beginPath();
for (var i = 0; i < data.length; i++) {
l[i] = left + i * barWidth[0] * xSpacing + (i + 1) * barWidth[1] * xSpacing;
drawMathsText(ctx, data[i].value, 24, l[i] + 0.5 * xSpacing * barWidth[0], top + height + 5, false, [], 'center', 'top', '#000');
//ctx.moveTo(l[i]+0.5*xSpacing*barWidth[0],top+height);
//ctx.lineTo(l[i]+0.5*xSpacing*barWidth[0],top+height+5);
}
ctx.stroke();
ctx.lineWidth = barOutlineWidth;
ctx.strokeStyle = barOutlineColor;
ctx.fillStyle = barColor;
ctx.beginPath();
for (var i = 0; i < data.length; i++) {
var t = top + height - ySpacing * data[i].freq;
var h = ySpacing * data[i].freq;
ctx.fillRect(l[i], t, xSpacing * barWidth[0], h);
ctx.strokeRect(l[i], t, xSpacing * barWidth[0], h);
}
if (arraysEqual(xLabel, ['']) == false) {
text({
context: ctx,
textArray: ['<<align:center>><<font:Arial>><<fontSize:24>><<color:#000>>' + xLabel],
left: left + width - 200,
width: 200,
top: top + height + 30,
});
}
if (arraysEqual(yLabel, ['']) == false) {
text({
context: ctx,
textArray: ['<<align:right>><<font:Arial>><<fontSize:24>><<color:#000>>' + yLabel],
left: left - 50 - 200,
width: 200,
top: top + height * 0.03,
});
}
if (arraysEqual(title, ['']) == false) {
text({
context: ctx,
textArray: ['<<align:center>><<font:Arial>><<fontSize:24>><<color:#000>>' + title],
left: titlePos[0],
top: titlePos[1],
width: titlePos[2],
maxHeight: titlePos[3],
box: {
color: '#FFC',
dash: [],
border: '#000',
borderWidth: 3,
radius: 5,
pos: [titlePos[0] - 10, titlePos[1] - 5, titlePos[2] + 12, titlePos[3] + 10]
}
// box.pos may need tweaking when text has been improved
});
}
}
function isNode(o) { //Returns true if it is a DOM node
return (
typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string");
}
function isElement(o) { //Returns true if it is a DOM element
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string");
}
/***************************/
/* VECTOR GEOMETRY */
/***************************/
function getVectorAB(a, b) {
return [b[0] - a[0], b[1] - a[1]];
}
function getUnitVector(vector) {
var mag = Math.sqrt(Math.pow(vector[0], 2) + Math.pow(vector[1], 2));
return [vector[0] / mag, vector[1] / mag];
}
function setVectorMag(vector, mag) {
var unit = getUnitVector(vector);
return [unit[0] * mag, unit[1] * mag];
}
function getDist(a, b) {
return Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2));
}
function getPerpVector(vector, dir) {
if (un(dir))
dir = 1;
if (dir == 1) {
return [-1 * vector[1], vector[0]];
} else {
return [vector[1], -1 * vector[0]];
}
}
function pointAddVector(point, vector, scalarMult) {
if (un(scalarMult))
scalarMult = 1;
return [point[0] + scalarMult * vector[0], point[1] + scalarMult * vector[1]];
}
function getVectorMag(vector) {
return Math.sqrt(Math.pow(vector[0], 2) + Math.pow(vector[1], 2));
}
function getVectorAngle(vector) {
var angle = Math.atan(vector[1] / vector[0]);
if (vector[0] >= 0 && vector[1] >= 0)
return angle;
if (vector[0] >= 0 && vector[1] < 0)
return angle + 2 * Math.PI;
if (vector[0] < 0)
return angle + Math.PI;
}
function angleToVector(angle, mag) {
if (un(mag)) mag = 1;
return [mag*Math.cos(angle),mag*Math.sin(angle)];
}
function rotateVector(vector, angle) {
var mag = Math.sqrt(Math.pow(vector[0], 2) + Math.pow(vector[1], 2));
var theta = getVectorAngle(vector) + angle;
return [mag * Math.cos(theta), mag * Math.sin(theta)];
}
function getVectorLinesIntersection(p1, v1, p2, v2) {
var lambda = (p1[1] * v2[0] + p2[0] * v2[1] - p1[0] * v2[1] - p2[1] * v2[0]) / (v1[0] * v2[1] - v1[1] * v2[0]);
return pointAddVector(p1, v1, lambda);
}
function getMidpoint(p1, p2) {
return [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
}
function getFootOfPerp(p, v, q) { // foot of perp from point q to line p + kv
var lambda = (q[0] * v[0] + q[1] * v[1] - p[0] * v[0] - p[1] * v[1]) / (v[0] * v[0] + v[1] * v[1]);
return pointAddVector(p, v, lambda);
}
function getPerpDist(p, v, q) { // shortest dist from point q to line p + kv
var foot = getFootOfPerp(p, v, q);
return getDist(q, foot);
}
var vector = { // generalised 3d versions
getVectorAB: function (a, b) {
if (a.length !== b.length) return null;
var v = [];
for (var i = 0; i < a.length; i++) v[i] = b[i] - a[i];
return v;
},
scalarMult: function (a, k) {
var r = [];
for (var i = 0; i < a.length; i++)
r[i] = a[i] * k;
return r;
},
dotProduct: function (a, b) {
if (a.length !== b.length)
return null;
var p = 0;
for (var i = 0; i < a.length; i++)
p += b[i] * a[i];
return p;
},
crossProduct: function (a, b) {
if (a.length !== 3 || b.length !== 3)
return null;
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
];
},
getMagnitude: function (a) {
var m = 0;
for (var i = 0; i < a.length; i++)
m += a[i] * a[i];
return Math.sqrt(m);
},
getUnitVector: function (a) {
var u = [];
var m = vector.getMagnitude(a);
for (var i = 0; i < a.length; i++)
u[i] = a[i] / m;
return u;
},
setMagnitude: function (a, m) {
var b = [];
var unitVector = vector.getUnitVector(a);
for (var i = 0; i < a.length; i++)
b[i] = unitVector[i] * m;
return b;
},
addVectors: function (a, b, k) {
if (a.length !== b.length)
return null;
if (un(k))
k = 1;
var r = [];
for (var i = 0; i < a.length; i++)
r[i] = a[i] + b[i] * k;
return r;
},
getAngleBetweenVectors: function (a, b) {
if (a.length !== b.length)
return null;
var dot = vector.dotProduct(a, b);
var mag1 = vector.getMagnitude(a);
var mag2 = vector.getMagnitude(b);
return Math.acos(dot / (mag1 * mag2));
}
};
/***************************/
/* POLYGONS */
/***************************/
function drawPolygon(obj) {
// required
var ctx = obj.ctx;
var points = obj.points;
// optional
var fillColor = obj.fillColor || obj.fillStyle || false;
var lineColor = obj.lineColor || obj.color || obj.strokeStyle || false;
var lineWidth = obj.lineWidth || obj.thickness || false;
var closed = boolean(obj.closed, true);
var lineDecoration = obj.lineDecoration || [];
var angles = obj.angles || [];
var outerAngles = obj.outerAngles || [];
var exteriorAngles = obj.exteriorAngles || [];
var clockwise = boolean(obj.clockwise, true)
var sf = obj.sf || 1;
var calcTextSnapPos = boolean(obj.calcTextSnapPos, false)
//console.log(ctx,points,lineColor,lineWidth,closed);
var angleLabelPos = [];
var outerAngleLabelPos = [];
var exteriorAngleLabelPos = [];
var prismPoints = [];
var textSnapPos = [];
ctx.save();
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.beginPath();
if (!un(obj.solidType)) {
if (obj.solidType == 'prism') {
var prismVector = obj.prismVector || [40, -40];
var vectorAngle = getVectorAngle(prismVector);
var points2 = [];
var points2Vis = [];
var angles2 = [];
for (var p = 0; p < points.length; p++) {
points2[p] = pointAddVector(points[p], prismVector);
prismPoints[p] = points2[p];
var prev = p - 1;
if (prev < 0)
prev = obj.points.length - 1;
var next = p + 1;
if (next > obj.points.length - 1)
next = 0;
angles2[p] = [
posToAngle(obj.points[prev][0], obj.points[prev][1], obj.points[p][0], obj.points[p][1]),
posToAngle(obj.points[next][0], obj.points[next][1], obj.points[p][0], obj.points[p][1])
];
if (anglesInOrder(angles2[p][1], vectorAngle, angles2[p][0]) == true) {
points2Vis[p] = false;
} else {
points2Vis[p] = true;
}
}
if (closed == true && fillColor !== false && fillColor !== 'none') {
for (var p = 0; p < points.length; p++) { // fill polygons
var next = (p + 1) % (points.length);
ctx.moveTo(points[p][0], points[p][1]);
ctx.lineTo(points2[p][0], points2[p][1]);
ctx.lineTo(points2[next][0], points2[next][1]);
ctx.lineTo(points[next][0], points[next][1]);
ctx.closePath();
ctx.fillStyle = fillColor;
ctx.fill();
}
}
for (var p = 0; p < points.length; p++) { // draw lines
ctx.save();
if (points2Vis[p] == false || points2Vis[(p + 1) % (points.length)] == false) {
if (obj.solidShowAllLines == false) continue;
ctx.strokeStyle = getShades(ctx.strokeStyle)[12];
}
ctx.beginPath();
ctx.moveTo(points2[p][0], points2[p][1]);
ctx.lineTo(points2[(p + 1) % (points.length)][0], points2[(p + 1) % (points.length)][1]);
ctx.stroke();
ctx.restore();
}
for (var p = 0; p < points.length; p++) { // draw lines
ctx.save();
if (points2Vis[p] == false) {
if (obj.solidShowAllLines == false) continue;
ctx.strokeStyle = getShades(ctx.strokeStyle)[12];
}
ctx.beginPath();
ctx.moveTo(points[p][0], points[p][1]);
ctx.lineTo(points2[p][0], points2[p][1]);
ctx.stroke();
ctx.restore();
}
}
}
if (closed == true && fillColor !== false && fillColor !== 'none') {
ctx.moveTo(points[0][0], points[0][1]);
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
ctx.closePath();
ctx.fillStyle = fillColor;
ctx.fill();
}
for (var i = 0; i < points.length; i++) {
var p1 = i == 0 ? points.last() : points[i-1];
var p2 = points[i];
var p3 = points[(i+1)%points.length];
var a1 = getAngleFromAToB(p2,p1);
var a2 = getAngleFromAToB(p2,p3);
if (a2 > a1) {
var a3 = (a1+a2)/2;
} else {
var a3 = (a1+a2+2*Math.PI)/2;
while (a3 > 2*Math.PI) {
a3 -= 2*Math.PI;
}
}
var n = Math.PI/8;
var align = [0,0];
if (a3 < n || a3 >= 15*n) {
align = [-1,0];
} else if (a3 < 3*n) {
align = [-1,-1];
} else if (a3 < 5*n) {
align = [0,-1];
} else if (a3 < 7*n) {
align = [1,-1];
} else if (a3 < 9*n) {
align = [1,0];
} else if (a3 < 11*n) {
align = [1,1];
} else if (a3 < 13*n) {
align = [0,1];
} else if (a3 < 15*n) {
align = [-1,1];
}
var vector = angleToVector(a3,5);
var labelPos = pointAddVector(p2,vector);
if (calcTextSnapPos == true) textSnapPos.push({type:'polygonVertex',pos:labelPos,align:align,angle:a3,vertex:i,polygon:obj});
// side labels
var mid = midpoint(p2[0],p2[1],p3[0],p3[1]);
var ang = a2-0.5*Math.PI;
while (ang < 0) ang += 2*Math.PI;
var align = [0,0];
if (ang < n || ang >= 15*n) {
align = [-1,0];
} else if (ang < 3*n) {
align = [-1,-1];
} else if (ang < 5*n) {
align = [0,-1];
} else if (ang < 7*n) {
align = [1,-1];
} else if (ang < 9*n) {
align = [1,0];
} else if (ang < 11*n) {
align = [1,1];
} else if (ang < 13*n) {
align = [0,1];
} else if (ang < 15*n) {
align = [-1,1];
}
var margin = align.indexOf(0) > -1 ? 10 : 5;
var vector = angleToVector(ang,margin);
var labelPos = pointAddVector(mid,vector);
if (calcTextSnapPos == true) textSnapPos.push({type:'polygonSide',pos:labelPos,align:align,angle:ang,vertex:i,polygon:obj});
}
for (var i = 0; i < points.length; i++) {
if (typeof angles[i] == 'object' && angles[i] !== null) {
angle = clone(angles[i]);
} else {
continue;
angle = {
labelMeasure: true,
measureLabelOnly: true,
measureOnly: true
};
}
angle.ctx = ctx;
angle.b = points[i];
if (clockwise == false) {
angle.a = points[(i + 1) % (points.length)];
if (i == 0) {
angle.c = points[points.length - 1];
} else {
angle.c = points[i - 1];
}
} else {
angle.c = points[(i + 1) % (points.length)];
if (i == 0) {
angle.a = points[points.length - 1];
} else {
angle.a = points[i - 1];
}
}
angle.drawLines = false;
if (typeof angle.lineWidth == 'undefined')
angle.lineWidth = ctx.lineWidth;
if (typeof angle.lineColor == 'undefined')
angle.lineColor = ctx.lineWidth;
if (typeof angle.labelColor == 'undefined')
angle.labelColor = ctx.strokeStyle;
angleLabelPos[i] = drawAngle(angle);
}
if (obj.anglesMode == 'outer' && (un(obj.solidType) || obj.solidType !== 'prism')) {
for (var i = 0; i < outerAngles.length; i++) {
if (typeof outerAngles[i] == 'object' && outerAngles[i] !== null) {
outerAngles[i].ctx = ctx;
outerAngles[i].b = points[i];
if (clockwise == true) {
outerAngles[i].a = points[(i + 1) % (points.length)];
if (i == 0) {
outerAngles[i].c = points[points.length - 1];
} else {
outerAngles[i].c = points[i - 1];
}
} else {
outerAngles[i].c = points[(i + 1) % (points.length)];
if (i == 0) {
outerAngles[i].a = points[points.length - 1];
} else {
outerAngles[i].a = points[i - 1];
}
}
outerAngles[i].drawLines = false;
if (typeof outerAngles[i].lineWidth == 'undefined')
outerAngles[i].lineWidth = ctx.lineWidth;
if (typeof outerAngles[i].lineColor == 'undefined')
outerAngles[i].lineColor = ctx.lineWidth;
if (typeof outerAngles[i].labelColor == 'undefined')
outerAngles[i].labelColor = ctx.strokeStyle;
outerAngleLabelPos[i] = drawAngle(outerAngles[i]);
}
}
}
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
if (lineColor !== false) {
ctx.strokeStyle = lineColor;
}
if (lineWidth !== false) {
ctx.lineWidth = lineWidth;
}
if (obj.anglesMode == 'exterior' && (un(obj.solidType) || obj.solidType !== 'prism')) {
for (var i = 0; i < exteriorAngles.length; i++) {
if (typeof exteriorAngles[i] == 'object') {
exteriorAngleLabelPos[i] = [];
var ext = exteriorAngles[i];
if (!un(ext.a1) && ext.a1 !== null) {
ext.a1.ctx = ctx;
ext.a1.c = points[(i + 1) % (points.length)];
ext.a1.b = points[i];
ext.a1.a = ext.line2.pos;
ext.a1.drawLines = false;
if (un(ext.a1.lineWidth))
ext.a1.lineWidth = ctx.lineWidth;
if (un(ext.a1.lineColor))
ext.a1.lineColor = ctx.lineWidth;
if (un(ext.a1.labelColor))
ext.a1.labelColor = ctx.strokeStyle;
exteriorAngleLabelPos[i][0] = drawAngle(ext.a1);
}
if (!un(ext.a2) && ext.a2 !== null) {
ext.a2.ctx = ctx;
ext.a2.c = ext.line2.pos;
ext.a2.b = points[i];
ext.a2.a = ext.line1.pos;
ext.a2.drawLines = false;
if (un(ext.a2.lineWidth))
ext.a2.lineWidth = ctx.lineWidth;
if (un(ext.a2.lineColor))
ext.a2.lineColor = ctx.lineWidth;
if (un(ext.a2.labelColor))
ext.a2.labelColor = ctx.strokeStyle;
exteriorAngleLabelPos[i][1] = drawAngle(ext.a2);
}
if (!un(ext.a3) && ext.a3 !== null) {
ext.a3.ctx = ctx;
ext.a3.c = ext.line1.pos;
ext.a3.b = points[i];
if (i > 0) {
ext.a3.a = points[i - 1];
} else {
ext.a3.a = points[points.length - 1];
}
ext.a3.drawLines = false;
if (un(ext.a3.lineWidth))
ext.a3.lineWidth = ctx.lineWidth;
if (un(ext.a3.lineColor))
ext.a3.lineColor = ctx.lineWidth;
if (un(ext.a3.labelColor))
ext.a3.labelColor = ctx.strokeStyle;
exteriorAngleLabelPos[i][2] = drawAngle(ext.a3);
}
if (boolean(ext.line1.show, false)) {
ctx.save();
ctx.lineWidth = lineWidth;
ctx.moveTo(points[i][0], points[i][1]);
ctx.lineTo(ext.line1.pos[0], ext.line1.pos[1]);
ctx.stroke();
ctx.restore();
}
if (boolean(ext.line2.show, false)) {
ctx.save();
ctx.lineWidth = lineWidth;
ctx.moveTo(points[i][0], points[i][1]);
ctx.lineTo(ext.line2.pos[0], ext.line2.pos[1]);
ctx.stroke();
ctx.restore();
}
}
}
}
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
if (closed == true) {
ctx.closePath();
}
ctx.lineWidth = lineWidth;
if (obj.strokeStyle !== 'none')
ctx.stroke();
for (var i = 0; i < lineDecoration.length; i++) {
if (typeof lineDecoration[i] == 'object' && lineDecoration[i] !== null) {
switch (lineDecoration[i].type) {
case 'dash':
var dashLength = lineDecoration[i].length || 8 * sf;
var number = lineDecoration[i].number || 1;
if (number == 1) {
drawDash(ctx, points[i][0], points[i][1], points[(i + 1) % (points.length)][0], points[(i + 1) % (points.length)][1], dashLength);
} else if (number == 2) {
drawDoubleDash(ctx, points[i][0], points[i][1], points[(i + 1) % (points.length)][0], points[(i + 1) % (points.length)][1], dashLength);
}
break;
case 'arrow':
var arrowLength = lineDecoration[i].length || 12 * sf;
var number = lineDecoration[i].number || 1;
var direction = lineDecoration[i].direction || 1;
ctx.save();
if (direction == 1) {
drawParallelArrow({
context: ctx,
startX: points[i][0],
startY: points[i][1],
finX: points[(i + 1) % (points.length)][0],
finY: points[(i + 1) % (points.length)][1],
arrowLength: arrowLength,
lineWidth: ctx.lineWidth,
numOfArrows: number,
color: ctx.strokeStyle
});
} else if (direction == -1) {
drawParallelArrow({
context: ctx,
finX: points[i][0],
finY: points[i][1],
startX: points[(i + 1) % (points.length)][0],
startY: points[(i + 1) % (points.length)][1],
arrowLength: arrowLength,
lineWidth: ctx.lineWidth,
numOfArrows: number,
color: ctx.strokeStyle
});
}
ctx.restore();
break;
default:
break;
};
}
}
ctx.restore();
return {
angleLabelPos: angleLabelPos,
outerAngleLabelPos: outerAngleLabelPos,
exteriorAngleLabelPos: exteriorAngleLabelPos,
prismPoints: prismPoints,
textSnapPos: textSnapPos
};
}
function checkLinesForPolygon(lines, allowSelfIntersect) {
if (typeof allowSelfIntersect == 'undefined') allowSelfIntersect = false;
if (lines.length < 3) return false;
var lines = clone(lines);
lines = reduceLineSegments(lines);
// remove lines with zero length
for (var l = lines.length - 1; l >= 0; l--) {
if (arraysEqual(lines[l][0], lines[l][1])) lines.splice(l, 1);
}
var gradients = [];
for (var l = 0; l < lines.length; l++) {
var line = lines[l];
gradients[l] = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
}
function pointsEqual(p1, p2) {
if (p1[0] == p2[0] && p1[1] == p2[1]) return true;
return false;
}
//console.clear();
//console.log('lines:',clone(lines),lines.length);
//console.log('gradients:',gradients);
// test for overlapping parallel lines
for (var a = 0; a < lines.length - 1; a++) {
var lineA = lines[a];
for (var b = a + 1; b < lines.length; b++) {
var lineB = lines[b];
if (gradients[a] !== gradients[b]) continue;
var test1 = isPointOnLineSegment(lineB[0], lineA[0], lineA[1]);
var test2 = isPointOnLineSegment(lineB[1], lineA[0], lineA[1]);
var test3 = (pointsEqual(lineA[0], lineB[0]) || pointsEqual(lineA[0], lineB[1]) || pointsEqual(lineA[1], lineB[0]) || pointsEqual(lineA[1], lineB[1])) ? true : false;
if (test1 == false && test2 == false && test3 == false) continue;
//console.log('a:',a,lineA[0],lineA[1],'b:',b,lineB[0],lineB[1],'gradient:',gradients[a],gradients[b]); //console.log(pointsEqual(lineA[0],lineB[0]),pointsEqual(lineA[0],lineB[1]),pointsEqual(lineA[1],lineB[0]),pointsEqual(lineA[1],lineB[1]));
//console.log(test1,test2,test3);
// lines overlap - find most extreme of the 4 points
var pos = lineA.concat(lineB);
var min = pos[0];
var max = pos[0];
if (gradients[a] == Infinity || gradients[a] == -Infinity) {
for (var p = 1; p < pos.length; p++) {
if (pos[p][1] < min[1])
min = pos[p];
if (pos[p][1] > max[1])
max = pos[p];
}
} else {
for (var p = 1; p < pos.length; p++) {
if (pos[p][0] < min[0])
min = pos[p];
if (pos[p][0] > max[0])
max = pos[p];
}
}
lines[a] = [min, max];
lines.splice(b, 1);
}
}
var pos = [];
//console.log('reduced lines:');
for (var l = 0; l < lines.length; l++) {
var line = lines[l];
//console.log('('+line[0][0]+','+line[0][1]+')','('+line[1][0]+','+line[1][1]+')');
var found = [false, false];
for (var p = 0; p < pos.length; p++) {
if (pos[p].pos[0] == line[0][0] && pos[p].pos[1] == line[0][1]) {
pos[p].count++;
found[0] = true;
}
if (pos[p].pos[0] == line[1][0] && pos[p].pos[1] == line[1][1]) {
pos[p].count++;
found[1] = true;
}
}
if (found[0] == false)
pos.push({
pos: clone(line[0]),
count: 1
});
if (found[1] == false)
pos.push({
pos: clone(line[1]),
count: 1
});
//console.log(found,clone(pos));
}
//console.log('pos:',pos);
for (var p = 0; p < pos.length; p++) {
if (pos[p].count !== 2) return false;
}
var polygon = [lines.shift()];
while (lines.length > 0) {
var pos = polygon.last()[1];
var found = false;
for (var l = 0; l < lines.length; l++) {
var line = lines[l];
if (arraysEqual(line[0], pos)) {
polygon.push(line);
lines.splice(l, 1);
found = true;
break;
} else if (arraysEqual(line[1], pos)) {
polygon.push(line.reverse());
lines.splice(l, 1);
found = true;
break;
}
}
//console.log(clone(polygon),clone(lines),found);
if (found == false)
return false;
}
for (var p = 0; p < polygon.length; p++) polygon[p] = polygon[p][0];
//console.log('polygon:',polygon);
//if (allowSelfIntersect == false && polygonSelfIntersect2(polygon) == false) return false;
return polygon;
}
/*function reduceLineSegments(lines) {
var lines = clone(lines);
// remove lines with zero length
for (var l = lines.length - 1; l >= 0; l--) {
if (pointsEqual(lines[l][0], lines[l][1])) lines.splice(l, 1);
}
var gradients = [];
for (var l = 0; l < lines.length; l++) {
var line = lines[l];
gradients[l] = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
}
function pointsEqual(p1, p2) {
if (p1[0] == p2[0] && p1[1] == p2[1]) return true;
return false;
}
// test for overlapping parallel lines
for (var a = 0; a < lines.length - 1; a++) {
var lineA = lines[a];
for (var b = a + 1; b < lines.length; b++) {
var lineB = lines[b];
if (gradients[a] !== gradients[b]) continue;
var test1 = isPointOnLineSegment(lineB[0], lineA[0], lineA[1]);
var test2 = isPointOnLineSegment(lineB[1], lineA[0], lineA[1]);
var test3 = (pointsEqual(lineA[0], lineB[0]) || pointsEqual(lineA[0], lineB[1]) || pointsEqual(lineA[1], lineB[0]) || pointsEqual(lineA[1], lineB[1])) ? true : false;
if (test1 == false && test2 == false && test3 == false) continue;
// lines overlap - find most extreme of the 4 points
var pos = lineA.concat(lineB);
var min = pos[0];
var max = pos[0];
if (gradients[a] == Infinity || gradients[a] == -Infinity) {
for (var p = 1; p < pos.length; p++) {
if (pos[p][1] < min[1])
min = pos[p];
if (pos[p][1] > max[1])
max = pos[p];
}
} else {
for (var p = 1; p < pos.length; p++) {
if (pos[p][0] < min[0])
min = pos[p];
if (pos[p][0] > max[0])
max = pos[p];
}
}
lines[a] = [min, max];
lines.splice(b, 1);
}
}
return lines;
}*/
function reduceLineSegments(lines) {
var keepGoing = true;
while (keepGoing === true) {
keepGoing = false;
for (var l1 = 0; l1 < lines.length-1; l1++) {
var line1 = lines[l1];
for (var l2 = l1+1; l2 < lines.length; l2++) {
var line2 = lines[l2];
var x1 = line1[0][0];
var y1 = line1[0][1];
var x2 = line1[1][0];
var y2 = line1[1][1];
var x3 = line2[0][0];
var y3 = line2[0][1];
var x4 = line2[1][0];
var y4 = line2[1][1];
if ((x1 === x2 && x1 === x3 && x1 === x4) || (Math.abs((y2 - y1) / (x2 - x1) - (y4 - y3) / (x4 - x3))) < 0.01) { // vertical or gradients equal
// if one of the points is between the two points on the other line
if ((x1 >= Math.min(x3,x4) && x1 <= Math.max(x3,x4) && y1 >= Math.min(y3,y4) && y1 <= Math.max(y3,y4)) ||
(x2 >= Math.min(x3,x4) && x2 <= Math.max(x3,x4) && y2 >= Math.min(y3,y4) && y2 <= Math.max(y3,y4)) ||
(x3 >= Math.min(x1,x2) && x3 <= Math.max(x1,x2) && y3 >= Math.min(y1,y2) && y3 <= Math.max(y1,y2)) ||
(x4 >= Math.min(x1,x2) && x4 <= Math.max(x1,x2) && y4 >= Math.min(y1,y2) && y4 <= Math.max(y1,y2))) {
var x5 = Math.min(x1,x2,x3,x4);
var x6 = Math.max(x1,x2,x3,x4);
var y5 = x5 === x6 ? Math.min(y1,y2,y3,y4) : x5 === x1 ? y1 : x5 === x2 ? y2 : x5 === x3 ? y3 : y4;
var y6 = x5 === x6 ? Math.max(y1,y2,y3,y4) : x6 === x1 ? y1 : x6 === x2 ? y2 : x6 === x3 ? y3 : y4;
lines[l1] = [[x5,y5],[x6,y6]];
lines.splice(l2, 1);
l2--;
keepGoing = true;
}
}
}
}
}
return lines;
}
function polygonConvexTest(polygon) {
var polygon = clone(polygon);
if (polygonClockwiseTest(polygon) == false)
polygon.reverse();
for (var p = 0; p < polygon.length; p++) {
var angle = measureAngle({
a: polygon[p],
b: polygon[(p + 1) % polygon.length],
c: polygon[(p + 2) % polygon.length]
});
if (angle > Math.PI)
return false;
}
return true;
}
function polygonClockwiseTest(pos) {
if (pos.length < 3)
return null;
var sum = (pos[0][0] - pos.last()[0]) * (pos[0][1] + pos.last()[1]);
for (var i = 0; i < pos.length - 1; i++) {
sum += (pos[i + 1][0] - pos[i][0]) * (pos[i + 1][1] + pos[i][1]);
}
if (sum > 0)
return true;
return false;
};
function getPolygonSideLabelPoints(polygon, gap) {
if (un(gap))
gap = 25;
var points = [];
for (var i = 0; i < polygon.length; i++) {
var j = (i + 1) % polygon.length;
var mid = [(polygon[i][0] + polygon[j][0]) / 2, (polygon[i][1] + polygon[j][1]) / 2];
var vec = getVectorAB(polygon[i], polygon[j]);
var perp = getPerpVector(vec);
var perpDist = setVectorMag(perp, gap);
var point = pointAddVector(mid, perpDist);
points.push(point);
}
return points;
}
function drawRegularPolygon(obj) {
var ctx = obj.ctx;
var c = obj.center || obj.c;
var r = obj.radius || obj.r;
var p = obj.points || obj.p || 3;
var s = obj.step || obj.s || 1;
var startAngle = -Math.PI / 2;
if (typeof obj.startAngle == 'number')
startAngle = obj.startAngle;
var vertices = [];
for (var i = 0; i < p; i++) {
var angle = startAngle + i * (2 * Math.PI) / p;
vertices.push([c[0] + r * Math.cos(angle), c[1] + r * Math.sin(angle)]);
}
ctx.moveTo(vertices[0][0], vertices[0][1]);
for (var i = p; i >= 0; i--) {
ctx.lineTo(vertices[(i * s) % p][0], vertices[(i * s) % p][1]);
}
}
function drawRegularPolygonEllipse(ctx, obj) {
var c = obj.center;
var rX = obj.radiusX;
var rY = obj.radiusY;
var p = obj.points;
var s = obj.step || 1;
var startAngle = -Math.PI / 2;
if (typeof obj.startAngle == 'number')
startAngle = obj.startAngle;
var vertices = [];
for (var i = 0; i < p; i++) {
var angle = startAngle + i * (2 * Math.PI) / p;
vertices.push([c[0] + rX * Math.cos(angle), c[1] + rY * Math.sin(angle), angle]);
}
ctx.beginPath();
ctx.moveTo(vertices[0][0], vertices[0][1]);
for (var i = p; i >= 0; i--) {
ctx.lineTo(vertices[(i * s) % p][0], vertices[(i * s) % p][1]);
}
ctx.stroke();
return vertices;
}
function polygonIntersections(p1, p2) {
var intersections = [];
for (var i = 0; i < p1.length; i++) {
var line1 = [p1[i], p1[(i + 1) % p1.length]];
for (var j = 0; j < p2.length; j++) {
var line2 = [p2[j], p2[(j + 1) % p2.length]];
if (lineSegmentsIntersectionTest(line1, line2)) {
intersections.push({
edges: [i, j],
intersection: linesIntersection(line1, line2)
});
}
}
}
if (intersections.length == 0)
return false;
return intersections
}
function isPolygonInPolygon(p1, p2, includePerimeter) {
if (hitTestPolygon2(p1[0], p2) == false) return false;
for (var i = 0; i < p1.length; i++) {
var line1 = [p1[i], p1[(i + 1) % p1.length]];
for (var j = 0; j < p2.length; j++) {
var line2 = [p2[j], p2[(j + 1) % p2.length]];
if (lineSegmentsIntersectionTest(line1, line2) == true) {
if (includePerimeter == false) return false;
var p3 = isPointOnLineSegment(line1[0],line2[0],line2[1]);
var p4 = isPointOnLineSegment(line1[1],line2[0],line2[1]);
if (p3 == false && p4 == false) return false;
if (p3 == true && p4 == true) continue; // edge is part of edge
if (p3 == true && p4 == false && hitTestPolygon2(line1[1],p2) == false) return false;
if (p3 == false && p4 == true && hitTestPolygon2(line1[0],p2) == false) return false;
}
}
}
return true;
}
function polygonsIntersectionPolygon(p1, p2) {
var p = []; // polygon of intersecting region
// add all edge-edge intersections
for (var i = 0; i < p1.length; i++) {
var line1 = [p1[i], p1[(i + 1) % p1.length]];
for (var j = 0; j < p2.length; j++) {
var line2 = [p2[j], p2[(j + 1) % p2.length]];
if (lineSegmentsIntersectionTest(line1, line2)) {
p.push(linesIntersection(line1, line2));
}
}
}
// get all vertices of p1 that are inside p2
for (var i = 0; i < p1.length; i++) {
if (hitTestPolygon(p1[i], p2, false))
p.push(p1[i]);
}
// get all vertices of p1 that are inside p2
for (var i = 0; i < p2.length; i++) {
if (hitTestPolygon(p2[i], p1, false))
p.push(p2[i]);
}
// get centre of p
var center = [0, 0];
for (var i = 0; i < p.length; i++) {
center[0] += p[i][0];
center[1] += p[i][1];
}
center[0] = center[0] / p.length;
center[1] = center[1] / p.length;
// order p
p.sort(function (a, b) {
var a1 = getAngleFromAToB(center, a);
var a2 = getAngleFromAToB(center, b);
return a2 - a1;
});
console.log(p, polygonClockwiseTest(p));
return p;
}
function polygonArea(verticesArray) {
verticesArray.push(verticesArray[0]);
var area = 0;
for (var i = 0; i < verticesArray.length - 1; i++) {
area += (verticesArray[i][0] * verticesArray[i + 1][1]) - (verticesArray[i][1] * verticesArray[i + 1][0]);
}
area = 0.5 * Math.abs(area);
return area;
}
function polygonCountSides(verticesArray) {
// collapses polygon (ie. finds any three points that are on a straight line and reduces to single line)
// returns number of sides/vertices of collapsed polygon
var verticesNum = verticesArray.length - 1; // given number of vertices
if (verticesNum < 3)
return verticesNum;
verticesArray.push(verticesArray[0]);
verticesArray.push(verticesArray[1]);
var verticesCount = 0; // return number of vertices
for (var i = 0; i <= verticesNum; i++) {
var point1 = verticesArray[i];
var point2 = verticesArray[i + 1];
var point3 = verticesArray[i + 2];
var m1 = (point2[1] - point1[1]) / (point2[0] - point1[0]);
var m2 = (point3[1] - point2[1]) / (point3[0] - point2[0]);
var error = false;
if (point1[0] == point2[0] && point1[1] == point2[1])
error = true;
if (point1[0] == point3[0] && point1[1] == point3[1])
error = true;
if (point3[0] == point2[0] && point3[1] == point2[1])
error = true;
if (m1 !== m2 && error == false)
verticesCount++;
}
verticesArray.splice(-2, 2);
logMe("verticesArray:", verticesArray, "polygon");
return verticesCount;
}
function polygonSelfIntersect(verticesArray) {
// vertices array such as [[], [], [], []]
verticesArray = clone(verticesArray);
verticesArray.push(verticesArray[0]); // duplicate first vertex to last vertex
var segmentArray = [];
var events = [];
// create segments array [[x1,y1,x2,y2],[...],...] where x1 <= x2
for (var i = 0; i < verticesArray.length - 1; i++) {
segmentArray.push([verticesArray[i][0], verticesArray[i][1], verticesArray[i + 1][0], verticesArray[i + 1][1]]);
// choose left-most end of segment to be 'start'
var order;
if (verticesArray[i][0] < verticesArray[i + 1][0]) {
order = 1;
} else if (verticesArray[i][0] > verticesArray[i + 1][0]) {
order = 2;
} else {
if (verticesArray[i][1] < verticesArray[i + 1][1]) {
order = 1;
} else {
order = 2;
}
}
if (order == 1) {
events.push([verticesArray[i][0], verticesArray[i][1], i, 'start']);
events.push([verticesArray[i + 1][0], verticesArray[i + 1][1], i, 'end']);
} else {
events.push([verticesArray[i][0], verticesArray[i][1], i, 'end']);
events.push([verticesArray[i + 1][0], verticesArray[i + 1][1], i, 'start']);
}
}
// sort the events by x value
events.sort(function (a, b) {
return a[0] - b[0]
});
var sweepLine = [];
for (var j = 0; j < events.length; j++) {
if (events[j][3] == 'start') {
sweepLine.push([events[j][0], events[j][1], events[j][2]]); // x-value, y-value, lineSegmentId
sweepLine.sort(function (a, b) {
return a[1] - b[1]
}); // sort sweepLine by y-value
} else if (events[j][3] == 'end') {
// get position in sweepline (pos1) and segment id (seg1)
var pos1,
seg1;
for (var k = 0; k < sweepLine.length; k++) {
if (sweepLine[k][2] == events[j][2]) {
pos1 = k;
seg1 = segmentArray[k];
break;
}
}
// if not the lowest line, check for intersection with line below
if (pos1 > 0) {
var pos2 = sweepLine[pos1 - 1][2];
var seg2 = segmentArray[pos2];
if (intersects(seg1[0], seg1[1], seg1[2], seg1[3], seg2[0], seg2[1], seg2[2], seg2[3]) == true)
return true;
}
if (pos1 < sweepLine.length - 1) {
var pos2 = sweepLine[pos1 + 1][2];
var seg2 = segmentArray[pos2];
if (intersects(seg1[0], seg1[1], seg1[2], seg1[3], seg2[0], seg2[1], seg2[2], seg2[3]) == true)
return true;
}
}
}
return false;
}
function polygonSelfIntersect2(polygon) {
var edges = [];
for (var i = 0; i < polygon.length; i++)
edges.push([polygon[i], polygon[(i + 1) % polygon.length]]);
for (var i = 0; i < edges.length - 1; i++) {
for (var j = i + 1; j < edges.length; j++) {
if (
arraysEqual(edges[i][0], edges[j][0]) ||
arraysEqual(edges[i][0], edges[j][1]) ||
arraysEqual(edges[i][1], edges[j][0]) ||
arraysEqual(edges[i][1], edges[j][1])) {
continue;
}
if (intersects(edges[i][0][0], edges[i][0][1], edges[i][1][0], edges[i][1][1], edges[j][0][0], edges[j][0][1], edges[j][1][0], edges[j][1][1]) == true) {
return true;
}
}
}
return false;
}
function findMinPolygon(edgeVertices, lines, point) {
var edges = [];
var lines2 = [];
edgeVertices.push(edgeVertices[0].slice());
// split edges into smaller vectors according to intersection points with lines
for (var i = 0; i < edgeVertices.length - 1; i++) {
edges[i] = [{
point: edgeVertices[i].slice(0),
dist: 0
}, {
point: edgeVertices[i + 1].slice(0),
dist: dist(edgeVertices[i][0], edgeVertices[i][1], edgeVertices[i + 1][0], edgeVertices[i + 1][1])
}
];
for (var j = 0; j < lines.length; j++) {
//console.log('edgeVertices:',edgeVertices[i][0],edgeVertices[i][1],edgeVertices[i+1][0],edgeVertices[i+1][1]);
// console.log('lineVertices:',lines[j][0][0],lines[j][0][1],lines[j][1][0],lines[j][1][1]);
// console.log('intersects2:',intersects2(lines[j][0][0],lines[j][0][1],lines[j][1][0],lines[j][1][1],edgeVertices[i][0],edgeVertices[i][1],edgeVertices[i+1][0],edgeVertices[i+1][1]));
if (intersects2(lines[j][0][0], lines[j][0][1], lines[j][1][0], lines[j][1][1], edgeVertices[i][0], edgeVertices[i][1], edgeVertices[i + 1][0], edgeVertices[i + 1][1])) {
var int = intersection(edgeVertices[i][0], edgeVertices[i][1], edgeVertices[i + 1][0], edgeVertices[i + 1][1], lines[j][0][0], lines[j][0][1], lines[j][1][0], lines[j][1][1]);
// check that the intersection point is not one of the end points of the edge
if ((int[0] == edgeVertices[i][0] && int[1] == edgeVertices[i][1]) || (int[0] == edgeVertices[i + 1][0] && int[1] == edgeVertices[i + 1][1])) {
//console.log('corner');
} else {
edges[i].push({
point: int,
dist: dist(edges[i][0].point[0], edges[i][0].point[1], int[0], int[1])
});
}
if (typeof lines2[j] == 'undefined') {
lines2[j] = [{
point: int.slice(0),
dist: 0
}
];
} else {
lines2[j].push({
point: int.slice(0),
dist: dist(int[0], int[1], lines2[j][0].point[0], lines2[j][0].point[1])
});
}
}
}
}
for (var i = 0; i < lines.length - 1; i++) {
for (var j = i + 1; j < lines.length; j++) {
var int = intersection(lines[i][0][0], lines[i][0][1], lines[i][1][0], lines[i][1][1], lines[j][0][0], lines[j][0][1], lines[j][1][0], lines[j][1][1]);
if (hitTestPolygon(int, edgeVertices)) {
lines2[i].push({
point: int.slice(0),
dist: dist(int[0], int[1], lines2[i][0].point[0], lines2[i][0].point[1])
});
lines2[j].push({
point: int.slice(0),
dist: dist(int[0], int[1], lines2[j][0].point[0], lines2[j][0].point[1])
});
}
}
}
for (var i = 0; i < edges.length; i++) {
edges[i].sortOn('dist');
}
for (var i = 0; i < lines2.length; i++) {
lines2[i].sortOn('dist');
}
//console.log('edges:',edges.slice(0));
//console.log('lines2:',lines2.slice(0));
// find duplicate points in lines2 and remove
for (var i = 0; i < lines2.length; i++) {
for (var j = 1; j < lines2[i].length; ) {
if (lines2[i][j - 1].dist == lines2[i][j].dist) {
lines2[i].splice(j, 1);
} else {
j++;
}
}
}
var vectors = [];
for (var i = 0; i < edges.length; i++) {
for (var j = 0; j < edges[i].length - 1; j++) {
vectors.push([edges[i][j].point.slice(0), edges[i][j + 1].point.slice(0)]);
}
}
for (var i = 0; i < lines2.length; i++) {
for (var j = 0; j < lines2[i].length - 1; j++) {
vectors.push([lines2[i][j].point.slice(0), lines2[i][j + 1].point.slice(0)]);
}
}
//console.log('vectors:',vectors.slice(0));
//console.log('point:',point);
var minDist = [];
// work out whish vector is closest to the point
for (var i = 0; i < vectors.length; i++) {
minDist[i] = distancePointToLineSegment(point, vectors[i][0], vectors[i][1]);
}
//console.log('minDist:',minDist);
// start the polygon vertices array
var vertices = [];
var currVector = vectors[minDist.indexOf(minDist.min())];
// put the closest vector vertices into the array
if (measureAngle({
a: point,
b: currVector[0],
c: currVector[1]
}) < measureAngle({
a: point,
b: currVector[1],
c: currVector[0]
})) {
vertices.push(currVector[0].slice(0), currVector[1].slice(0));
} else {
vertices.push(currVector[1].slice(0), currVector[0].slice(0));
}
vectors.splice(minDist.indexOf(minDist.min()), 1);
//console.log('first vertices:',vertices[0],vertices[1]);
// until the path is completed
do {
var currPoint = vertices[vertices.length - 1].slice(0);
// find all poss vectors from the current point
var possVectors = [];
var possVectorsAngle = [];
var possVectorsIndex = [];
for (var i = 0; i < vectors.length; i++) {
if (arraysEqual(vectors[i][0], currPoint)) {
//console.log('---possVector:',vectors[i].slice(0));
possVectors.push(vectors[i].slice(0));
possVectorsIndex.push(i);
possVectorsAngle.push(measureAngle({
a: vertices[vertices.length - 2],
b: vertices[vertices.length - 1],
c: vectors[i][1]
}));
//console.log('angle:',measureAngle({a:vertices[vertices.length-2],b:vertices[vertices.length-1],c:vectors[i][1]}));
} else if (arraysEqual(vectors[i][1], currPoint)) {
//console.log('+++possVector:',[vectors[i][1].slice(0),vectors[i][0].slice(0)]);
possVectors.push([vectors[i][1].slice(0), vectors[i][0].slice(0)]);
possVectorsIndex.push(i);
possVectorsAngle.push(measureAngle({
a: vertices[vertices.length - 2],
b: vertices[vertices.length - 1],
c: vectors[i][0]
}));
//console.log('angle:',measureAngle({a:vertices[vertices.length-2],b:vertices[vertices.length-1],c:vectors[i][0]}));
}
}
//console.log('possVectors:',possVectors);
//console.log('possVectorsAngle:',possVectorsAngle);
//console.log('possVectorsIndex:',possVectorsIndex);
// chose the vector with the smallest angle
var selVectorPos = possVectorsAngle.indexOf(possVectorsAngle.min());
vertices.push(possVectors[selVectorPos][1].slice(0));
selVectorPos = possVectorsIndex[selVectorPos];
vectors.splice(selVectorPos, 1);
//console.log('vertex:',vertices[vertices.length-1]);
} while (arraysEqual(vertices[0], vertices[vertices.length - 1]) == false);
//console.log('vertices:',vertices);
return vertices;
}
function samePolygons(verticesArray1, verticesArray2) {
var a = verticesArray1;
var b = verticesArray2;
if (a.length !== b.length)
return false;
for (var i = 0; i < a.length; i++) {
var found = false;
for (var j = 0; j < b.length; j++) {
if (roundToNearest(a[i][0], 0.0001) == roundToNearest(b[j][0], 0.0001) && roundToNearest(a[i][1], 0.0001) == roundToNearest(b[j][1], 0.0001)) {
found = true;
break;
}
}
if (found == false)
return false;
}
return true;
}
function polygonCongruenceTest(p1, p2, mode) { // mode 0 = any congruent shape, 1 = rotations allowed but not reflections, 2 = no rotations or reflectons allowed
if (typeof mode == 'undefined')
mode = 0
if (p1.length !== p2.length)
return false;
var p1 = clone(p1);
var p2 = clone(p2);
if (polygonClockwiseTest(p1) == false)
p1.reverse();
if (polygonClockwiseTest(p2) == false)
p2.reverse();
if (mode == 2) { // test edge vectors are congruent
var vectors1 = [],
vectors2 = [];
for (var p = 0; p < p1.length; p++) {
var b = p1[p];
var c = p == p1.length - 1 ? p1[0] : p1[p + 1];
vectors1[p] = [c[0] - b[0], c[1] - b[1]];
var b = p2[p];
var c = p == p2.length - 1 ? p2[0] : p2[p + 1];
vectors2[p] = [c[0] - b[0], c[1] - b[1]];
}
//console.log(vectors1,vectors2);
for (var p = 0; p < p1.length; p++) {
if (arraysEqual(vectors1[0], vectors2[p])) {
//console.log(p,vectors1[0],vectors2[p]);
var match = true;
for (var q = 1; q < p2.length; q++) {
var r = (p + q) % p2.length;
//console.log(q,vectors1[q],vectors2[r]);
if (arraysEqual(vectors1[q], vectors2[r]) == false) {
match = false;
break;
}
}
if (match == true)
return true;
}
}
return false;
} else { // test edge lengths and angles are congruent
var lengths1 = [],
lengths2 = [];
var angles1 = [],
angles2 = [];
for (var p = 0; p < p1.length; p++) {
var a = p == 0 ? p1.last() : p1[p - 1];
var b = p1[p];
var c = p == p1.length - 1 ? p1[0] : p1[p + 1];
lengths1[p] = dist(b[0], b[1], c[0], c[1]);
angles1[p] = measureAngle({
a: a,
b: b,
c: c
});
}
for (var p = 0; p < p2.length; p++) {
var a = p == 0 ? p2.last() : p2[p - 1];
var b = p2[p];
var c = p == p2.length - 1 ? p2[0] : p2[p + 1];
lengths2[p] = dist(b[0], b[1], c[0], c[1]);
angles2[p] = measureAngle({
a: a,
b: b,
c: c
});
}
for (var p = 0; p < p1.length; p++) {
if (Math.abs(lengths1[0] - lengths2[p]) < 0.001 && Math.abs(angles1[0] - angles2[p]) < 0.001) {
var match = true;
for (var q = 1; q < p2.length; q++) {
var r = (p + q) % p2.length;
if (Math.abs(lengths1[q] - lengths2[r]) > 0.001 || Math.abs(angles1[q] - angles2[r]) > 0.001) {
match = false;
break;
}
}
if (match == true)
return true;
if (mode == 0) { // check for a match in reverse vertices direction ie. a reflection
for (var q = 1; q < p2.length; q++) {
var r = p - q;
if (r < 0)
r = p2.length + r;
if (Math.abs(lengths1[q] - lengths2[r]) > 0.001 || Math.abs(angles1[q] - angles2[r]) > 0.001) {
match = false;
break;
}
}
if (match == true)
return true;
}
}
}
return false;
}
}
function polygonGetCenter(polygon) {
var totals = [0,0];
for (var p = 0; p < polygon.length; p++) {
totals[0] += polygon[p][0];
totals[1] += polygon[p][1];
if (polygon[p].length == 3) {
if (un(totals[2])) totals[2] = 0;
totals[2] += polygon[p][2];
}
}
for (var t = 0; t < totals.length; t++) {
totals[t] /= polygon.length;
}
return totals;
}
function lineSegmentsIntersectionTest(line1, line2) {
return intersects(line1[0][0], line1[0][1], line1[1][0], line1[1][1], line2[0][0], line2[0][1], line2[1][0], line2[1][1]);
}
function linesIntersection(line1, line2) {
return intersection(line1[0][0], line1[0][1], line1[1][0], line1[1][1], line2[0][0], line2[0][1], line2[1][0], line2[1][1]);
}
function intersects(a, b, c, d, p, q, r, s) {
// returns true iff the line segemnt from (a,b)->(c,d) intersects with the line segment from (p,q)->(r,s)
var det,
gamma,
lambda;
det = (c - a) * (s - q) - (r - p) * (d - b);
if (det === 0) {
return false;
} else {
lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
}
};
function intersects2(a, b, c, d, p, q, r, s) {
// returns true iff the infinite line though (a,b) & (c,d) intersects with line segment (p,q)->(r,s)
var int = intersection(a, b, c, d, p, q, r, s);
return isPointOnLineSegment(int, [p, q], [r, s]);
};
function intersection(aX, aY, bX, bY, cX, cY, dX, dY) { // finds and returns the intersection point of the lines AB and CD
if (aX instanceof Array) {
var a = aX, b = aY, c = bX, d = bY;
aX = a[0];
aY = a[1];
bX = b[0];
bY = b[1];
cX = c[0];
cY = c[1];
dX = d[0];
dY = d[1];
}
if (aX !== bX && cX !== dX) {
var m1 = (bY - aY) / (bX - aX); // gradient of AB;
var m2 = (dY - cY) / (dX - cX); // gradient of CD;
if (m1 == m2)
return 'parallel';
var xIntersection = ((aY - aX * m1) - (cY - cX * m2)) / (m2 - m1);
var yIntersection = aY + m1 * (xIntersection - aX);
return [xIntersection, yIntersection];
} else if (aX == bX && cX == dX) {
return 'parallel';
} else if (aX == bX) {
// if AB is vertical
var m2 = (dY - cY) / (dX - cX); // gradient of CD;
var xIntersection = aX;
var yIntersection = cY + m2 * (xIntersection - cX);
return [xIntersection, yIntersection];
} else if (cX == dX) {
// if CD is vertical
var m1 = (bY - aY) / (bX - aX); // gradient of AB;
var xIntersection = cX;
var yIntersection = aY + m1 * (xIntersection - aX);
return [xIntersection, yIntersection];
}
}
function dist(x1, y1, x2, y2) {
if (x1 instanceof Array && y1 instanceof Array) return (dist(x1[0],x1[1],y1[0],y1[1]));
return Math.pow(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2), 0.5);
}
function midpoint(x1, y1, x2, y2) {
return [0.5 * (x1 + x2), 0.5 * (y1 + y2)];
}
function collapsePath(path) {
var path1 = path.slice(0);
do {
var joinFound = false;
for (var i = path1.length - 1; i >= 0; i--) {
if (joinFound == false) {
for (var j = i - 1; j >= 0; j--) {
if (path1[i].obj[0].type == path1[j].obj[0].type) {
if (path1[i].type == 'pen') {}
else if (path1[i].obj[0].type == 'line') {
var x1 = path1[i].obj[0].startPos[0];
var y1 = path1[i].obj[0].startPos[1];
var x2 = path1[i].obj[0].finPos[0];
var y2 = path1[i].obj[0].finPos[1];
var x3 = path1[j].obj[0].startPos[0];
var y3 = path1[j].obj[0].startPos[1];
var x4 = path1[j].obj[0].finPos[0];
var y4 = path1[j].obj[0].finPos[1];
var m1 = (y2 - y1) / (x2 - x1);
var m2 = (y4 - y3) / (x4 - x3);
if (Math.abs(m1) > Math.abs(m2)) {
var mMax = m1;
var mMin = m2;
} else {
var mMax = m2;
var mMin = m1;
}
// if gradients are equal and point from line 1 is on line 2
//console.log('grad/grad: ',mMax/mMin);
//console.log('pointOnLine: ',isPointOnLine([x1,y1],[x3,y3],[x4,y4],3.5));
if (((mMin >= 0 && mMax >= 0) || (mMin < 0 && mMax < 0)) && Math.abs(mMax / mMin) < 1.1 && isPointOnLine([x1, y1], [x3, y3], [x4, y4], 3.5) == true) {
// if one of the points is between the two points on the other line
if ((x1 >= Math.min(x3, x4) && x1 <= Math.max(x3, x4) && y1 >= Math.min(y3, y4) && y1 <= Math.max(y3, y4)) || (x2 >= Math.min(x3, x4) && x2 <= Math.max(x3, x4) && y2 >= Math.min(y3, y4) && y2 <= Math.max(y3, y4)) || (x3 >= Math.min(x1, x2) && x3 <= Math.max(x1, x2) && y3 >= Math.min(y1, y2) && y3 <= Math.max(y1, y2)) || (x4 >= Math.min(x1, x2) && x4 <= Math.max(x1, x2) && y4 >= Math.min(y1, y2) && y4 <= Math.max(y1, y2))) {
var xMin = Math.min(x1, x2, x3, x4);
var xMax = Math.max(x1, x2, x3, x4);
if (xMin == x1)
var yMin = y1;
if (xMin == x2)
var yMin = y2;
if (xMin == x3)
var yMin = y3;
if (xMin == x4)
var yMin = y4;
if (xMax == x1)
var yMax = y1;
if (xMax == x2)
var yMax = y2;
if (xMax == x3)
var yMax = y3;
if (xMax == x4)
var yMax = y4;
joinFound = true;
path1[i].startPos = [xMin, yMin];
path1[i].finPos = [xMax, yMax];
path1.splice(j, 1);
break;
}
}
} else if (path1[i].obj[0].type == 'arc') {
if (Math.abs(path1[i].obj[0].center[0] - path1[j].obj[0].center[0]) < 2 && Math.abs(path1[i].obj[0].center[1] - path1[j].obj[0].center[1]) < 2 && Math.abs(path1[i].obj[0].radius - path1[j].obj[0].radius) < 2) {
if ((path1[i].obj[0].startAngle >= path1[j].obj[0].startAngle && path1[i].obj[0].startAngle <= path1[j].obj[0].finAngle) ||
/* if min angle of first is between min & max angle of second*/
(path1[i].obj[0].finAngle >= path1[j].obj[0].startAngle && path1[i].obj[0].finAngle <= path1[j].obj[0].finAngle) ||
/* if max angle of first is between min & max angle of second*/
(path1[j].obj[0].startAngle >= path1[i].obj[0].startAngle && path1[j].obj[0].startAngle <= path1[i].obj[0].finAngle) ||
/* if min angle of second is between min & max angle of first*/
(path1[j].obj[0].finAngle >= path1[i].obj[0].startAngle && path1[j].obj[0].finAngle <= path1[i].obj[0].finAngle) ||
/* if max angle of second is between min & max angle of first*/
(path1[i].obj[0].startAngle <= path1[j].obj[0].startAngle && path1[i].obj[0].finAngle >= path1[j].obj[0].finAngle) ||
/* if second is contained within first*/
(path1[j].obj[0].startAngle <= path1[i].obj[0].startAngle && path1[j].obj[0].finAngle >= path1[i].obj[0].finAngle))
/* if first is contained within second*/
{
joinFound = true;
path1[i].obj[0].startAngle = Math.min(path1[i].obj[0].startAngle, path1[j].obj[0].startAngle);
path1[i].obj[0].finAngle = Math.max(path1[i].obj[0].finAngle, path1[j].obj[0].finAngle);
path1.splice(j, 1);
break;
}
}
}
}
}
}
}
} while (joinFound == true);
return path1;
}
function getIntersectionPoints(path) {
// currently only handles line/line, line/arc and arc/arc intersections
var intPoints = [];
for (var i = 0; i < path.length; i++) {
if (typeof path[i].obj == 'undefined')
continue;
for (var j = 0; j < path[i].obj.length; j++) {
var obj1 = path[i].obj[j];
if (obj1.drawing == true)
continue;
for (var k = i; k < path.length; k++) {
for (var l = 0; l < path[k].obj.length; l++) {
if (i == k && j == l)
continue;
var obj2 = path[k].obj[l];
if (obj2.drawing == true)
continue;
if (obj1.type == 'line' && obj2.type == 'line') {
var int = intersection(obj1.startPos[0], obj1.startPos[1], obj1.finPos[0], obj1.finPos[1], obj2.startPos[0], obj2.startPos[1], obj2.finPos[0], obj2.finPos[1]);
if (isPointOnLineSegment(int, obj1.startPos, obj1.finPos) == true)
intPoints.push(int);
} else if (obj1.type == 'line' && obj2.type == 'arc') {
var int = lineCircleIntersections(obj1.startPos, obj1.finPos, obj2.center, obj2.radius);
for (var m = 0; m < int.length; m++) {
if (isPointOnLineSegment(int[m], obj1.startPos, obj1.finPos) == true && isAngleInPath(obj2, obj2.center, obj2.radius, int[m][2], 1) == true)
intPoints.push(int[m]);
}
} else if (obj1.type == 'arc' && obj2.type == 'line') {
var int = lineCircleIntersections(obj2.startPos, obj2.finPos, obj1.center, obj1.radius);
for (var m = 0; m < int.length; m++) {
if (isPointOnLineSegment(int[m], obj2.startPos, obj2.finPos) == true && isAngleInPath(obj1, obj1.center, obj1.radius, int[m][2], 1) == true)
intPoints.push(int[m]);
}
} else if (obj1.type == 'arc' && obj2.type == 'arc') {
var int = circleIntersections(obj1.center[0], obj1.center[1], obj1.radius, obj2.center[0], obj2.center[1], obj2.radius);
for (var m = 0; m < int.length; m++) {
var a = isPointOnArc(int[m], obj1);
var b = isPointOnArc(int[m], obj2);
if (a == true && b == true)
intPoints.push(int[m]);
}
}
}
}
}
}
return intPoints;
}
function getEndPoints(path) {
// currently only handles pen, line and arc paths
var endPoints = [];
for (var i = 0; i < path.length; i++) {
if (typeof path[i].obj == 'undefined')
continue;
for (var j = 0; j < path[i].obj.length; j++) {
if (path[i].obj[j].drawing == true)
continue;
switch (path[i].obj[j].type) {
case 'line':
endPoints.push(path[i].obj[j].startPos, path[i].obj[j].finPos);
break;
case 'arc':
var arcEnds = getEndPointsOfArc(path[i].obj[j]);
for (var k = 0; k < arcEnds.length; k++) {
endPoints.push(arcEnds[k]);
}
break;
}
}
}
return endPoints;
}
function isAngleInPath(arcPath, center, radius, angle, tolerance) {
if (dist(arcPath.center[0], arcPath.center[1], center[0], center[1]) > tolerance)
return false;
if (Math.abs(arcPath.radius - radius) > tolerance)
return false;
var startAngle = arcPath.startAngle;
var finAngle = arcPath.finAngle;
if (arcPath.clockwise == true) {
if (startAngle < angle && finAngle > angle)
return true;
if (startAngle + 2 * Math.PI < angle && finAngle + 2 * Math.PI > angle)
return true;
if (startAngle - 2 * Math.PI < angle && finAngle - 2 * Math.PI > angle)
return true;
} else {
if (startAngle < finAngle)
startAngle += 2 * Math.PI;
if (startAngle > angle && finAngle < angle)
return true;
if (startAngle + 2 * Math.PI > angle && finAngle + 2 * Math.PI < angle)
return true;
if (startAngle - 2 * Math.PI > angle && finAngle - 2 * Math.PI < angle)
return true;
}
return false;
}
function doesArcIncludeAngle(arc, angle) {
while (angle < 0) {
angle += 2 * Math.PI;
}
angle = angle % (2 * Math.PI);
if (arc.clockwise == false) {
if (arc.finAngle > arc.startAngle) {
if (angle <= arc.startAngle || angle >= arc.finAngle)
return true;
} else {
if (angle >= arc.finAngle && angle <= arc.startAngle)
return true;
}
} else {
if (arc.finAngle > arc.startAngle) {
if (angle >= arc.startAngle || angle <= arc.finAngle)
return true;
} else {
if (angle <= arc.finAngle && angle >= arc.startAngle)
return true;
}
}
return false;
}
function distancePointToPath(point, path) {
if (path.length > 1) {
var closestDist = distancePointToLineSegment(point, path[0], path[1]);
for (var i = 1; i < path.length - 1; i++) {
closestDist = Math.min(closestDist, distancePointToLineSegment(point, path[i], path[i + 1]));
}
return closestDist;
} else if (path.length == 1) {
return dist(point[0], point[1], path[0][0], path[0][1]);
} else {
return 100000000000;
}
}
function isPointOnLine(point, linePos1, linePos2, tolerance) {
if (un(tolerance))
tolerance = 0.001;
var closestPos = closestPointOnLine(point, linePos1, linePos2);
var distance = dist(closestPos[0], closestPos[1], point[0], point[1]);
if (distance <= tolerance)
return true;
return false;
}
function closestPointOnLine(point, linePos1, linePos2) {
var dirVector = [linePos2[0] - linePos1[0], linePos2[1] - linePos1[1]];
var lambda = ((point[0] - linePos1[0]) * dirVector[0] + (point[1] - linePos1[1]) * dirVector[1]) / (Math.pow(dirVector[0], 2) + Math.pow(dirVector[1], 2));
return [linePos1[0] + lambda * dirVector[0], linePos1[1] + lambda * dirVector[1]];
}
function distancePointToLine(point, linePos1, linePos2) {
var closest = closestPointOnLine(point, linePos1, linePos2);
return dist(point[0], point[1], closest[0], closest[1]);
}
function closestPointOnLineSegment(point, linePos1, linePos2) {
var dirVector = [linePos2[0] - linePos1[0], linePos2[1] - linePos1[1]];
var lambda = ((point[0] - linePos1[0]) * dirVector[0] + (point[1] - linePos1[1]) * dirVector[1]) / (Math.pow(dirVector[0], 2) + Math.pow(dirVector[1], 2));
if (lambda <= 0) {
return linePos1;
} else if (lambda >= 1) {
return linePos2;
} else {
return [linePos1[0] + lambda * dirVector[0], linePos1[1] + lambda * dirVector[1]];
}
}
function distancePointToLineSegment(point, linePos1, linePos2) {
var closest = closestPointOnLineSegment(point, linePos1, linePos2);
return dist(point[0], point[1], closest[0], closest[1]);
}
function checkPathForLine(path, x1, y1, isEnd1, x2, y2, isEnd2, tolerance) {
for (var i = 0; i < path.length; i++) {
for (var k = 0; k < path[i].obj.length; k++) {
if (path[i].obj[k].type == 'line' && lineCheck(x1, y1, isEnd1, x2, y2, isEnd2, tolerance, path[i].obj[k].startPos[0], path[i].obj[k].startPos[1], path[i].obj[k].finPos[0], path[i].obj[k].finPos[1]) == true)
return true;
}
}
return false;
}
function isPointOnLineSegment(point, linePos1, linePos2, tolerance) {
if (un(tolerance)) tolerance = 0.001;
return distancePointToLineSegment(point, linePos1, linePos2) < tolerance;
}
/*function isPointOnLineSegment(point,linePos1,linePos2) {
var dirVector = [linePos2[0]-linePos1[0],linePos2[1]-linePos1[1]];
var lambda = ((point[0]-linePos1[0])*dirVector[0]+(point[1]-linePos1[1])*dirVector[1])/(Math.pow(dirVector[0],2)+Math.pow(dirVector[1],2));
if (lambda >= 0 && lambda <= 1) {
return true;
} else {
return false;
}
}*/
function isPointInSector(point, dims) {
if (dist(point[0], point[1], dims[0], dims[1]) > dims[2])
return false;
var a1 = dims[3];
var a2 = getAngleTwoPoints([dims[0], dims[1]], point);
var a3 = dims[4];
return anglesInOrder(a1, a2, a3);
}
function sameLine(line1, line2) {
return (isPointOnLine(line1[0], line2[0], line2[1], 0) && isPointOnLine(line1[1], line2[0], line2[1], 0));
}
function lineCheck(x1, y1, isEnd1, x2, y2, isEnd2, tolerance, xTest1, yTest1, xTest2, yTest2) {
//console.log(x1,y1,isEnd1,x2,y2,isEnd2,tolerance,xTest1,yTest1,xTest2,yTest2);
// if points should be ends of the line, test if they are
if (isEnd1 == true && dist(x1, y1, xTest1, yTest1) > tolerance && dist(x1, y1, xTest2, yTest2) > tolerance) {
return false;
}
if (isEnd2 == true && dist(x2, y2, xTest1, yTest1) > tolerance && dist(x2, y2, xTest2, yTest2) > tolerance) {
return false;
}
// test if the points are on the line, with the given tolerance
if (isPointOnLine([x1, y1], [xTest1, yTest1], [xTest2, yTest2], tolerance) == false || isPointOnLine([x2, y2], [xTest1, yTest1], [xTest2, yTest2], tolerance) == false) {
return false;
}
// test if the line has been drawn far enough
var mag = dist(x1, y1, x2, y2);
var minPos1 = [x1 + 2 * tolerance / mag * (x2 - x1), y1 + 2 * tolerance / mag * (y2 - y1)];
var minPos2 = [x2 + 2 * tolerance / mag * (x1 - x2), y2 + 2 * tolerance / mag * (y1 - y2)];
/*
console.log('pos1:'+x1+','+y1);
console.log('pos2:'+x2+','+y2);
console.log('minPos1:'+minPos1[0]+','+minPos1[1]);
console.log('minPos2:'+minPos2[0]+','+minPos2[1]);
console.log('testPos1:'+xTest1+','+yTest1);
console.log('testPos2:'+xTest2+','+yTest2);
console.log('dist to min pos, dist to actual pos:');
console.log('pos1,testPos1:',dist(minPos1[0],minPos1[1],xTest1,yTest1),dist(x1,y1,xTest1,yTest1),dist(minPos1[0],minPos1[1],xTest1,yTest1)>dist(x1,y1,xTest1,yTest1));
console.log('pos2,testPos2:',dist(minPos2[0],minPos2[1],xTest2,yTest2),dist(x2,y2,xTest2,yTest2),dist(minPos2[0],minPos2[1],xTest2,yTest2)>dist(x2,y2,xTest2,yTest2));
console.log('pos1,testPos2:',dist(minPos1[0],minPos1[1],xTest2,yTest2),dist(x1,y1,xTest2,yTest2),dist(minPos1[0],minPos1[1],xTest2,yTest2)>dist(x1,y1,xTest2,yTest2));
console.log('pos2,testPos1:',dist(minPos2[0],minPos2[1],xTest1,yTest1),dist(x2,y2,xTest1,yTest1),dist(minPos2[0],minPos2[1],xTest1,yTest1)>dist(x2,y2,xTest1,yTest1));
//*/
// if both points are closer to minPos than to either of the actual points
if ((dist(minPos1[0], minPos1[1], xTest1, yTest1) > dist(x1, y1, xTest1, yTest1)
&& dist(minPos1[0], minPos1[1], xTest2, yTest2) > dist(x1, y1, xTest2, yTest2))
|| (dist(minPos2[0], minPos2[1], xTest1, yTest1) > dist(x2, y2, xTest1, yTest1)
&& dist(minPos2[0], minPos2[1], xTest2, yTest2) > dist(x2, y2, xTest2, yTest2))) {
return false;
}
return true;
}
function getAngleTwoPoints(pos1, pos2) { // returns the angle in rad from pos1 to pos2
var m = (pos2[1] - pos1[1]) / (pos2[0] - pos1[0]);
var a = Math.atan(m);
if (pos1[1] == pos2[1]) { // horizontal
if (pos1[0] < pos2[0]) {
return 0;
} else {
return Math.PI;
}
}
if (pos1[0] == pos2[0]) { // vertical
if (pos1[1] < pos2[1]) {
return Math.PI / 2;
} else {
return 3 * Math.PI / 2;
}
}
if (m > 0) {
if (pos2[0] > pos1[0]) {
return a;
} else {
return a + Math.PI;
}
} else {
if (pos2[0] > pos1[0]) {
return a;
} else {
return a + Math.PI;
}
}
}
function interpolateTwoPoints(pos1, pos2, proportion) {
if (typeof proportion == 'undefined')
proportion = 0.5;
return [pos1[0] + proportion * (pos2[0] - pos1[0]), pos1[1] + proportion * (pos2[1] - pos1[1])];
}
function angleToPos(angle, centerX, centerY, radius, opt_angleType) {
if (opt_angleType == 'degrees')
angle = angle * Math.PI / 180;
return [centerX + Math.cos(angle) * radius, centerY - Math.sin(angle) * radius];
}
function posToAngle(posX, posY, centerX, centerY, radius) {
if (un(radius))
radius = dist(posX, posY, centerX, centerY);
var cosTheta = (posX - centerX) / radius;
var sinTheta = (posY - centerY) / radius;
if (sinTheta >= 0)
return Math.acos(cosTheta);
if (sinTheta < 0)
return 2 * Math.PI - Math.acos(cosTheta);
}
function extendLine(pos1, pos2, extLength) {
var m = (pos2[1] - pos1[1]) / (pos2[0] - pos1[0]);
var angle = Math.atan(m);
if (pos1[1] == pos2[1]) { // horizontal
if (pos1[0] < pos2[0]) {
return [pos2[0] + extLength, pos2[1]];
} else {
return [pos2[0] - extLength, pos2[1]];
}
}
if (pos1[0] == pos2[0]) { // vertical
if (pos1[1] < pos2[1]) {
return [pos2[0], pos2[1] + extLength];
} else {
return [pos2[0], pos2[1] - extLength];
}
}
if (m > 0) {
if (pos2[0] >= pos1[0]) {
return ([pos2[0] + extLength * Math.cos(angle), pos2[1] + extLength * Math.sin(angle)]);
} else {
return ([pos2[0] - extLength * Math.cos(angle), pos2[1] - extLength * Math.sin(angle)]);
}
} else {
if (pos2[0] <= pos1[0]) {
return ([pos2[0] - extLength * Math.cos(angle), pos2[1] - extLength * Math.sin(angle)]);
} else {
return ([pos2[0] + extLength * Math.cos(angle), pos2[1] + extLength * Math.sin(angle)]);
}
}
}
function isPointOnEllipse(point, center, radiusX, radiusY, tolerance) {
if (typeof tolerance == 'undefined') {
if (roundToNearest(Math.pow((point[0] - center[0]) / radiusX, 2) + Math.pow((point[1] - center[1]) / radiusY, 2), 0.0001) == 1) {
return true;
} else {
return false;
}
} else {
var max = Math.pow((point[0] - center[0]) / (radiusX + tolerance), 2) + Math.pow((point[1] - center[1]) / (radiusY + tolerance), 2);
var min = Math.pow((point[0] - center[0]) / (radiusX - tolerance), 2) + Math.pow((point[1] - center[1]) / (radiusY - tolerance), 2);
if (Math.max(min, max) >= 1 && Math.min(min, max) <= 1) {
return true;
} else {
return false;
}
}
}
function isPointInEllipse(point, center, radiusX, radiusY) {
if (roundToNearest(Math.pow((point[0] - center[0]) / radiusX, 2) + Math.pow((point[1] - center[1]) / radiusY, 2), 0.0001) <= 1) {
return true;
} else {
return false;
}
}
function isPointInRect(point, left, top, width, height) {
if (point[0] >= left && point[0] <= left + width && point[1] >= top && point[1] <= top + height) {
return true;
} else {
return false;
}
}
function distPointToRect(point, left, top, width, height) {
if (isPointInRect(point, left, top, width, height) == true)
return 0;
return Math.min(
distancePointToLineSegment(point, [left, top], [left + width, top]),
distancePointToLineSegment(point, [left + width, top], [left + width, top + height]),
distancePointToLineSegment(point, [left + width, top + height], [left, top + height]),
distancePointToLineSegment(point, [left, top + height], [left + width, top]))
}
function lineCircleIntersections(linePoint1, linePoint2, circleCenter, circleRadius) {
var a = linePoint1[0];
var b = linePoint1[1];
var c = linePoint2[0];
var d = linePoint2[1];
var p = circleCenter[0];
var q = circleCenter[1];
var r = circleRadius;
var m = (d - b) / (c - a);
var s = m * m + 1;
var t = -2 * p + 2 * m * (-m * a + b - q);
var u = p * p + (-m * a + b - q) * (-m * a + b - q) - r * r;
var discrim = t * t - 4 * s * u;
if (discrim < 0) {
return [];
} else if (discrim == 0) {
var x = (-t + Math.sqrt(discrim)) / (2 * s);
var y = m * (x - a) + b;
if (x >= p) {
if (y >= q) {
var angle = Math.atan((y - q) / (x - p));
} else {
var angle = 2 * Math.PI + Math.atan((y - q) / (x - p));
}
} else {
var angle = Math.PI + Math.atan((y - q) / (x - p));
}
return [[x, y, angle]];
} else {
var x1 = (-t + Math.sqrt(discrim)) / (2 * s);
var y1 = m * (x1 - a) + b;
if (x1 >= p) {
if (y1 >= q) {
var angle1 = Math.atan((y1 - q) / (x1 - p));
} else {
var angle1 = 2 * Math.PI + Math.atan((y1 - q) / (x1 - p));
}
} else {
var angle1 = Math.PI + Math.atan((y1 - q) / (x1 - p));
}
var x2 = (-t - Math.sqrt(discrim)) / (2 * s);
var y2 = m * (x2 - a) + b;
if (x2 >= p) {
if (y2 >= q) {
var angle2 = Math.atan((y2 - q) / (x2 - p));
} else {
var angle2 = 2 * Math.PI + Math.atan((y2 - q) / (x2 - p));
}
} else {
var angle2 = Math.PI + Math.atan((y2 - q) / (x2 - p));
}
return [[x1, y1, angle1], [x2, y2, angle2]];
}
}
function circleIntersections(x0, y0, r0, x1, y1, r1) {
var a,
dx,
dy,
d,
h,
rx,
ry;
var x2,
y2;
/* dx and dy are the vertical and horizontal distances between the circle centers. */
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy * dy) + (dx * dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return [];
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return [];
}
/* 'point 2' is the point where the line through the circle intersection points crosses the line between the circle centers. */
/* Determine the distance from point 0 to point 2. */
a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2 * d);
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a / d);
y2 = y0 + (dy * a / d);
/* Determine the distance from point 2 to either of the intersection points. */
h = Math.sqrt((r0 * r0) - (a * a));
/* Now determine the offsets of the intersection points from point 2. */
rx = -dy * (h / d);
ry = dx * (h / d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xii = x2 - rx;
var yi = y2 + ry;
var yii = y2 - ry;
return [[xi, yi], [xii, yii]];
}
function isPointOnArc(point, arcPath, tolerance) {
if (!tolerance)
tolerance = 0.00001;
var rad = dist(point[0], point[1], arcPath.center[0], arcPath.center[1]);
if (rad < arcPath.radius - tolerance || rad > arcPath.radius + tolerance)
return false;
if (Math.abs(arcPath.startAngle - arcPath.finAngle) + 0.00001 >= 2 * Math.PI)
return true;
var angle = getAngleFromAToB(arcPath.center, point);
var angle1 = simplifyAngle(arcPath.startAngle);
var angle2 = simplifyAngle(arcPath.finAngle);
var a = arcPath.clockwise;
var b = angle1 < angle2;
var c = angle >= angle1 && angle <= angle2;
/*
if (a && b && c) return true;
if (a && b && !c) return false;
if (a && !b && c) return true;
if (a && !b && !c) return false;
if (!a && b && c) return false;
if (!a && b && !c) return true;
if (!a && !b && c) return false;
if (!a && !b && !c) return true;
/*/
if (a == true) {
if (b == true) {
if (c == true) {
return true;
} else {
return false;
}
} else {
if (c == true) {
return true;
} else {
return false;
}
}
} else {
if (b == true) {
if (c == true) {
return false;
} else {
return true;
}
} else {
if (c == true) {
return false;
} else {
return true;
}
}
}
//*/
}
function simplifyAngle(angle) {
while (angle < 0) {
angle += (2 * Math.PI)
};
angle = angle % (2 * Math.PI);
return angle;
}
function getAngleFromAToB(a, b) {
// angle as measured anticlockwise from positive x-direction (upside down on canvas) - A is in the centre
var angle = Math.atan((b[1] - a[1]) / (b[0] - a[0]));
if (b[0] >= a[0] && b[1] >= a[1])
return angle;
if (b[0] >= a[0] && b[1] < a[1])
return angle + 2 * Math.PI;
if (b[0] < a[0])
return angle + Math.PI;
}
function getEndPointsOfArc(arcPath) {
// if closed circle, return empty
if (Math.abs(arcPath.startAngle - arcPath.finAngle) % (2 * Math.PI) == 0)
return [];
return [
[arcPath.center[0] + arcPath.radius * Math.cos(arcPath.startAngle), arcPath.center[1] + arcPath.radius * Math.sin(arcPath.startAngle)],
[arcPath.center[0] + arcPath.radius * Math.cos(arcPath.finAngle), arcPath.center[1] + arcPath.radius * Math.sin(arcPath.finAngle)]
]
}
function vectorFromAToB(a, b) {
return [b[0] - a[0], b[1] - a[1]];
}
function snapToPoints(point, snapPoints, tolerance) {
var minDist = [];
for (var i = 0; i < snapPoints.length; i++) {
minDist.push(dist(point[0], point[1], snapPoints[i][0], snapPoints[i][1]));
}
if (arrayMin(minDist) < tolerance) {
return snapPoints[minDist.indexOf(arrayMin(minDist))].slice(0);
} else {
return point.slice(0);
}
}
function snapToGrid(point, snapGrid) {
//snapGrid should contain: {left,top,width,height,dx,dy}
}
function getBezierPoints(p1, p2, p3, density) {
if (!density)
density = 10;
var length = quadraticBezierLength(p1, p2, p3);
var points = [p1];
for (var i = density; i < length; i += density) {
points.push(getQuadraticCurvePoint(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], i / length));
}
points.push(p3);
return points;
}
function getQBezierValue(t, p1, p2, p3) { // called by getQuadraticCurvePoint, src: http://jsfiddle.net/QA6VG/
var iT = 1 - t;
return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
}
function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) { // called by getBezierPoints, src: http://jsfiddle.net/QA6VG/
return [getQBezierValue(position, startX, cpX, endX), getQBezierValue(position, startY, cpY, endY)];
}
function quadraticBezierLength(p0, p1, p2) { // called by getBezierPoints, src: https://gist.github.com/tunght13488/6744e77c242cc7a94859
function Point(x, y) {
this.x = x;
this.y = y;
}
var a = new Point(
p0[0] - 2 * p1[0] + p2[0],
p0[1] - 2 * p1[1] + p2[1]);
var b = new Point(
2 * p1[0] - 2 * p0[0],
2 * p1[1] - 2 * p0[1]);
var A = 4 * (a.x * a.x + a.y * a.y);
var B = 4 * (a.x * b.x + a.y * b.y);
var C = b.x * b.x + b.y * b.y;
var Sabc = 2 * Math.sqrt(A + B + C);
var A_2 = Math.sqrt(A);
var A_32 = 2 * A * A_2;
var C_2 = 2 * Math.sqrt(C);
var BA = B / A_2;
return (A_32 * Sabc + A_2 * B * (Sabc - C_2) + (4 * C * A - B * B) * Math.log((2 * A_2 + BA + Sabc) / (BA + C_2))) / (4 * A_32);
}
function drawTextBox(canvas, canvasctx, canvasData, backgroundColor, borderColor, borderWidth, font, textColor, textAlign, textLine1, textLine2) {
var polygon = false;
if (typeof canvasData[41] !== 'undefined')
polygon = true;
canvasctx.clearRect(0, 0, canvasData[2], canvasData[3]);
canvasctx.fillStyle = backgroundColor;
canvasctx.strokeStyle = borderColor;
canvasctx.lineWidth = borderWidth;
if (polygon == true) {
canvasctx.beginPath();
canvasctx.moveTo(canvasData[41][0][0], canvasData[41][0][1]);
for (j = 0; j < canvasData[41].length; j++) {
if (j > 0)
canvasctx.lineTo(canvasData[41][j][0], canvasData[41][j][1]);
}
canvasctx.closePath();
canvasctx.fill();
canvasctx.stroke();
} else {
if (backgroundColor !== '')
canvasctx.fillRect(0, 0, canvasData[2], canvasData[3]);
if (borderColor !== '')
canvasctx.strokeRect(0, 0, canvasData[2], canvasData[3]);
}
canvasctx.font = font;
canvasctx.fillStyle = textColor;
canvasctx.textAlign = textAlign;
canvasctx.textBaseline = "middle";
if (!textLine2) {
canvasctx.fillText(textLine1, 0.5 * canvasData[2], 0.5 * canvasData[3]);
} else {
canvasctx.fillText(textLine1, 0.5 * canvasData[2], 0.3 * canvasData[3]);
canvasctx.fillText(textLine2, 0.5 * canvasData[2], 0.7 * canvasData[3]);
}
}
function drawMathsTextBox(canvasctx, canvasData, textArray, object) {
// optional object can contain: backColor, algText, fontSize, borderColor, borderWidth, textColor, textAlign
if (!object) {
var fontSize = 0.45 * canvasData[3];
var horizAlign = 'center';
var algText = true;
var textColor = '#000';
var backgroundColor = '#6F9';
var borderColor = '#000';
var borderWidth = 4;
} else {
var fontSize = object.fontSize || 0.45 * canvasData[3];
var horizAlign = object.textAlign || 'center';
var algText = true;
if (typeof object.algText == 'boolean')
algText = object.algText;
var textColor = object.textColor || '#000';
var backgroundColor = object.backColor || '#6F9';
var borderColor = object.borderColor || '#000';
var borderWidth = object.borderWidth || 4;
}
var horizPos = 0.5 * canvasData[2];
if (horizAlign == 'left')
horizPos = 5;
if (horizAlign == 'right')
horizPos = canvasData[2] - 5;
canvasctx.clearRect(0, 0, canvasData[2], canvasData[3]);
canvasctx.fillStyle = backgroundColor;
canvasctx.strokeStyle = borderColor;
canvasctx.lineWidth = borderWidth;
canvasctx.fillRect(0, 0, canvasData[2], canvasData[3]);
canvasctx.strokeRect(borderWidth * 0.5, borderWidth * 0.5, canvasData[2] - borderWidth, canvasData[3] - borderWidth);
drawMathsText(canvasctx, textArray, fontSize, horizPos, 0.47 * canvasData[3], algText, [], horizAlign, 'middle', textColor, 'draw');
}
function wrapText(context, text, x, y, maxWidth, lineHeight, font, fillStyle) {
context.font = font;
context.fillStyle = fillStyle;
var words = text.split(' ');
var line = '';
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
context.fillText(line, x, y);
}
function drawFrac(context, font, x, y, horizAlign, numerator, denominator) {
context.save();
context.font = font;
var fractionWidth = Math.max(context.measureText(numerator).width, context.measureText(denominator).width);
context.textAlign = 'center';
context.textBaseline = 'bottom';
switch (horizAlign) {
case 'right':
context.fillText(numerator, x - 2 - 0.5 * fractionWidth, y - 5);
context.textBaseline = 'top';
context.fillText(denominator, x - 2 - 0.5 * fractionWidth, y + 5);
context.strokeStyle = textColor;
context.lineWidth = 3;
context.moveTo(x - 4 - fractionWidth, y);
context.lineTo(x, y);
break;
case 'center':
context.fillText(numerator, x, y - 5);
context.textBaseline = 'top';
context.fillText(denominator, x, y + 5);
context.strokeStyle = textColor;
context.lineWidth = 3;
context.moveTo(x - 2 - 0.5 * fractionWidth, y);
context.lineTo(x + 2 + 0.5 * fractionWidth, y);
break;
default:
context.fillText(numerator, x + 2 + 0.5 * fractionWidth, y - 5);
context.textBaseline = 'top';
context.fillText(denominator, x + 2 + 0.5 * fractionWidth, y + 5);
context.strokeStyle = textColor;
context.lineWidth = 3;
context.moveTo(x, y);
context.lineTo(x + 4 + fractionWidth, y);
break;
}
context.stroke();
context.restore();
}
function generateNormalData(mean, sd, n) {
var normal = gaussian(mean, sd);
var data = [];
var groups = [];
for (var i = 0; i < 8; i++)
groups[i] = {
min: roundToNearest(mean + (i - 4) * sd, 0.00001),
max: roundToNearest(mean + (i - 3) * sd, 0.00001),
frequency: 0
};
for (var i = 0; i < n; i++) {
var value = normal()
data.push(value);
for (var g = 0; g < 8; g++) {
if (value >= groups[g].min && value <= groups[g].max) {
groups[g].frequency++;
break;
}
}
}
return {
data: data,
grouped: groups
};
}
// returns a gaussian random function with the given mean and stdev.
function gaussian(mean, stdev) {
var y2;
var use_last = false;
return function () {
var y1;
if (use_last) {
y1 = y2;
use_last = false;
} else {
var x1,
x2,
w;
do {
x1 = 2.0 * Math.random() - 1.0;
x2 = 2.0 * Math.random() - 1.0;
w = x1 * x1 + x2 * x2;
} while (w >= 1.0);
w = Math.sqrt((-2.0 * Math.log(w)) / w);
y1 = x1 * w;
y2 = x2 * w;
use_last = true;
}
var retval = mean + stdev * y1;
if (retval > 0)
return retval;
return -retval;
}
}
function phi(x) { // for normal distribution
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var p = 0.3275911;
var sign = 1
if (x < 0)
sign = -1
x = Math.abs(x) / Math.sqrt(2)
// A&S formula 7.1.26
var t = 1 / (1 + p * x)
var y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x)
return 0.5 * (1 + sign * y)
}
function enlargeDash(dash, sf) {
if (typeof sf == 'undefined')
return dash;
for (var i = 0; i < dash.length; i++) {
dash[i] = dash[i] * sf;
}
return dash;
}
function numberToString(n, commaForThousand) {
var decPart = roundToNearest(n - Math.floor(n), 0.00001);
var str = Math.floor(n).toString();
if (str.length < 4 || (str.length == 4 && boolean(commaForThousand, false) == false)) {}
else {
var count = 0;
for (var i = str.length; i >= 1; i--) {
if (count == 3) {
str = str.slice(0, i) + ',' + str.slice(i);
count = 0;
}
count++;
}
}
if (decPart > 0) {
var str2 = String(decPart);
str = str + str2.slice(1);
}
return str;
}
function stringToNumber(str) {
var str = replaceAll(str, ',', '');
var str = replaceAll(str, ' ', '');
return parseInt(str);
}
function numberToWords(n, capFirstLetter) {
var string = n.toString(),
units,
tens,
scales,
start,
end,
chunks,
chunksLen,
chunk,
ints,
i,
word,
words,
and = 'and';
if (parseInt(string) === 0)
return 'zero';
units = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];
tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
scales = ['', 'thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion', 'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quatttuor-decillion', 'quindecillion', 'sexdecillion', 'septen-decillion', 'octodecillion', 'novemdecillion', 'vigintillion', 'centillion'];
start = string.length; /* Split user arguemnt into 3 digit chunks from right to left */
chunks = [];
while (start > 0) {
end = start;
chunks.push(string.slice((start = Math.max(0, start - 3)), end));
}
chunksLen = chunks.length; /* Check if function has enough scale words to be able to stringify the user argument */
if (chunksLen > scales.length) {
return '';
}
words = []; /* Stringify each integer in each chunk */
for (i = 0; i < chunksLen; i++) {
chunk = parseInt(chunks[i]);
if (chunk) {
/* Split chunk into array of individual integers */
ints = chunks[i].split('').reverse().map(parseFloat);
/* If tens integer is 1, i.e. 10, then add 10 to units integer */
if (ints[1] === 1) {
ints[0] += 10;
}
if (i > 0)
words.push(',');
/* Add scale word if chunk is not zero and array item exists */
if ((word = scales[i])) {
words.push(word);
}
/* Add unit word if array item exists */
if ((word = units[ints[0]])) {
words.push(word);
}
/* Add tens word if array item exists */
if ((word = tens[ints[1]])) {
words.push(word);
}
/* Add 'and' string after units or tens integer if: */
if (ints[0] || ints[1]) {
/* Chunk has a hundreds integer or chunk is the first of multiple chunks */
if (ints[2] || !i && chunksLen) {
words.push(and);
}
}
/* Add hundreds word if array item exists */
if ((word = units[ints[2]])) {
words.push(word + ' hundred');
}
}
}
var str = words.reverse().join(' ');
str = replaceAll(str, ' ,', ',');
str = replaceAll(str, ', and', ' and');
if (str.slice(0, 4) == 'and ')
str = str.slice(4);
if (str.slice(-1) == ',')
str = str.slice(0, -1);
if (boolean(capFirstLetter, true))
str = str.charAt(0).toUpperCase() + str.slice(1);
return str;
}