Team:TUDelft/js/plotfit

function plot(params) {

   $("main.container").addClass("progress");
   progress(params, plot);


   // ################
   // 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...");
       // 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) {
           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);
           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]];
       aa = numeric.transpose(aa);
       DATA = numeric.transpose(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 r = find_r(t, N);
       var objective = makeObjective(logistic, t, N);
       var possible_K = [0.5, 1, 1.5];
       var possible_r = [0.9, 1, 1.1];
       var pos_min = new Array(6);
       var pos_r = new Array(6);
       for (var i = 0; i < 3; i++) {
           pre_K = possible_K[i];
           for (var k = 0; k < 3; k++) {
               pre_r = possible_r[k];
               var initial = [N[0], pre_K * N[N.length - 1], pre_r * r];
               try {
                   var minimiser = numeric.uncmin(objective, initial);
                   pos_min[i+k] = minimiser;
                   pos_r[i+k] = r_sq(logistic, minimiser.solution, N, t);
               } catch (err) {
                   pos_r[i+k] = 0;
                   Warning(err);
               }
           }
       }
       console.log(pos_r);
       var indexOfMaxValue = pos_r.reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0);
       minimiser = pos_min[indexOfMaxValue];
       result("Solution found after " + minimiser.iterations + " iterations.");
       let r2 = r_sq(logistic, minimiser.solution, N, t);
       result("With an R squared of " + r2 + ".");
       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);
   }
   FINDR = find_r;
   function find_r(t, N) {
       var K = Math.max(...N);
       var N_N = vectorize(((N) => N/K), N);
       var lijntje = vectorize(((N_N) =>Math.log(N_N/(1-N_N))), N_N);
       lijntje = removeAfterInfinity(lijntje);
       t = t.slice(0, lijntje.length);
       var initial = [0.005, lijntje[0]];
       var objective = makeObjective(line, t, lijntje);
       try {
           var minimiser = numeric.uncmin(objective, initial);
       } catch (err) {
           Warning("cannot find r: " + err);
           return 2;
       }
       return minimiser.solution[0];
   }
   function r_sq(f, popt, ydata, xdata) {
       let residuals = new Array(ydata.length);
       for(var i = 0; i<ydata.length; i++) {
           residuals[i] = ydata[i] - f(popt, xdata[i]);
       }
       var ss_res = 0;
       for (var i = 0; i<residuals.length; i++) {
           ss_res += Math.pow(residuals[i], 2);
       }
       let mean = ydata.reduce((a,b)=>a+b, 0) / ydata.length;
       var ss_tot = 0;
       for (var i = 0; i<ydata.length; i++) {
           ss_tot += Math.pow(ydata[i] - mean, 2);
       }
       return 1 - (ss_res / ss_tot);
   }
   
   function removeAfterInfinity(arr) {
       var res = [];
       for (i=0; i<arr.length; i++) {
           if (arr[i] !== Infinity) {
               res.push(arr[i]);
           } else {
               return res;
           }
       }
   }
   
   // lijntje = line(params, t)
   function line(params, t) {
       var a, b;
       [a, b] = params
       return a*t + b;
   }


   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);
       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, ans;
       [N0, K, r] = params;
       var below = (K + N0 * (Math.exp(r * t) - 1));
       if (below === Infinity) {
           ans = 0;
       } else {
           ans = (K * N0 * Math.exp(r * t)) / (K + N0 * (Math.exp(r * t) - 1));
       }
       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);
   }

}