//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: ['<><>' + 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('<><><>'); 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:['<><>x'],color:'#CCF',minWidth:100,minHeight:70},{text:['<><>y'],color:'#CCF',minWidth:100,minHeight:70},{text:['<><><>z'],color:'#CCF',minWidth:100,minHeight:70}, ], [ // row 1{},{text:['2']},{text:['<>3']}, ], [ // row 2{text:['4']},{},{text:['<>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('<><><>'); 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('<><><>'); 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:['<><>x'],color:'#CCF',minWidth:100,minHeight:70},{text:['<><>y'],color:'#CCF',minWidth:100,minHeight:70},{text:['<><><>z'],color:'#CCF',minWidth:100,minHeight:70}, ], [ // row 1{},{text:['2']},{text:['<>3']}, ], [ // row 2{text:['4']},{},{text:['<>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('<><><>'); 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('<><><>'); 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: ['<>' + 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('<><><>'); 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 || [['<>win'], ['<>draw'], ['<>lose']] var probabilities = obj.probabilities || [['<>', ['frac', ['1'], ['4']]], ['<>', ['frac', ['1'], ['2']]], ['<>', ['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 || ['<>A']; var labelB = obj.labelB || ['<>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 || ['<>A']; var labelB = obj.labelB || ['<>B']; var labelC = obj.labelC || ['<>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: ['<><><><>' + num] }) } else if (typeof labels == 'object' && typeof labels[count] !== 'undefined') { text({ context: context, left: axisPos - 100, width: 200, top: top + 0.75 * height, textArray: ['<><><><>' + 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: ['<><><><>' + xLabel], left: left + width - 200, width: 200, top: top + height + 30, }); } if (arraysEqual(yLabel, ['']) == false) { text({ context: ctx, textArray: ['<><><><>' + yLabel], left: left - 50 - 200, width: 200, top: top + height * 0.03, }); } if (arraysEqual(title, ['']) == false) { text({ context: ctx, textArray: ['<><><><>' + 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; }