diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..adab1cb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+SVGS/**
diff --git a/fourier.js b/fourier.js
new file mode 100644
index 0000000..4013744
--- /dev/null
+++ b/fourier.js
@@ -0,0 +1,197 @@
+var cycles;
+
+onmessage = function(e) {
+ var eq = [];
+ var paths = e.data[0];
+ cycles = e.data[1];
+ for(var i = 0; i < paths.length; i++) {
+ eq.push(calculate(paths[i], i, paths.length))
+ }
+ postMessage(eq);
+}
+
+function calculate(ft, pathNum, pathTotal) {
+ var bottom = -Math.floor(cycles/2);
+ var top = Math.ceil(cycles/2);
+ // These two objects store Ck values for the epicycloids.
+ var reC = {};
+ var imC = {};
+ var piMult = 2*Math.PI/(ft.length);
+ var eachInterval = [];
+ var tInterval = [];
+ var zeroConstants = [];
+
+ for(var i = 0; i < ft.length; i++) { // Precalculate equations
+ var t = [piMult*i, piMult*(i+1)];
+ var pt = ft[i];
+ var ts = 1/(piMult);
+ var shift = poly(new Array(pt.length-1).fill([]));
+ var vec = poly(new Array(pt.length).fill([]));
+
+ for(var j = 0; j < pt.length; j++) vec[0] = vec[0].concat(poly([pt[j]]));
+ for(var j = 0; j < pt.length-1; j++) {
+ for(var k = 0; k < pt.length-1-j; k++) {
+ shift[j] = shift[j].concat(polyOp(-ts*t[0], "*", polyOp(vec[j][k+1], "-", vec[j][k])));
+ vec[j+1] = vec[j+1].concat(polyOp(polyOp(vec[j][k], "+", shift[j][k]), "+", polyOp(ts, "*", polyOp(vec[j][k+1], "-", vec[j][k]), 1)));
+ }
+ }
+ var finalVec = Object.values(vec[pt.length-1][0]);
+ eachInterval.push(integralConstants(finalVec, t)); // Precalculate constants for integral.
+ zeroConstants.push(integralZero(finalVec, t));
+ tInterval.push(t);
+ }
+ for(var k = bottom; k <= top; k++) { // For every epicycloid
+ var reCk = 0;
+ var imCk = 0;
+
+ for(var i = 0; i < ft.length; i++) { // Carry out integral for finding Ck
+ var integralPart = (k === 0) ? zeroConstants[i] : integrate(eachInterval[i][0], eachInterval[i][1], tInterval[i], k);
+ reCk += integralPart[0];
+ imCk += integralPart[1];
+ }
+
+ reC[k] = Rnd(reCk/(2*Math.PI),5);
+ imC[k] = Rnd(imCk/(2*Math.PI),5);
+ postMessage(.9*((pathNum + (k + (cycles)/2+1)/(cycles+1))/pathTotal));
+ }
+ return [reC, imC];
+}
+
+function integralZero(shearConsts, t) {
+ var val = Object.values(shearConsts);
+ var f = polyOp(poly(val.map((a,i)=>arrOp(a, "/" ,i+1))), "+", poly([0]), 1);
+ return arrOp(polySub(f, t[1]), "-",polySub(f, t[0]));
+}
+
+function integralConstants(shearConsts, t) {
+ var n = shearConsts.length; // One more than actual order
+ /* Since there are different constants for the real and imaginary polynomials,
+ the integrals are now separated. The integral is calculated separately, with the real
+ and imaginary portions calculated at the same time, but using the different respective
+ constants. The real and imaginary portions that resolve after the integration
+ is taken into consideration later.
+ */
+ var integralRe = new Array(n).fill([]);
+ var integralIm = new Array(n).fill([]);
+ for(var i = 0; i < n; i++) {
+ for(var j = 0; j < n-i; j++) {
+ if(i === 0) {
+ integralRe[i] = integralRe[i].concat([shearConsts[j][0]]);
+ integralIm[i] = integralIm[i].concat([shearConsts[j][1]]);
+ } else {
+ integralRe[i] = integralRe[i].concat([integralRe[i-1][j+1]*(j+1)]);
+ integralIm[i] = integralIm[i].concat([integralIm[i-1][j+1]*(j+1)]);
+ }
+ }
+ integralRe[i] = poly(integralRe[i]);
+ integralIm[i] = poly(integralIm[i]);
+ }
+
+ var fullPolyRe = [{}, {}]; // Real expanded constants
+ var fullPolyIm = [{}, {}]; // Imaginary expanded constants
+ for(var i = 0; i < n; i++) {
+ var neg = (i%4===2 || i%4===3) ? -1 : 1;
+ integralRe[i] = [polySub(integralRe[i], t[0]), polySub(integralRe[i], t[1])];
+ integralIm[i] = [polySub(integralIm[i], t[0]), polySub(integralIm[i], t[1])];
+ //console.log([integralRe, integralIm])
+ integralRe[i] = polyOp(poly([integralRe[i]]), "*", neg, -i-1);
+ integralIm[i] = polyOp(poly([integralIm[i]]), "*", neg, -i-1);
+ if(i%2===0) {
+ fullPolyIm[0] = Object.assign(fullPolyIm[0], integralRe[i]);
+ fullPolyRe[1] = Object.assign(fullPolyRe[1], integralIm[i]);
+ } else {
+ fullPolyRe[0] = Object.assign(fullPolyRe[0], integralRe[i]);
+ fullPolyIm[1] = Object.assign(fullPolyIm[1], integralIm[i]);
+ }
+ }
+ return [fullPolyRe, fullPolyIm];
+}
+
+function integrate(kRe, kIm, t, k) {
+ var c = [
+ polySub(kRe[0], k),
+ polySub(kIm[0], k),
+ arrOp(-1,"*",polySub(kRe[1], k)),
+ polySub(kIm[1], k),
+ [Math.sin(k*t[0]), Math.sin(k*t[1])],
+ [Math.cos(k*t[0]), Math.cos(k*t[1])]
+ ];
+ c = c[0].map((a,i)=>c.map(b=>b[i]));
+
+ function reCki(c) {
+ return c[4]*(c[1]+c[3]) + c[5]*(c[0]+c[2]);
+ }
+
+ function imCki(c) {
+ return c[5]*(c[1]+c[3]) - c[4]*(c[0]+c[2]);
+ }
+
+ return [(reCki(c[1]) - reCki(c[0])), (imCki(c[1]) - imCki(c[0]))];
+}
+
+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]+")"));
+}
+
+function poly(list) {
+ var obj = {};
+ for(var i = 0; i < list.length; i++) obj[i] = list[i];
+ return obj;
+}
+
+function polyOp(poly1, op, poly2, pow) {
+ pow = pow || 0;
+ var obj = {};
+ var which = poly1;
+ if(Object.keys(poly1).length === 0) {
+ poly1 = poly(new Array(Object.keys(poly2).length).fill(poly1));
+ which = poly2;
+ }
+ if(Object.keys(poly2).length === 0) {
+ poly2 = poly(new Array(Object.keys(poly1).length).fill(poly2));
+ which = poly1;
+ }
+ var greater = poly1;
+ if(Object.keys(poly2).length > Object.keys(poly1).length) greater = poly2;
+ Object.keys(greater).map(function(a) {
+ if((poly1[a] || []).length > 1 || (poly2[a] || []).length > 1) {
+ obj[parseInt(a)+pow] = arrOp((poly1[a] || 0), op, (poly2[a] || 0));
+ for(var i = 0; i < pow; i++) obj[i] = new Array(which[a].length).fill(0);
+ } else {
+ obj[parseInt(a)+pow] = eval((poly1[a] || 0) + op + "("+(poly2[a] || 0)+")");
+ for(var i = 0; i < pow; i++) obj[i] = 0;
+ }
+ });
+ return obj;
+}
+
+function polySub(poly, x) {
+ var key = Object.keys(poly);
+ var val = Object.values(poly).map((a,i)=>arrOp(a,"*",Math.pow(x,parseInt(key[i])))).reduce((a,b)=>arrOp(a,"+",b))
+ return (val.length === 1) ? val[0] : val;
+}
+
+function varType(variable) {
+ var type = typeof variable;
+ if(type === "object") {
+ return (variable.constructor === Array) ? "Array" : "Object";
+ } else {
+ return type[0].toUpperCase() + type.slice(1);
+ }
+}
+
+function Rnd(item,fig) {
+ if(varType(item) === "Array") {
+ var arr = [];
+ for(var i = 0; i < item.length; i++) {
+ arr[i] = Rnd(item[i],fig);
+ }
+ return arr;
+ } else if(varType(item) === "Number") {
+ return Math.round(item*Math.pow(10,fig))/Math.pow(10,fig);
+ } else {
+ throw new TypeError("Expected Integers, got " + varType(item) + ".");
+ }
+}
\ No newline at end of file
diff --git a/grapher.js b/grapher.js
new file mode 100644
index 0000000..2e2626a
--- /dev/null
+++ b/grapher.js
@@ -0,0 +1,34 @@
+onmessage = function(e) {
+ var calcArray = [];
+ var eq = e.data;
+ for(var i = 0; i < eq.length; i++) {
+ calcArray = calcEquation(eq[i]);
+ postMessage(.9+.1*((i+1)/eq.length));
+ postMessage(calcArray);
+ }
+}
+
+function calcEquation(cycleArray) {
+ var step = 1000;
+ /* x(t) and y(t) are in form
+ reCk * cos(kt) - imCk * sin(kt)
+ reCk * sin(kt) + imCk * cos(kt)
+ respectively.
+ */
+ var keys = Object.keys(cycleArray[0]);
+ var calcArray = [];
+ calcArray.push(new Array(step).fill([cycleArray[0][0], cycleArray[1][0]]));
+ for(var i = 0; i < keys.length-1; i++) {
+ var num = ((i+1)%2===1) ? Math.ceil((i+1)/2) : -Math.floor((i+1)/2);
+ var cycloidPoints = [];
+ for(var j = 0; j < step; j++) {
+ var cycNum = num*2*Math.PI*j/step;
+ cycloidPoints.push([
+ cycleArray[0][num]*Math.cos(cycNum) - cycleArray[1][num]*Math.sin(cycNum),
+ cycleArray[0][num]*Math.sin(cycNum) + cycleArray[1][num]*Math.cos(cycNum)
+ ]);
+ }
+ calcArray.push(calcArray[i].map((a,j)=>[a[0]+cycloidPoints[j][0], a[1]+cycloidPoints[j][1]]));
+ }
+ return calcArray;
+}
\ No newline at end of file
diff --git a/main.css b/main.css
new file mode 100644
index 0000000..3b3954f
--- /dev/null
+++ b/main.css
@@ -0,0 +1,326 @@
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ font-family: 'Poppins', sans-serif;
+}
+
+body {
+ display: grid;
+ grid-template-columns: 25% 75%;
+ grid-template-rows: 1fr;
+ overflow-y: hidden;
+}
+
+h1, h2, h3, h4, h5 {
+ margin: 0;
+}
+
+.transition {
+ -webkit-transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
+ transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
+ -moz-transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
+ -ms-transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
+}
+
+#sidebar {
+ color: white;
+ background-color: #00897B;
+ grid-column: 1;
+ grid-row: 1;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 10% 8% 74% 8%;
+ box-shadow: 1px 0px 4px 2px #2c2c2c;
+ z-index: 10;
+ height: 100vh;
+}
+
+#sideTitle {
+ grid-column: 1;
+ grid-row: 2;
+ background-color: rgba(0,0,0,0.1);
+ display: grid;
+}
+
+#sidebar h1 {
+ font-weight: 400;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ margin: auto 0 auto 5%;
+}
+
+#graph {
+ width: 100%;
+ height: 100%;
+ grid-column: 2;
+ grid-row: 1;
+}
+
+#doGraph {
+ background-color: rgba(255,255,255,0.2);
+}
+
+#doGraph:hover {
+ background-color: rgba(255,255,255,0.15);
+}
+
+#canvas {
+ background-color: #EEE;
+}
+
+#setCont {
+ height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ grid-column: 1;
+ grid-row: 3;
+}
+
+.set {
+ display: grid;
+ grid-template-columns: 2% 18% 40% 40%;
+ grid-template-rows: 55% 45%;
+ height: 9vh;
+ background-color: rgba(0,0,0,0.2);
+ border-bottom: 1px solid black;
+ cursor: pointer;
+}
+
+.set:first-child {
+ border-top: 1px solid black;
+}
+
+.set:hover {
+ background-color: rgba(0,0,0,0.25);
+}
+
+.color {
+ grid-column: 2;
+ grid-row: 1/3;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 5% 95%;
+}
+
+.color h2 {
+ grid-column: 1;
+ grid-row: 1;
+ font-size: 100%;
+ font-weight: 100;
+ margin: 0 auto 0 10%;
+}
+
+.color > div {
+ grid-column: 1;
+ grid-row: 1/3;
+ border-radius: 50px;
+ height: 4vh;
+ width: 4vh;
+ margin: auto;
+ box-shadow: inset 0 0 0 0 rgba(0,0,0,0);
+ cursor: pointer;
+}
+
+.color > div:hover {
+ box-shadow: inset 50px 0 rgba(255,255,255,0.2);
+}
+
+.colSel {
+ position: relative;
+ top: 50%;
+ left: 50%;
+ background-color:rgba(255,255,255,0.9);
+ display: none;
+ opacity: 0;
+ grid-template-columns: repeat(4, 1fr);
+ grid-template-rows: repeat(4, 1fr);
+ grid-gap: 1vh;
+ width: 500%;
+ padding: 20%;
+ border-radius: 5px;
+ z-index: 20;
+}
+
+.colSel div {
+ border-radius: 50px;
+ height: 4vh;
+ width: 4vh;
+ margin: auto;
+ box-shadow: inset 0 0 0 0 rgba(0,0,0,0);
+ cursor: pointer;
+}
+
+.colSel div:hover {
+ box-shadow: inset 50px 0 rgba(255,255,255,0.2);
+}
+
+.selected {
+ grid-column: 1;
+ grid-row: 1/3;
+ background-color: #9CCC65;
+}
+
+.name {
+ font-weight: 200;
+ margin-top: auto;
+ grid-column: 3/5;
+ grid-row: 1;
+}
+
+.terms {
+ font-weight: 100;
+ margin: auto 0 auto 0;
+ grid-column: 3;
+ grid-row: 2;
+ color: #E0E0E0;
+}
+
+.eqs {
+ font-weight: 100;
+ margin: auto 0 auto 0;
+ grid-column: 4;
+ grid-row: 2;
+ color: #E0E0E0;
+}
+
+.ondrag {
+ animation: ondrag 1s ease-in-out 0s infinite alternate;
+}
+
+@keyframes ondrag {
+ from {background-color: rgba(255,255,255,0);}
+ to {background-color: rgba(255,255,255,0.3);}
+}
+
+#clickFile {
+ background-color: rgba(0,0,0,0.1);
+ grid-column: 1;
+ grid-row: 4;
+ display: grid;
+ grid-template-columns: 80% 20%;
+ grid-template-rows: 1fr;
+ font-size: 120%;
+ cursor: pointer;
+}
+
+#clickFile:hover {
+ background-color: rgba(0,0,0,0.2);
+}
+
+#clickFile input {
+ height: 0;
+ visibility: hidden;
+}
+
+#clickFile p, #clickFile i {
+ margin: auto;
+}
+
+@keyframes spin {
+ from {transform:rotate(0deg);}
+ to {transform:rotate(360deg);}
+}
+
+.spin {
+ animation: spin 2.5s linear 0s infinite;
+}
+
+.option {
+ height: 6vh;
+ background-color: rgba(0,0,0,0.05);
+ display: grid;
+ grid-template-columns: 40% 40% 20%;
+ cursor: pointer;
+}
+
+.option:hover {
+ background-color: rgba(0,0,0,0.1);
+}
+
+.disabled {
+ background-color: rgba(0,0,0,0.15);
+ color: #AEAEAE;
+ pointer-events: none;
+}
+
+.disabled input {
+ color: #AEAEAE !important;
+}
+
+.disabled:hover {
+ background-color: rgba(0,0,0,0.15);
+}
+
+.option p {
+ margin: auto 0 auto 10%;
+ font-weight: 300;
+ font-size: 110%;
+ grid-row: 1;
+ grid-column: 3;
+ display: inline;
+}
+
+.option p:first-child {
+ grid-column: 1;
+}
+
+.option input {
+ margin: auto;
+ height: 50%;
+ width: 50%;
+ grid-row: 1;
+ grid-column: 2;
+ background-color: rgba(0,0,0,0);
+ color: white;
+ border: 0;
+ font-family: 'Poppins',sans-serif;
+ padding: 1%;
+ font-size: 110%;
+ font-weight: 300;
+ text-align: center;
+}
+
+.option input:hover {
+ background-color: rgba(0,0,0,0.05);
+}
+
+.option input:focus {
+ background-color: rgba(0,0,0,0.1);
+}
+
+input[type="number"] {
+ -webkit-appearance: textfield;
+ -moz-appearance: textfield;
+ appearance: textfield;
+
+}
+
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+}
+
+#mathContRe, #mathContIm {
+ overflow-y: auto;
+ height: 46.9vh;
+}
+
+#mathType p {
+ grid-column: 1/3;
+ margin: auto 0 auto 5%;
+}
+
+#mathContRe {
+ display: none;
+ opacity: 0;
+}
+
+.MJXc-display {
+ font-size: 80% !important;
+}
+
+#copyBox {
+ position: absolute;
+ top: -50%;
+}
\ No newline at end of file
diff --git a/main.html b/main.html
new file mode 100644
index 0000000..cffdcc2
--- /dev/null
+++ b/main.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Epicycles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..a4962cc
--- /dev/null
+++ b/main.js
@@ -0,0 +1,992 @@
+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,"")).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;
+ var diff = allSets.length;
+ for (var i = 0; i < e.dataTransfer.items.length; i++) {
+ if (e.dataTransfer.items[i].kind === 'file') {
+ loadSVGStatus(true);
+ setTimeout(function() {
+ var file = e.dataTransfer.items[i].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);
+ 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 {
+ 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));
+ }
+ }
+
+ var lastPoint = [0,0];
+ 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];
+ } else if(command === "T") {
+ var lastSet = processed[processed.length-1];
+ var newBez = arrOp(arrOp(2,"*",lastSet[2]), "-", lastSet[1]);
+ processed.push([lastPoint].concat([newBez]).concat(offset));
+ lastPoint = processed[processed.length-1].slice().reverse()[0];
+ } else if(command === "S") {
+ var lastSet = processed[processed.length-1];
+ var newBez = arrOp(arrOp(2,"*",lastSet[3]), "-", lastSet[2]);
+ 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];
+ }
+ }
+ 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]+")"));
+}
\ No newline at end of file