initial commit

This commit is contained in:
Kenneth Jao 2018-08-28 12:53:13 -04:00
parent 1341645fa3
commit a347aca0b8
6 changed files with 1584 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
SVGS/**

197
fourier.js Normal file
View File

@ -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) + ".");
}
}

34
grapher.js Normal file
View File

@ -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;
}

326
main.css Normal file
View File

@ -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%;
}

34
main.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Epicycles</title>
<link rel="icon" href="favicon.ico?v=2">
<link rel="stylesheet" href="./main.css">
<link href="https://fonts.googleapis.com/css?family=Poppins:100,200,400" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>
</head>
<body>
<div id="sidebar">
<div id="sideTitle">
<h1>Equation List</h1>
</div>
<div id="setCont" class="transition" ondrop="fileDrop(event);" ondragover="fileDrag(event);" ondragleave="fileDragLeave(event);">
</div>
<div id="clickFile" class="transition" onclick="this.children[2].click();">
<p>Drag or browse for a file!</p>
<i class="fas fa-file-upload"></i>
<input type="file">
</div>
</div>
<div id="graph">
<canvas id="canvas">
</canvas>
</div>
<input id="copyBox">
<script src="./main.js"></script>
</body>

992
main.js Normal file
View File

@ -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]+")"));
}