Team:TUDelft/js/plotfit

function plot(params) {

   $("main.container").addClass("progress");
   progress(params, plot);
   //Globals:
   var DATA;
   // ################
   // The rest defines the functions:
   // ################
   // Gets data from form and validates.
   function progress(params, plot) {
       // Print an error
       // Empty alert box and results box
       $('#alert').html("");
       $('#results').html("");
       Message("Processing...");
       // Getting all the data from the form.
       var data = params.data.value.trim();
       var thousands_seperator = params.thousands_seperator.value;
       var thousands_checked = params.thousands_usage.checked;
       var value_seperator = params.value_seperator.value;
       var decimal_seperator = params.decimal_seperator.value;
       if (value_seperator === 't') value_seperator = '\t';
       Message("Validating format...");
       console.log("value seperator" + value_seperator + ".");
       // validation and manipulation
       if (thousands_checked) {
           if (thousands_seperator == value_seperator) {
               Warning("Your thousands seperator is the same as the value seperator.");
           }
           if (thousands_seperator == decimal_seperator) {
               Warning("Your thousands seperator is the same as the value seperator.");
           }
           data = replaceAll(data, thousands_seperator, );
       }
       if (value_seperator == decimal_seperator) {
           return Error("Your value seperator is the same as the decimal seperator.");
       }
       // turn value seperator into ',' and decimal seperator into '.', and preventing those to be mixed up.
       if (data.indexOf('¬') === -1) {
           dataWithStrange = replaceAll(data, value_seperator, '¬');
           dataDecimalToPoint = replaceAll(dataWithStrange, decimal_seperator, '.');
           data = replaceAll(dataDecimalToPoint, '¬', ',');
       } else {
           return Error("¬ cannot be used as delimiter.");
       }
       data = replaceAll(data, "\"", "");
       Message("Parsing...");
       aa = numeric.parseCSV(data);
       Message("Validating data...")
       aa[0] = aa[0].filter(filterExceptZero);
       aa[1] = aa[1].filter(filterExceptZero);
       if (aa.length > 2) {
           aa = numeric.transpose(aa);
       }
       if (aa.length !== 2) {
           return Error("Enter data with 2 columns or rows.");
       }
       if (aa[0].length !== aa[1].length) {
           console.log(aa[0]);
           console.log(aa[1]);
           Warning("Your two columns or rows are of different length. The longest one is truncated to match the other.");
           var min = Math.min(aa[0].length, aa[1].length);
           console.log(min);
           if (aa[0].length == min) {
               aa[1] = aa[1].slice(0, min);
           } else {
               aa[0] = aa[0].slice(0, min);
           }
       }
       aa = [aa[0], aa[1]];
       console.log(aa[0]);
       console.log(aa[1])
       console.log(aa);
       aa = numeric.transpose(aa);
       DATA = aa;
       Message("Plotting...");
       plot(aa, fit);
   }
   // Plots the data
   function plot(data, fit) {
       result("Your data has size " + data.length + ".");
       $.plot($("#placeholder"), [{
           color: 'yellow',
           data: data
       }]);
       fit(data, plotfit);
   }
   function fit(data, plotfit) {
       data = numeric.transpose(data);
       var t = data[0];
       var N = data[1];
       var initial = [N[0], N[N.length - 1], 0.005];
       var objective = makeObjective(logistic, t, N);
       try {
           var minimiser = numeric.uncmin(objective, initial);
       } catch (err) {
           return Error(err)
       }
       result("Solution found after " + minimiser.iterations + " iterations.");
       result(minimiser.message);
       draw(minimiser.solution);
       var t = numeric.linspace(t[0], t[t.length - 1], t.length);
       var N = vectorize(
           setParamInFunc(minimiser.solution, logistic), t
       );
       //        var N = (func, x) - > {
       //            var res = Array(x.length);
       //            for (var i = 0; i < x.length; i++) {
       //                res[i] = func(x[i]);
       //            }
       //            return res;
       //        }((func, params) - > {
       //            return (x) - >
       //                return func(params, x);
       //        }, t);
       var datafit = [t, N];
       datafit = numeric.transpose(datafit);
       data = numeric.transpose(data);
       plotfit(datafit, data, callback);
   }


   function plotfit(datafit, data, callback) {
       $.plot($("#placeholder"), [{
           label: "fitted data",
           data: datafit,
           color: 'green'
       }, {
           label: "input data",
           data: data,
           color: 'yellow'
       }], {
           legend: {
               position: "nw"
           }
       });
       callback();
   }
   // Tells user we are done.
   function callback() {
       $("main.container").removeClass("progress");
   }
   // ################
   // The following functions are out of the callback work loop
   // ################
   // Decorator for functions to perform vectorized
   function vectorize(func, x) {
       var res = new Array(x.length);
       testtest = func;
       for (var i = 0; i < x.length; i++) {
           res[i] = func(x[i]);
       }
       return res;
   }
   // Decorator for functions to set params fixed
   function setParamInFunc(params, func) {
       function resFunc(x) {
           return func(params, x);
       }
       return resFunc;
   }
   // Function to fit
   // N = logistic(params,t);
   function logistic(params, t) {
       var N0, K, r;
   [N0, K, r] = params;
       var ans = (K * N0 * Math.exp(r * t)) / (K + N0 * (Math.exp(r * t) - 1));
       var below = (K + N0 * (Math.exp(r * t) - 1));
       if (below === Infinity) {
           ans = 0;
       }
       return ans;
   }
   // Least square objective
   // Build as factory with a decorator
   function makeObjective(targetFunc, x, y) {
       function objective(params) {
           var total = 0.0
           for (var i = 0; i < y.length; i++) {
               var result = targetFunc(params, x[i])
               var delta = result - y[i];
               total += delta * delta;
           }
           return total;
       }
       return objective;
   }
   function draw(params) {
       param_names = ["N0", "K", "r"];
       for (var i = 0; i < param_names.length; i++) {
           result(param_names[i] + " is " + params[i]);
       }
       var latex = katex.renderToString("N(t) = \\frac{K \\cdot N_0 \\cdot e^{rt}}{K + N_0 \\cdot (e^{rt}-1)}");
       result("For the function:")
       resultHTML(latex);
   }
   function result(e) {
       $("#results").append($("<p />", {
           class: "result",
           text: e
       }));
   }
   function resultHTML(e) {
       $("#results").append($("<p />", {
           class: "result",
           html: e
       }));
   }
   // Print an error (This function is usually returned.)
   function Error(e) {
       $("#alert").prepend($('<p />', {
           class: 'error',
           text: e
       }));
       $("main.container").removeClass("progress");
   }
   // Print a warming
   function Warning(e) {
       $("#alert").append($('<p />', {
           class: 'warning',
           text: e
       }));
   }
   // Print a message
   function Message(e) {
       $("#alert").append($('<p />', {
           class: 'message',
           text: e
       }));
   }
   // This functions takes a string and adds \ before any character that needs to be escaped in a regex. 
   // This is necesarry when putting user input into a regex. 
   function escapeRegExp(string) {
       return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
   }
   // This functions replaces all matched characters and also does this safe for user input
   function replaceAll(str, find, replace) {
       return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
   }
   // This function returns false when NaN, false, and undefined, but returns true for 0
   function filterExceptZero(el) {
       if (el === 0) return true;
       else return Boolean(el);
   }

}