From a347aca0b8287cc6922300f5e09e8d7c4e4d0472 Mon Sep 17 00:00:00 2001 From: Kenneth Jao Date: Tue, 28 Aug 2018 12:53:13 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + fourier.js | 197 +++++++++++ grapher.js | 34 ++ main.css | 326 ++++++++++++++++++ main.html | 34 ++ main.js | 992 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1584 insertions(+) create mode 100644 .gitignore create mode 100644 fourier.js create mode 100644 grapher.js create mode 100644 main.css create mode 100644 main.html create mode 100644 main.js 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