epicycles/main.js

1001 lines
33 KiB
JavaScript

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var graphColors = [
"#E57373","#F06292","#BA68C8","#9575CD","#7986CB",
"#64B5F6","#4FC3F7","#4DD0E1","#4DB6AC","#81C784",
"#AED581","#CDDC39","#FFEB3B","#FFD54F","#FFB74D",
"#FF8A65"
];
var allSets = [];
var calcIndex;
var loadStart = null;
var loadFrame;
var percent = 0;
var f = new FontFace('Poppins', 'url(https://fonts.gstatic.com/s/poppins/v5/pxiEyp8kv8JHgFVrJJbecmNE.woff2)');
var fourier = new Worker("fourier.js");
var grapher = new Worker("grapher.js");
$(document).ready(function() {
var dim = document.getElementById("graph").getBoundingClientRect();
canvas.width = dim.width;
canvas.height = dim.height;
f.load().then(function() {
drawGraphBase();
});
});
document.onclick = function() {
var close = document.getElementsByClassName("colSel");
for(var i = 0; i < close.length; i++) {
var div = close[i];
div.style.opacity = "0";
setTimeout(function() {
div.style.display = "none";
}, 300);
}
}
fourier.onmessage = function(e) {
var result = e.data;
if(result.length === undefined) {
percent = result;
} else {
var thisSet = allSets[calcIndex];
thisSet.eq = result;
thisSet.calcArray = [];
grapher.postMessage(result);
var termDiv = document.getElementsByClassName("terms")[0];
var eqDiv = document.getElementsByClassName("eqs")[0];
var termNum = Object.keys(thisSet.eq[0][0]).length-1;
termDiv.textContent = termNum + " term" + ((termNum === 1) ? "" : "s");
var eqNum = thisSet.eq.length;
eqDiv.textContent = eqNum + " equation" + ((eqNum === 1) ? "" : "s");
var disable = ["graphOp", "viewEqOp", "viewFormOp", "exportData"];
for(var i = 0; i < disable.length; i++) {
var d = document.getElementById(disable[i]);
if(d === null) continue;
d.className = d.className.replace(" disabled", "");
}
if(eqNum > 1) {
var div = document.getElementById("viewFormOp");
div.children[0].textContent = "View equations";
div.onclick = function() {
viewAllEq(calcIndex);
}
}
}
}
grapher.onmessage = function(e) {
var result = e.data;
if(result.length === undefined) {
percent = result;
} else {
var thisSet = allSets[calcIndex];
thisSet.calcArray.push(result);
if(thisSet.calcArray.length === thisSet.eq.length) {
var range = thisSet.range;
var ratio = (canvas.height > canvas.width) ? canvas.height/canvas.width : canvas.width/canvas.height;
var r = [1.1*ratio*range[0]/2, 1.1*ratio*range[1]/2];
var [scale, axes] = drawGraphBase([[-r[0], r[0]],[-r[1], r[1]]]);
window.cancelAnimationFrame(loadFrame);
for(var i = 0; i < thisSet.eq.length; i++) {
drawEquation(thisSet.calcArray[i], thisSet.calcArray[i].length-2, scale, axes);
}
percent = 0;
}
}
}
document.querySelectorAll("#clickFile input")[0].onchange = function() {
var that = this;
loadSVGStatus(true);
setTimeout(function() {
var file = that.files[0];
if(file.name.search(".svg") === -1) {
alert("This file is not an svg! Please select another file.");
loadSVGStatus(false);
return;
}
var reader = new FileReader();
reader.onload = function() {
var el = document.createElement("div");
el.innerHTML = reader.result;
svg = Array.from(el.getElementsByTagName("path")).map(a=>a.getAttribute("d").replace(/(\n|\t)/g,"")).map(a=>a[0].toUpperCase() + a.substring(1)).reduce((a,b)=>a.concat(b));
var [paths, range] = processSVG(svg);
var oneSet = {
"name": file.name.replace(".svg","").substring(0,15),
"color": graphColors[Math.floor(Math.random()*graphColors.length)],
"paths": paths,
"range": range,
"eq": [],
"calcArray": [],
};
allSets.push(oneSet);
updateSidebar();
}
reader.readAsText(file);
}, 10);
}
function fileDrop(e) {
e.preventDefault();
document.getElementById("setCont").className = "";
if(!e.dataTransfer.items) return;
console.log(e.dataTransfer.items)
var diff = allSets.length;
for (var i = 0; i < e.dataTransfer.items.length; i++) {
var thisItem = e.dataTransfer.items[i];
if (thisItem.kind === 'file') {
loadSVGStatus(true);
setTimeout(function() {
var file = thisItem.getAsFile();
if(file.name.search(".svg") === -1) {
alert("This file is not an svg! Please select another file.");
loadSVGStatus(false);
return;
}
var reader = new FileReader();
reader.onload = function() {
var el = document.createElement("div");
el.innerHTML = reader.result;
svg = Array.from(el.getElementsByTagName("path")).map(a=>a.getAttribute("d").replace(/(\n|\t)/g,"")).reduce((a,b)=>a.concat(b));
var [paths, range] = processSVG(svg);
var oneSet = {
"name": file.name.replace(".svg",""),
"color": graphColors[Math.floor(Math.random()*graphColors.length)],
"paths": paths,
"range": range,
"eq": [],
"calcArray": []
};
allSets.push(oneSet);
if(allSets.length-diff === e.dataTransfer.items.length) updateSidebar();
}
reader.readAsText(file);
}, 10);
}
}
e.dataTransfer.items.clear();
}
function fileDrag(e) {
e.preventDefault();
document.getElementById("setCont").className = "ondrag";
}
function fileDragLeave(e) {
e.preventDefault();
document.getElementById("setCont").className = "";
}
function loadSVGStatus(processing) {
var p = document.querySelectorAll("#clickFile p")[0];
var i = document.querySelectorAll("#clickFile i")[0];
if(processing) {
p.textContent = "Processing...";
i.className = "fas fa-circle-notch spin";
} else {
p.textContent = "Drag or browse for a file!";
i.className = "fas fa-file-upload";
}
}
function updateSidebar() {
loadSVGStatus(false);
var setCont = document.getElementById("setCont");
setCont.style.opacity = "0";
setTimeout(function() {
while(setCont.children[0]) setCont.removeChild(setCont.children[0]);
allSets.forEach(function(a,i) {
var div = document.createElement("div");
div.className = "set transition";
div.onclick = function() {
let counter = i;
setInfo(counter);
}
var sel = document.createElement("div");
sel.className = "selected";
var col = document.createElement("div");
col.className = "color";
var h2 = document.createElement("h2");
h2.appendChild(document.createTextNode(i+1));
var circ = document.createElement("div");
circ.className = "transition";
circ.style.backgroundColor = a.color;
var colSel = document.createElement("div");
colSel.className = "colSel transition";
for(var j = 0; j < graphColors.length; j++) {
var col1 = document.createElement("div");
col1.className = "transition";
col1.style.backgroundColor = graphColors[j];
col1.onclick = function(e) {
e.stopPropagation();
let counter = i;
var parent = this.parentNode;
var thisSet = allSets[counter];
parent.parentNode.style.backgroundColor = this.style.backgroundColor;
thisSet.color = this.style.backgroundColor;
parent.style.opacity = "0";
if(thisSet.calcArray.length > 0 && calcIndex === counter) {
var range = thisSet.range;
var ratio = (canvas.height > canvas.width) ? canvas.height/canvas.width : canvas.width/canvas.height;
var r = [1.1*ratio*range[0]/2, 1.1*ratio*range[1]/2];
var [scale, axes] = drawGraphBase([[-r[0], r[0]],[-r[1], r[1]]]);
window.cancelAnimationFrame(loadFrame);
for(var k = 0; k < thisSet.eq.length; k++) {
drawEquation(thisSet.calcArray[k], thisSet.calcArray[k].length-2, scale, axes);
}
}
setTimeout(function() {
parent.style.display = "none";
},300);
}
colSel.appendChild(col1);
}
circ.onclick = function(e) {
e.stopPropagation();
var innerDiv = this.children[0];
innerDiv.style.display = "grid";
setTimeout(function() {
innerDiv.style.opacity = "1";
}, 10);
}
circ.appendChild(colSel);
col.appendChild(h2);
col.appendChild(circ);
var name = document.createElement("h2");
name.className = "name";
name.appendChild(document.createTextNode(a.name));
div.appendChild(sel);
div.appendChild(col);
div.appendChild(name);
var terms = document.createElement("p");
terms.className = "terms";
var eqs = document.createElement("p");
eqs.className = "eqs";
if(a.eq.length !== 0) {
var termNum = Object.keys(a.eq[0][0]).length-1;
terms.appendChild(document.createTextNode(termNum + " term" + ((termNum === 1) ? "" : "s")));
var eqNum = a.eq.length;
eqs.appendChild(document.createTextNode(eqNum + " equation" + ((eqNum === 1) ? "" : "s")));
}
div.appendChild(terms);
div.appendChild(eqs);
setCont.appendChild(div);
});
setCont.style.opacity = "1";
}, 400);
}
function setInfo(index) {
calcIndex = index;
var options = [
{
name: "Calculate",
id: "calcOp",
input: true,
inputRange: [1, 1500],
inputDefValue: 70,
onclick: function() {
var terms = parseInt(this.children[1].value);
loadFrame = window.requestAnimationFrame(loading);
fourier.postMessage([allSets[index].paths, terms]);
var gInp = document.getElementById("graphOp").children[1];
gInp.setAttribute("min", "1");
gInp.setAttribute("max", terms);
gInp.value = terms;
this.setAttribute("min", terms);
}
},
{
name: "Graph",
id: "graphOp",
input: true,
inputRange: [1,1500],
inputDefValue: 1,
disableDef: true,
onclick: function() {
var thisSet = allSets[index];
var range = thisSet.range;
var ratio = (canvas.height > canvas.width) ? canvas.height/canvas.width : canvas.width/canvas.height;
var r = [1.1*ratio*range[0]/2, 1.1*ratio*range[1]/2];
var [scale, axes] = drawGraphBase([[-r[0], r[0]],[-r[1], r[1]]]);
for(var i = 0; i < thisSet.eq.length; i++) {
drawEquation(thisSet.calcArray[i], this.children[1].value, scale, axes);
}
}
}
];
var viewForm = {
name: "View formula",
id: "viewFormOp",
input: false,
disableDef: true,
onclick: function() {
viewMath(index, 0);
}
};
var viewEq = {
name: "View equations",
id: "viewEqOp",
input: false,
disableDef: true,
onclick: function() {
viewAllEq(index);
}
};
var exportDiv = {
name: "Export data",
id: "exportData",
input: false,
disableDef: true,
onclick: function() {
var thisSet = allSets[index];
var data = {
name: thisSet.name,
paths: thisSet.paths,
constants: thisSet.eq
};
try {
var blob = new Blob([JSON.stringify(data)], {type: "text/plain;charset=utf-8"});
} catch(err) {
var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
var bb = new BlobBuilder();
bb.append((new XMLSerializer).serializeToString(data));
var blob = bb.getBlob("text/plain;charset=utf-8");
}
saveAs(blob, allSets[index].name+"_data.txt");
}
};
var back = {
name: "Back",
id: "backOp",
input: false,
onclick: function() {
updateSidebar();
}
};
if(allSets[index].eq.length > 1) {
options.push(viewEq);
} else {
options.push(viewForm);
}
options.push(exportDiv);
options.push(back);
var setCont = document.getElementById("setCont");
var selectedSet = setCont.children[index];
setCont.style.opacity = "0";
setTimeout(function() {
while(setCont.children[0]) setCont.removeChild(setCont.children[0]);
setCont.appendChild(selectedSet);
for(var i = 0; i < options.length; i++) {
var op = document.createElement("div");
op.className = "option transition" + ((options[i].disableDef && allSets[index].eq.length === 0) ? " disabled" : "");
op.id = options[i].id;
var p = document.createElement("p");
p.appendChild(document.createTextNode(options[i].name));
op.appendChild(p);
if(options[i].input) {
var inp = document.createElement("input");
inp.setAttribute("type", "number");
inp.setAttribute("min", options[i].inputRange[0]);
inp.setAttribute("max", options[i].inputRange[1]);
inp.onkeyup = function(e) {
if(e.keyCode === 13) this.parentNode.click();
}
inp.className = "transition";
inp.oninput = function() {
var val = parseInt(this.value);
this.value = Math.floor(val);
if(val < parseInt(this.getAttribute("min"))) this.value = this.getAttribute("min");
if(val > parseInt(this.getAttribute("max"))) this.value = this.getAttribute("max");
}
inp.onclick = function(e) {
e.stopPropagation();
}
var thisSet = allSets[index];
if(thisSet.eq.length !== 0) {
inp.value = Object.keys(thisSet.eq[0][0]).length-1;
} else {
inp.value = options[i].inputDefValue;
}
op.appendChild(inp);
var p2 = document.createElement("p");
p2.appendChild(document.createTextNode("terms!"));
op.appendChild(p2);
}
op.onclick = options[i].onclick;
setCont.appendChild(op);
}
setTimeout(function() {
setCont.style.opacity = "1";
}, 300);
}, 300);
}
function viewMath(index, index2) {
var mathContIm = document.createElement("div");
mathContIm.id = "mathContIm";
var mathContRe = document.createElement("div");
mathContRe.id = "mathContRe";
var thisEq = allSets[index].eq[index2];
var options = [
{
name: "Form: Imaginary",
id: "mathType",
input: false,
onclick: function() {
var mc = [document.getElementById("mathContIm"), document.getElementById("mathContRe")];
if(this.textContent.search("Imag") !== -1) {
this.children[0].textContent = "Form: Parametric";
mc[0].style.opacity = "0";
setTimeout(function() {
mc[0].style.display = "none";
mc[1].style.display = "block";
setTimeout(function() {
mc[1].style.opacity = "1";
}, 10);
}, 300);
} else {
this.children[0].textContent = "Form: Imaginary";
mc[1].style.opacity = "0";
setTimeout(function() {
mc[1].style.display = "none";
mc[0].style.display = "block";
setTimeout(function() {
mc[0].style.opacity = "1";
}, 10);
}, 300);
}
}
},
{
name: "Copy",
id: "copyButton",
input: true,
inputRange: [1, Object.keys(thisEq[0]).length],
inputDefValue: 1,
disableDef: true,
onclick: function() {
var inp = document.getElementById("copyBox");
var type = (document.getElementById("mathType").textContent.search("Imag") !== -1) ? "im" : "re";
inp.value = toLatex(thisEq, type, this.children[1].value);
inp.select();
document.execCommand("copy");
}
},
{
insert: true,
insertDiv: mathContIm
},
{
insert: true,
insertDiv: mathContRe
},
{
name: "Back",
id: "backOp",
input: false,
onclick: function() {
if(index2 === 0) {
setInfo(0);
} else {
setInfoEq(index, index2);
}
}
}
];
var setCont = document.getElementById("setCont");
var selectedSet = setCont.children[0];
setCont.style.opacity = "0";
setTimeout(function() {
while(setCont.children[0]) setCont.removeChild(setCont.children[0]);
setCont.appendChild(selectedSet);
var eqKeys = Object.keys(thisEq[0]);
for(var i = 0; i < eqKeys.length-1; i++) {
var j = ((i+1)%2===1) ? Math.ceil((i+1)/2) : -Math.floor((i+1)/2);
var num = (j === 1) ? "" :j;
var end = (i === eqKeys.length-2) ? "" : "+";
var add = (thisEq[1][eqKeys[i]] < 1) ? "-" : "+";
var sub = (thisEq[1][eqKeys[i]] < 1) ? "+" : "-";
var oneTermIm = document.createElement("div");
var oneTermRe = document.createElement("div");
oneTermIm.textContent = "$$\\left(" + thisEq[0][j] + add + Math.abs(thisEq[1][j]) + "i\\right)e^{" + num + "it}" + end + "$$" ;
if(i === 0) {
oneTermRe.textContent = "$$\\left\\{\\begin{matrix}x=" + thisEq[0][j] + "\\cos\\left(" + num + "t\\right)" + sub + Math.abs(thisEq[1][j]) + "\\sin\\left(" + num + "t\\right)"
+ "+\\\\y=" + thisEq[0][j] + "\\sin\\left(" + num + "t\\right)" + add + Math.abs(thisEq[1][j]) + "\\cos\\left(" + num + "t\\right)+\\end{matrix}\\right.$$";
} else {
oneTermRe.textContent = "$$"+ thisEq[0][j] + "\\cos\\left(" + num + "t\\right)" + sub + Math.abs(thisEq[1][j]) + "\\sin\\left(" + num + "t\\right)"
+ "+\\\\" + thisEq[0][j] + "\\sin\\left(" + num + "t\\right)" + add + Math.abs(thisEq[1][j]) + "\\cos\\left(" + num + "t\\right)+$$"
}
mathContIm.appendChild(oneTermIm);
mathContRe.appendChild(oneTermRe);
}
for(var i = 0; i < options.length; i++) {
if(options[i].insert) {
setCont.appendChild(options[i].insertDiv);
} else {
var op = document.createElement("div");
op.className = "option transition";
op.id = options[i].id;
var p = document.createElement("p");
p.appendChild(document.createTextNode(options[i].name));
op.appendChild(p);
if(options[i].input) {
var inp = document.createElement("input");
inp.setAttribute("type", "number");
inp.setAttribute("min", options[i].inputRange[0]);
inp.setAttribute("max", options[i].inputRange[1]);
inp.onkeyup = function(e) {
if(e.keyCode === 13) this.parentNode.click();
}
inp.className = "transition";
inp.oninput = function() {
var val = parseInt(this.value);
this.value = Math.floor(val);
if(val < parseInt(this.getAttribute("min"))) this.value = this.getAttribute("min");
if(val > parseInt(this.getAttribute("max"))) this.value = this.getAttribute("max");
}
inp.onclick = function(e) {
e.stopPropagation();
}
var thisSet = allSets[index];
if(thisSet.eq.length !== 0) {
inp.value = Object.keys(thisSet.eq[0][0]).length-1;
} else {
inp.value = options[i].inputDefValue;
}
op.appendChild(inp);
var p2 = document.createElement("p");
p2.appendChild(document.createTextNode("terms!"));
op.appendChild(p2);
}
op.onclick = options[i].onclick;
setCont.appendChild(op);
}
}
MathJax.Hub.Queue(["Typeset",MathJax.Hub,"mathCont"]);
setTimeout(function() {
setCont.style.opacity = "1";
}, 300);
}, 300);
}
function viewAllEq(index) {
var setCont = document.getElementById("setCont");
var selectedSet = setCont.children[0];
setCont.style.opacity = "0";
setTimeout(function() {
while(setCont.children[0]) setCont.removeChild(setCont.children[0]);
setCont.appendChild(selectedSet);
allSets[index].eq.forEach(function(a,i) {
var div = document.createElement("div");
div.className = "option";
div.onclick = function() {
setInfoEq(index, i);
};
var p = document.createElement("p");
p.appendChild(document.createTextNode("Equation " + (i+1)));
div.appendChild(p);
setCont.appendChild(div);
});
var div = document.createElement("div");
div.className = "option";
div.onclick = function() {
setInfo(calcIndex);
}
var p = document.createElement("p");
p.appendChild(document.createTextNode("Back"));
div.appendChild(p);
setCont.appendChild(div);
setTimeout(function() {
setCont.style.opacity = "1";
}, 300);
}, 400);
}
function setInfoEq(index1, index2) {
var termNum = Object.keys(allSets[index1].eq[0][0]).length - 1;
var options = [
{
name: "Graph",
id: "graphOp",
input: true,
inputRange: [1, termNum],
inputDefValue: termNum,
onclick: function() {
var thisSet = allSets[index1];
var thisPath = allSets[index1].paths[index2];
var flat = thisPath.reduce((a,b)=>a.concat(b));
flat = flat[0].map((a,i)=>flat.map(b=>b[i]));
var range = [Math.max.apply(null, flat[0]), Math.max.apply(null, flat[1])];
thisPath = thisPath.map(b=>b.map(c=>[c[0]-range[0]/2, -1*(c[1]-range[1]/2)]));
var thisCalcArray = new Array(thisSet.calcArray[index2].length);
var thisTerm = thisSet.calcArray[index2][this.children[1].value];
flat = thisTerm[0].map((a,i)=>thisTerm.map(b=>b[i]));
var min = [Math.min.apply(null, flat[0]), Math.min.apply(null, flat[1])]
var range = [
Math.abs(Math.max.apply(null, flat[0])-min[0]),
Math.abs(Math.max.apply(null, flat[1])-min[1])
];
thisTerm = thisTerm.map(c=>[c[0]-min[0]-range[0]/2, c[1]-min[1]-range[1]/2]);
thisCalcArray[this.children[1].value] = thisTerm;
var ratio = (canvas.height > canvas.width) ? canvas.height/canvas.width : canvas.width/canvas.height;
var r = [1.1*ratio*range[0]/2, 1.1*ratio*range[1]/2];
var [scale, axes] = drawGraphBase([[-r[0], r[0]],[-r[1], r[1]]]);
drawEquation(thisCalcArray, this.children[1].value, scale, axes);
}
},
{
name: "View formula",
id: "viewFormOp",
input: false,
onclick: function() {
viewMath(index1, index2);
}
},
{
name: "Back",
id: "backOp",
input: false,
onclick: function() {
viewAllEq(index1);
}
}
];
var setCont = document.getElementById("setCont");
var selectedSet1 = setCont.children[0];
var selectedSet2 = setCont.children[index2+1];
selectedSet2.onclick = function() {};
setCont.style.opacity = "0";
setTimeout(function() {
while(setCont.children[0]) setCont.removeChild(setCont.children[0]);
setCont.appendChild(selectedSet1);
setCont.appendChild(selectedSet2);
for(var i = 0; i < options.length; i++) {
var op = document.createElement("div");
op.className = "option transition";
op.id = options[i].id;
var p = document.createElement("p");
p.appendChild(document.createTextNode(options[i].name));
op.appendChild(p);
if(options[i].input) {
var inp = document.createElement("input");
inp.setAttribute("type", "number");
inp.setAttribute("min", options[i].inputRange[0]);
inp.setAttribute("max", options[i].inputRange[1]);
inp.onkeyup = function(e) {
if(e.keyCode === 13) this.parentNode.click();
}
inp.className = "transition";
inp.oninput = function() {
var val = parseInt(this.value);
this.value = Math.floor(val);
if(val < parseInt(this.getAttribute("min"))) this.value = this.getAttribute("min");
if(val > parseInt(this.getAttribute("max"))) this.value = this.getAttribute("max");
}
inp.onclick = function(e) {
e.stopPropagation();
}
inp.value = options[i].inputDefValue;
op.appendChild(inp);
var p2 = document.createElement("p");
p2.appendChild(document.createTextNode("terms!"));
op.appendChild(p2);
}
op.onclick = options[i].onclick;
setCont.appendChild(op);
}
setTimeout(function() {
setCont.style.opacity = "1";
}, 300);
}, 300);
}
function drawEquation(calcArray, cycles, scale, axes) {
var arr = calcArray[cycles];
ctx.beginPath();
ctx.lineJoin = "round";
ctx.strokeStyle = allSets[calcIndex].color;
ctx.lineWidth = 2;
ctx.moveTo(axes[0]+arr[0][0]*scale, axes[1]-arr[0][1]*scale);
for(var i = 1; i < arr.length; i++) {
ctx.lineTo(axes[0]+arr[i][0]*scale, axes[1]-arr[i][1]*scale);
}
ctx.lineTo(axes[0]+arr[0][0]*scale, axes[1]-arr[0][1]*scale);
ctx.stroke();
}
function drawGraphBase(win) {
var win = win || [[-55,55], [-55,55]];
var range = [Math.abs(win[0][0]-win[0][1]), Math.abs(win[1][0]-win[1][1])];
var largeRange, which, winWhich;
if(range[0] > range[1]) {
largeRange = range[0];
which = "width";
winWhich = 0;
} else {
largeRange = range[1];
which = "height";
winWhich = 1;
}
var nonRound = largeRange/13;
var log = Math.ceil(Math.log10(nonRound));
var scale = (Math.abs(nonRound-Math.pow(10, log)) > Math.abs(nonRound-5*Math.pow(10, log-1))) ? 5*Math.pow(10, log-1) : Math.pow(10, log);
var ppu = canvas[which]/largeRange;
var unit = Math.floor(ppu*scale);
var realAxes = [];
var axes = [];
var offset = [];
if(win[0][0] >= 0 && win[0][1] >= 0) {
axes[0] = .1 * canvas.width;
offset[0] = Math.min.apply(null, win[0].map((a=>Math.floor(Math.abs(a)))));
realAxes[0] = axes[0] + ppu*(scale-offset[0]);
} else if(win[0][0] <= 0 && win[0][1] <= 0) {
axes[0] = .9 *canvas.width;
offset[0] = -Math.min.apply(null, win[0].map((a=>Math.floor(Math.abs(a)))));
realAxes[0] = axes[0] + ppu*(scale-offset[0]);
} else {
axes[0] = Math.abs(win[winWhich][0]/largeRange) * canvas.width;
offset[0] = 0;
realAxes[0] = axes[0];
}
if(win[1][0] >= 0 && win[1][1] >= 0) {
axes[1] = .9 * canvas.height;
offset[1] = Math.min.apply(null, win[1].map((a=>Math.floor(Math.abs(a)))));
realAxes[1] = axes[1] + ppu*(scale-offset[1]);
} else if(win[0][0] <= 0 && win[1][1] <= 0) {
axes[1] = .1 *canvas.height;
offset[1] = -Math.min.apply(null, win[1].map((a=>Math.floor(Math.abs(a)))));
realAxes[1] = axes[1] + ppu*(scale-offset[1]);
} else {
axes[1] = Math.abs(win[winWhich][1]/largeRange) * canvas.height;
offset[1] = 0;
realAxes[1] = axes[1];
}
ctx.clearRect(0,0,canvas.width,canvas.height);
// Sub-intervals
ctx.lineWidth = 1;
ctx.strokeStyle = "#E0E0E0";
ctx.beginPath();
for(var i = -unit*4; i <= unit*4; i++) {
if(i === 0) continue;
var x = axes[0] + ppu*i*scale/5;
var y = axes[1] + ppu*i*scale/5;
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
ctx.closePath();
// Intervals
ctx.lineWidth = 1;
ctx.strokeStyle = "#BDBDBD";
ctx.beginPath();
for(var i = -unit; i <= unit; i++) {
if(i === 0) continue;
var x = axes[0] + ppu*i*scale;
var y = axes[1] - ppu*i*scale;
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
ctx.closePath();
// Axes
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#424242";
ctx.moveTo(axes[0], 0);
ctx.lineTo(axes[0], canvas.height);
ctx.moveTo(0, axes[1]);
ctx.lineTo(canvas.width, axes[1]);
ctx.stroke();
ctx.closePath();
// Texts
var textOffset = canvas.width/75;
ctx.fillStyle = "#000";
for(var i = -unit; i <= unit; i++) {
var x = axes[0] + ppu*i*scale;
var y = axes[1] - ppu*i*scale;
ctx.font = (canvas.width/75).toString() + "px Poppins";
ctx.textBaseline = "middle";
if(i === 0) {
ctx.fillText((i*scale).toString(), axes[0]-textOffset/2, y+textOffset);
} else {
ctx.textAlign = "center";
ctx.fillText((i*scale + Math.floor(offset[0])).toString(), x, axes[1]+textOffset);
ctx.textAlign = "end";
ctx.fillText((i*scale + Math.floor(offset[1])).toString(), axes[0]-textOffset/2, y);
}
}
return [ppu, realAxes];
}
function loading(t) {
if(!loadStart) loadStart = t;
var circTime = 700;
var progress = t - loadStart;
progress = progress - Math.floor(progress/(3.5*circTime))*3.5*circTime;
ctx.clearRect(0,0,canvas.width,canvas.height)
var r = 0.05*canvas.width;
var beg = 1.5*Math.PI;
var circProg = progress/circTime - Math.floor(progress/circTime);
var cent = [canvas.width/2, canvas.height*.35];
var animProg = beg - (3*Math.pow(circProg,2)-2*Math.pow(circProg,3))*2*Math.PI;
animProg = (animProg < 0) ? 2*Math.PI+animProg : animProg;
ctx.lineWidth = 4;
ctx.strokeStyle = "#000";
ctx.fillStyle = "#000";
if(progress < circTime) {
ctx.beginPath();
ctx.arc(cent[0]-3.5*r, cent[1], r, beg, animProg, true);
ctx.stroke();
} else if(progress < 2*circTime) {
ctx.beginPath();
ctx.arc(cent[0]-3.5*r, cent[1], r, 0, 2*Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(cent[0], cent[1], r, beg, animProg, true);
ctx.stroke();
} else if(progress < 3*circTime) {
ctx.beginPath();
ctx.arc(cent[0]-3.5*r, cent[1], r, 0, 2*Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(cent[0], cent[1], r, 0, 2*Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(cent[0]+3.5*r, cent[1], r, beg, animProg, true);
ctx.stroke();
} else if(progress <= 4*circTime) {
var newR = r-3*r*Math.pow(2*circProg,2)+2*r*Math.pow(2*circProg,3);
ctx.beginPath();
ctx.arc(cent[0]-3.5*r, cent[1], newR, 0, 2*Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(cent[0], cent[1], newR, 0, 2*Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(cent[0]+3.5*r, cent[1], newR, 0, 2*Math.PI, true);
ctx.stroke();
}
ctx.textAlign = "center";
ctx.baseLine = "middle";
ctx.font = (canvas.width/50).toString() + "px Poppins";
ctx.fillText("Generating equations, this may take a while.", canvas.width/2, canvas.height/2);
var rectWidth = canvas.width*0.7;
var edge = (canvas.width-rectWidth)/2;
ctx.rect(edge, canvas.height*.65, rectWidth, rectWidth*.05);
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle="#4CAF50";
ctx.fillRect(edge+2, canvas.height*.65+2, percent*(rectWidth-4), rectWidth*.05-4);
loadFrame = window.requestAnimationFrame(loading);
}
function toLatex(eq, type, amount) {
var eqKeys = Object.keys(eq[0]);
amount = amount || eqKeys.length-1;
var final = "";
for(var i = 0; i < amount; i++) {
var j = ((i+1)%2===1) ? Math.ceil((i+1)/2) : -Math.floor((i+1)/2);
var num = (j === 1) ? "" : j;
var end = (i === eqKeys.length-2) ? "" : "+";
var add = (eq[1][eqKeys[i]] < 1) ? "-" : "+";
var sub = (eq[1][eqKeys[i]] < 1) ? "+" : "-";
if(type === "im") {
final += "\\left(" + eq[0][j] + add + Math.abs(eq[1][j]) + "i\\right)e^{" + num + "it}" + end;
} else {
if(i === 0) {
final += "\\left\\{\\begin{matrix}x=" + eq[0][j] + "\\cos\\left(" + num + "t\\right)" + sub + Math.abs(eq[1][j]) + "\\sin\\left(" + num + "t\\right)"
+ "+\\\\y=" + eq[0][j] + "\\sin\\left(" + num + "t\\right)" + add + Math.abs(eq[1][j]) + "\\cos\\left(" + num + "t\\right)+\\end{matrix}\\right.";
} else {
final += eq[0][j] + "\\cos\\left(" + num + "t\\right)" + sub + Math.abs(eq[1][j]) + "\\sin\\left(" + num + "t\\right)"
+ "+\\\\" + eq[0][j] + "\\sin\\left(" + num + "t\\right)" + add + Math.abs(eq[1][j]) + "\\cos\\left(" + num + "t\\right)+"
}
}
}
return final;
}
function processSVG(svg) {
var commLength = {
"M": 1, "L": 1, "H": 1, "V": 1,
"Q": 2, "C": 3, "T": 1, "S": 2,
"A": 3
};
var allPaths = svg.replace(/z/gi,"").split(/(?=M)/gi);
console.log(allPaths.length);
var lastPoint = [0,0];
for(var k = 0; k < allPaths.length; k++) {
var processed = [];
var arr = allPaths[k].split(/(?=[a-z])/gi);
if(arr.length === 0) continue;
var commList = [];
for(var i = 0; i < arr.length; i++) {
var command = arr[i].match(/[a-z]/gi)[0];
var values = arr[i].match(/(-)?([0-9]+)(\.([0-9])+)?/gi);
if(command.toUpperCase() !== "A") {
values = [values.filter((a,i)=>i%2===0),values.filter((a,i)=>i%2===1)].map(a=>a.map(b=>parseFloat(b)));
values = values[0].map((a,i)=>[a, (values[1][i]||0)]);
if(command.toUpperCase() === "V") values.reverse();
} else {
alert("The A path command in the SVG is not supported! Please modify or change the SVG file to not contain the A command.")
/*values = [
values.filter((a,i)=>i%7===0||i%4===1),
values.filter((a,i)=>i%7===2||i%7===3||i%7===4),
values.filter((a,i)=>i%7===5||i%7===6)
].map(a=>a.map(b=>parseFloat(b)));*/
}
var len = commLength[command.toUpperCase()];
if(values.length !== len) {
for(var j = 0; j < values.length/len; j++) {
commList.push([command].concat(values.slice(len*j, len*(j+1))));
}
} else {
commList.push([command].concat(values));
}
}
console.log([commList, k]);
for(var i = 0; i < commList.length; i++) {
var command = commList[i][0];
var rel = false;
if(command === command.toLowerCase()) {
rel = true;
command = command.toUpperCase();
}
var points = commList[i].slice(1);
var offset = relOff(arrOp(lastPoint, "*", rel), points);
if(command === "M") {
lastPoint = offset[0];
console.log(lastPoint);
} else if(command === "T" || command === "S") {
var lastSet = processed[processed.length-1] || [lastPoint];
var len = lastSet.length;
if(len < 3) {
var newBez = lastPoint;
} else {
var newBez = arrOp(arrOp(2, "*", arrOp(lastSet[len-1], "-", lastSet[len-2])), "+", lastPoint);
}
processed.push([lastPoint].concat([newBez]).concat(offset));
lastPoint = processed[processed.length-1].slice().reverse()[0];
} else if(command === "A") {
alert("The A path command in the SVG is not supported! Please modify or change the SVG file to not contain the A command.")
return [];
} else {
processed.push([lastPoint].concat(offset));
lastPoint = processed[processed.length-1].slice().reverse()[0];
}
}
if(k === 0) console.log(processed);
allPaths[k] = processed;
}
var flat = allPaths.reduce((a,b)=>a.concat(b)).reduce((a,b)=>a.concat(b));
flat = flat[0].map((a,i)=>flat.map(b=>b[i]));
var range = [Math.max.apply(null, flat[0]), Math.max.apply(null, flat[1])];
allPaths = allPaths.map(a=>a.map(b=>b.map(c=>[c[0]-range[0]/2, -1*(c[1]-range[1]/2)])));
return [allPaths, range];
}
function relOff(offset, pointSet) {
return pointSet.map(a=>arrOp(a,"+", offset));
}
function arrOp(arr1, op, arr2) { // Applies operator arr1n (op) arr2n thus not following normal vector rules.
if(arr1.length === undefined) arr1 = new Array(arr2.length).fill(arr1);
if(arr2.length === undefined) arr2 = new Array(arr1.length).fill(arr2);
return arr1.map((a,i)=>eval(a+op+"("+arr2[i]+")"));
}