// t-SNE.js object and other global variables

var toggleValue = false; var k; var points = []; var all_fields; var pointsbeta = []; var KNNEnabled = true; 
// These are the dimensions for the square shape of the main panel\
var dimensions = document.getElementById('modtSNEcanvas').offsetWidth;

// These are the dimensions for the overview panel
var dim = document.getElementById('tSNEcanvas').offsetWidth;

var format; var new_file; var opt; var step_counter; var final_dataset; var max_counter; var dists; var dists2d; var all_labels; var runner; var tsne; var count_canvas = 0; var x_position = []; var y_position = []; var x_position2 = []; var y_position2 = []; var cost_each; var beta_all = [];
var points2d = []; var ArrayContainsDataFeatures = []; var ArrayContainsDataFeaturesCleared = [];
var InitialStatePoints = [];

function getfile(file){
  new_file = file;   //uploaded file data
}

// Parse data
var getData = function() {
  // form controls
    var value = document.getElementById("param-dataset").value;

    format = document.getElementById("param-dataset").value.split("."); //get the actual format
    if (format[value.split(".").length-1] == "csv") {
      parseData("./data/"+value);
    }else{
      parseData(new_file, init);
    }
};

function parseData(url) {
    Papa.parse(url, { //for csv file!
        download: true,
        header: true,
        dynamicTyping: true,
        skipEmptyLines: true,
        complete: function(results) {
          results.data = results.data.filter(function (el) {
          var counter = 0;
          for(key in el) {
              if(el.hasOwnProperty(key)) {
                  var value = el[key];
                  if (key === 'name'){
                  }else{
                    if(typeof(value) !== 'number' || value === undefined || key === "Version"){ //add more limitations if needed!
                      delete el[key];
                    }else{
                      el[counter] = el[key];
                      delete el[key];
                      counter = counter + 1;
                    }
                  }
              }
          }
          return el;
        });
        Papa.parse(url, { //for csv file!
            download: true,
            header: true,
            dynamicTyping: true,
            skipEmptyLines: true,
            complete: function(data) {
              doStuff(data.data);
            }
          });
          function doStuff(results_all){
          init(results.data, results_all, results.meta.fields);
          }
        }
    });
}

function setContinue(){
  d3v3.select("#SvgAnnotator").style("z-index", 2);
}

var ringNotes = [];
var gAnnotationsAll = [];
var AnnotationsAll = [];
var draggable = [];

function setReset(){
  d3.selectAll("#SvgAnnotator > *").remove(); 
  d3.selectAll("#OverviewtSNE > *").remove(); 
  d3.selectAll("#correlation > *").remove(); 
  d3.selectAll("#modtSNEcanvas_svg > *").remove(); 
  d3.selectAll("#kNNBar > *").remove(); 
  for (var i=0; i < InitialStatePoints.length; i++){
    InitialStatePoints[i].selected = true;
  }
  redraw(InitialStatePoints);
}

function setLayerProj(){
  d3.select("#modtSNEcanvas").style("z-index", 2);
  d3.select("#modtSNEcanvas_svg").style("z-index", 1);
}

function setLayerComp(){
  d3.select("#modtSNEcanvas_svg").style("z-index", 3);
}

function setLayerSche(){
  d3.select("#modtSNEcanvas_svg").style("z-index", 3);
  toggleValue = true;
}




function setAnnotator(){

  var viewport2 = getViewport();
  var vw2 = viewport2[0];
  var vh2 = viewport2[1];
  var textarea = document.getElementById("comment").value;

  var annotations = [
  {
  "cx": 232,
  "cy": 123,
  "r": 103,
  "text": textarea,
  "textOffset": [
    114,
    88
  ]
  }
  ];

  var ringNote = d3v3.ringNote()
  .draggable(true);

  var svgAnnotator = d3v3.select("#SvgAnnotator")
  .attr("width", vw2 * 0.5)
  .attr("height", vh2 * 0.888)
  .style("z-index", 3);


  var gAnnotations = svgAnnotator.append("g")
  .attr("class", "annotations")
  .call(ringNote, annotations);

  // Styling individual annotations based on bound data
  gAnnotations.selectAll(".annotation circle")
  .classed("shaded", function(d) { return d.shaded; });


  ringNotes.push(ringNote);
  gAnnotationsAll.push(gAnnotations);
  AnnotationsAll.push(annotations);
  draggable.push(true);
}


  // Hide or show the controls
  d3.select("#controls")
  .on("change", function() {
    if(ringNotes[0]){
      for (var i = 0; i < ringNotes.length; i++){
        ringNotes[i].draggable(draggable[i] = !draggable[i]);
        gAnnotationsAll[i]
            .call(ringNotes[i], AnnotationsAll[i])
            .selectAll(".annotation circle")
            .classed("shaded", function(d) { return d.shaded; });
      }
    } else{
      alert("Cannot hide the annotators' controls because, currently, there are no annotations into the visual representation.")
    }
  });

    // Three.js render loop
    function animate() {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }

    var MainCanvas;
    var Child;
    var renderer;
    var fov = 21;
    var near = 10;
    var far = 7000;
    var camera;
    var scene;

// function that executes after data is successfully loaded
function init(data, results_all, fields) {

    MainCanvas = document.getElementById('modtSNEcanvas');
    Child = document.getElementById('modtSNEDiv');

    // Add canvas
    renderer = new THREE.WebGLRenderer({ canvas: MainCanvas});
    renderer.setSize(dimensions, dimensions);
    Child.append(renderer.domElement);
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);
    
    // Set up camera and scene
    camera = new THREE.PerspectiveCamera(
      fov,
      dimensions / dimensions,
      near,
      far 
    );
    animate();
    step_counter = 0;
    max_counter = document.getElementById("param-maxiter-value").value;
    opt = {};
    var fields;
    fields.push("beta");
    fields.push("cost");
    all_fields = fields;
    opt.epsilon = document.getElementById("param-learningrate-value").value; // epsilon is learning rate (10 = default)
    opt.perplexity = document.getElementById("param-perplexity-value").value; // roughly how many neighbors each point influences (30 = default)
    tsne = new tsnejs.tSNE(opt);
    final_dataset = data;
    dataFeatures = results_all;
    var object;
    for (let k = 0; k < dataFeatures.length; k++){
      ArrayContainsDataFeatures.push(Object.values(dataFeatures[k]).concat(k));
      object = [];
      Object.values(dataFeatures[k]).forEach(function(dataFeature){
        if(typeof(dataFeature) == "number"){
          object.push(dataFeature);
        }
      });
      ArrayContainsDataFeaturesCleared.push(object);
    }
    
    $("#datasetDetails").html("Number of Dimensions: " + (ArrayContainsDataFeatures[0].length - 1) + ", Number of Samples: " + final_dataset.length);
    dists = computeDistances(data, document.getElementById("param-distance").value, document.getElementById("param-transform").value);
    tsne.initDataDist(dists);
    all_labels = [];
    for(var i = 0; i < data.length; i++) {
      if (final_dataset[i]["name"] != "" || final_dataset[i]["name"] != "undefined"){
        all_labels[i] = final_dataset[i]["name"];
      }
      else{
        all_labels[i];
      }
    }
    for(var i = 0; i < final_dataset.length; i++) {final_dataset[i].beta = tsne.beta[i]; beta_all[i] = tsne.beta[i];}
    runner = setInterval(step, 0);
}

// initialize distance matrix
function initDist(data) {
    var dist = new Array(data.length);
    for(var i = 0; i < data.length; i++) {
      dist[i] = new Array(data.length);
    }
    for(var i = 0; i < data.length; i++) {
      for(var j = 0; j < data.length; j++) {
        dist[i][j] = 0;
      }
    }
    return dist;
}

// calculate euclidean distance
function euclideanDist(data) {
    dist = initDist(data);
    for(var i = 0; i < data.length; i++) {
      for(var j = i + 1; j < data.length; j++) {
        for(var d in data[0]) {
          if(d != "name") {
        dist[i][j] += Math.pow(data[i][d] - data[j][d], 2);
          }
        }
        dist[i][j] = Math.sqrt(dist[i][j]);
        dist[j][i] = dist[i][j];
      }
    }
    return dist;
}

// calculate jaccard dist
function jaccardDist(data) {
    dist = initDist(data);
    for(var i = 0; i < data.length; i++) {
  for(var j = i + 1; j < data.length; j++) {
            for(var d in data[0]) {
    if(d != "name") {
        x = data[i][d];
        y = data[j][d];
        if(x == y) {
      dist[i][j] += 1;
        }
    }
            }
            dist[j][i] = dist[i][j];
  }
    }
    return dist;
}

// normalize distances to prevent numerical issues
function normDist(data, dist) {
    var max_dist = 0;
    for(var i = 0; i < data.length; i++) {
  for(var j = i + 1; j < data.length; j++) {
            if(dist[i][j] > max_dist) max_dist = dist[i][j];
  }
    }
    for(var i = 0; i < data.length; i++) {
  for(var j = 0; j < data.length; j++) {
            dist[i][j] /= max_dist;
  }
    }
    return dist;
}

function noTrans(data) {
    return data;
}

// Log transform
function logTrans(data) {
    for(var i = 0; i < data.length; i++) {
        for(var d in data[0]) {
      if(d != "name") {
    X = data[i][d];
    data[i][d] = Math.log10(X + 1);
      }
  }
    }
    return data;
}

// asinh transform
function asinhTrans(data) {
    for(var i = 0; i < data.length; i++) {
        for(var d in data[0]) {
       if(d != "name") {
    X = data[i][d];
    data[i][d] = Math.log(X + Math.sqrt(X * X + 1));
      }
  }
    }
    return data;
}
// binarize
function binTrans(data) {
    for(var i = 0; i < data.length; i++) {
        for(var d in data[0]) {
      if(d != "name") {
    X = data[i][d];
    if(X > 0) data[i][d] = 1;
    if(X < 0) data[i][d] = 0;
      }
  }
    }
    return data;
}

function computeDistances(data, distFunc, transFunc) {
    dist = eval(distFunc)(eval(transFunc)(data));
    dist = normDist(data, dist);
    return dist;
}

// function that updates embedding
function updateEmbedding() {
    var Y = tsne.getSolution(); // here we get the solution from the actual t-sne 
    var xExt = d3.extent(Y, d => d[0]);
    var yExt = d3.extent(Y, d => d[1]);
    var maxExt = [Math.min(xExt[0], yExt[0]), Math.max(xExt[1], yExt[1])];

    var x = d3.scaleLinear()
            .domain(maxExt)
            .range([10, +dimensions-10]);

    var y = d3.scaleLinear()
            .domain(maxExt)
            .range([10, +dimensions-10]);

    for(var i = 0; i < final_dataset.length; i++) {
      x_position[i] = x(Y[i][0]);
      y_position[i] = y(Y[i][1]);
          points[i] = {id: i, x: x_position[i], y: y_position[i], beta: final_dataset[i].beta, cost: final_dataset[i].cost, selected: true, DimON: null, starplot: false};
          points2d[i] = {id: i, x: x_position[i], y: y_position[i], selected: true};
          points[i] = extend(points[i], ArrayContainsDataFeaturesCleared[i]);
          points[i] = extend(points[i], dataFeatures[i]);
      }
    InitialStatePoints = points;
    function extend(obj, src) {
    for (var key in src) {
        if (src.hasOwnProperty(key)) obj[key] = src[key];
    }
      return obj;
    }
      if (step_counter == max_counter){
        ShepardHeatMap();
        OverviewtSNE(points);
        BetatSNE(points);
      }
}

function ShepardHeatMap () {
  var margin = { top: 35, right: 15, bottom: 15, left: 35 },
  dim2 = Math.min(parseInt(d3.select("#sheparheat").style("width")), parseInt(d3.select("#sheparheat").style("height")))
  width = dim2- margin.left - margin.right,
  height = dim2 - margin.top - margin.bottom,
  buckets = 10,
  gridSize = width / buckets,
  dim_1 = ["0.0", "0.2", "0.4", "0.6", "0.8", "1.0"],
  dim_2 = ["0.0", "0.4", "0.6", "1.0"]

  // Create the svg canvas
      var svg = d3.select("#sheparheat")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
dists2d = computeDistances(points2d, document.getElementById("param-distance").value, document.getElementById("param-transform").value);

var dist_list2d = [];
var dist_list = [];
  for (var j=0; j<dists2d.length; j++){
    dists2d[j] = dists2d[j].slice(0,j);
    dists[j] = dists[j].slice(0,j);
  }

for (var i=0; i<dists2d.length; i++){
  for (var j=0; j<dists2d.length; j++){
    let singleObj = {};
    singleObj = dists2d[i][j];
    dist_list2d.push(singleObj);
    let singleObj2 = {};
    singleObj2 = dists[i][j];
    dist_list.push(singleObj2);
  }
}
dist_list2d = dist_list2d.sort();
dist_list = dist_list.sort();
dist_list2d = dist_list2d.filter(function(val){ return val!==undefined; });
dist_list = dist_list.filter(function(val){ return val!==undefined; });
d3.tsv("./modules/heat.tsv").then(function(data) {

  data.forEach(function(d) {
      d.dim1 = +d.dim1;
      d.dim2 = +d.dim2;
      d.value = 0;
  });

  var counter = 0;
  var counnum = [];
  var temp_loop = 0;
  for (var l=0; l<100; l++) {counnum[l] = 0};
  var dist_list_all = [];
  dist_list_all =[dist_list, dist_list2d];
  for (var l=0; l<100; l++){
    for (k=0; k<dist_list_all[0].length;k++){
      if (l==0){
        if (dist_list_all[0][k] <= data[l].dim1/10 && dist_list_all[1][k] <= data[l].dim2/10){
          counnum[l] = counnum[l] + 1;
        }
      }else if (l <= 10){
        if (dist_list_all[0][k] < data[l].dim1/10 && dist_list_all[1][k] < data[l].dim2/10 && dist_list_all[1][k] > data[l-1].dim2/10){
          counnum[l] = counnum[l] + 1;
        }
      }else if (l % 10 == 1){
        temp_loop = data[l].dim1-1;
        if (dist_list_all[0][k] < data[l].dim1/10 && dist_list_all[1][k] < data[l].dim2/10 && dist_list_all[0][k] > temp_loop/10){
          counnum[l] = counnum[l] + 1;
        }
      }else{
        if (dist_list_all[0][k] <= data[l].dim1/10 && dist_list_all[1][k] <= data[l].dim2/10 && dist_list_all[1][k] >= data[l-1].dim2/10 && dist_list_all[0][k] > temp_loop/10){
          counnum[l] = counnum[l] + 1;
        }
      }
    }
    counter = counter + counnum[l];
  }
  for (var m=0; m<data.length; m++)
  {
    data[m].value = counnum[m];
  }

  var maxNum = Math.round(d3.max(data,function(d){ return d.value; }));
  var minNum = Math.round(d3.min(data,function(d){ return d.value; }));
  var colors = ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"];
  let calcStep = (maxNum-minNum)/colors.length;
  var colorScale = d3.scaleLinear()
      .domain(d3.range(0, maxNum+calcStep,calcStep))
      .range(colors);
  var tip = d3.tip()
              .attr('class', 'd3-tip')
              .style("visibility","visible")
              .offset([-20, 0])
              .html(function(d) {
                return "Value:  <span style='color:red'>" + Math.round(d.value);
              });

  tip(svg.append("g"));

  var dim1Labels = svg.selectAll(".dim1Label")
      .data(dim_1)
      .enter().append("text")
        .text(function (d) { return d; })
        .attr("x", 0)
        .attr("y", function (d, i) { return i * gridSize * 2; })
        .style("text-anchor", "end")
        .style("font-size", "10px")
        .attr("transform", "translate(-6," + gridSize / 4 + ")")
        .attr("class","mono");

  var title = svg.append("text")
                  .attr("class", "mono")
                  .attr("x", -(gridSize * 7))
                  .attr("y", -26)
                  .style("font-size", "12px")
                  .attr("transform", "rotate(-90)")
                  .attr("class","mono")
                  .text("Input Distance");


  var title = svg.append("text")
                  .attr("class", "mono")
                  .attr("x", gridSize * 3 )
                  .attr("y", -26)
                  .style("font-size", "12px")
                  .text("Output Distance");

  var dim2Labels = svg.selectAll(".dim2Label")
      .data(dim_2)
      .enter().append("text")
        .text(function(d) { return d; })
        .attr("x", function(d, i) { return i * gridSize * 3.2; })
        .attr("y", 0)
        .style("text-anchor", "middle")
        .style("font-size", "10px")
        .attr("transform", "translate(" + gridSize / 4 + ", -6)")
        .attr("class","mono");

  var heatMap = svg.selectAll(".dim2")
      .data(data)
      .enter().append("rect")
      .attr("x", function(d) { return (d.dim2 - 1) * gridSize; })
      .attr("y", function(d) { return (d.dim1 - 1) * gridSize; })
      .attr("rx", 0.4)
      .attr("ry", 0.4)
      .attr("class", "dim2 bordered")
      .attr("width", gridSize-2)
      .attr("height", gridSize-2)
      .style("fill", colors[0])
      .attr("class", "square")
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide);



  heatMap.transition()
      .style("fill", function(d) { return colorScale(d.value); });

  heatMap.append("title").text(function(d) { return d.value; });

  var heatleg = d3.select("#legend4");

  heatleg.append("g")
    .attr("class", "legendLinear")
    .attr("transform", "translate(0,10)");

  var legend = d3.legendColor()
    .labelFormat(d3.format(",.0f"))
    .cells(9)
    .title("Number of Points")
    .scale(colorScale);

  heatleg.select(".legendLinear")
    .call(legend);
      });
}

// perform single t-SNE iteration
function step() {
    step_counter++;
    if(step_counter <= max_counter) {
  var cost = tsne.step();
  cost_each = cost[1];
  for(var i = 0; i < final_dataset.length; i++) final_dataset[i].cost = cost_each[i];
  $("#cost").html("Number of Iteration: " + tsne.iter + ", Overall Cost: " + cost[0].toFixed(3));
    }
    else {
        clearInterval(runner);
    }
    updateEmbedding();
}

function resize(canvas) {
  // Lookup the size the browser is displaying the canvas.
  var displayWidth  = canvas.clientWidth;
  var displayHeight = canvas.clientHeight;

  // Check if the canvas is not the same size.
  if (canvas.width  != displayWidth ||
      canvas.height != displayHeight) {

    // Make the canvas the same size
    canvas.width  = displayWidth;
    canvas.height = displayHeight;
  }
}

var Arrayx = [];
var Arrayy = [];
var XYDistId = [];
var Arrayxy = [];
var DistanceDrawing1D = [];
var allTransformPoints = [];
var p;
var pFinal = [];
var paths;
var path;
var ArrayLimit = [];
var minimum;
var correlationResults = [];
var ArrayContainsDataFeaturesLimit = [];

function OverviewtSNE(points){
  if (step_counter == 1){
    d3.select("#OverviewtSNE").select("g").remove();
    d3.select("#correlation").select("g").remove();
    d3.selectAll("#modtSNEcanvas_svg > *").remove(); 
    Arrayx = [];
    Arrayy = [];
    XYDistId = [];
    Arrayxy = [];
    DistanceDrawing1D = [];
    allTransformPoints = [];
    p;
    pFinal = [];
    paths;
    path;
    ArrayLimit = [];
    minimum;
    correlationResults = [];
    ArrayContainsDataFeaturesLimit = [];
  }
  var canvas = document.getElementById('tSNEcanvas');
  gl = canvas.getContext('experimental-webgl');

  // If we don't have a GL context, give up now
  if (!gl) {
    alert('Unable to initialize WebGL. Your browser or machine may not support it.');
    return;
  }
  if (all_labels[0] == undefined){
    var colorScale = d3.scaleOrdinal(d3.schemeCategory10).domain(["No Category"]).range(["#0000ff"]);
  }
  else{
    var colorScale = d3.scaleOrdinal(d3.schemeCategory10).domain(all_labels);
  }
    d3.select("#legend3").select("svg").remove();
    var svg = d3.select("#legend3").append("svg");

    svg.append("g")
      .attr("class", "legendOrdinal")
      .attr("transform", "translate(8,5)");

    var legendOrdinal = d3.legendColor()
      .shape("path", d3.legendSize(100))
      .shapePadding(15)
      .scale(colorScale);

    svg.select(".legendOrdinal")
      .call(legendOrdinal);

  let vertices = [];
  let colors = [];

  for (var i=0; i<points.length; i++){
    let singleObj = {};
    // convert the position from pixels to 0.0 to 1.0
   let zeroToOne = points[i].x / dimensions;
   let zeroToOne2 = points[i].y / dimensions;

   // convert from 0->1 to 0->2
   let zeroToTwo = zeroToOne * 2.0;
   let zeroToTwo2 = zeroToOne2 * 2.0;

   // convert from 0->2 to -1->+1 (clipspace)
   let clipSpace = zeroToTwo - 1.0;
   let clipSpace2 = zeroToTwo2 - 1.0;
    singleObj = clipSpace;
    vertices.push(singleObj);
    singleObj = clipSpace2 * -1;
    vertices.push(singleObj);
    singleObj = 0.0;
    vertices.push(singleObj);
  }
  for (var i=0; i<points.length; i++){
    let singleCol = {};
    if (points[i].selected == false){
      let colval = d3.rgb(211,211,211);
      singleCol = colval.r/255;
      colors.push(singleCol);
      singleCol = colval.g/255;
      colors.push(singleCol);
      singleCol = colval.b/255;
      colors.push(singleCol);
    }else{
      let colval = d3.rgb(colorScale(points[i].name));
      singleCol = colval.r/255;
      colors.push(singleCol);
      singleCol = colval.g/255;
      colors.push(singleCol);
      singleCol = colval.b/255;
      colors.push(singleCol);
    }
  }
  // Create an empty buffer object and store vertex data
  var vertex_buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  // Create an empty buffer object and store color data
  var color_buffer = gl.createBuffer ();
  gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

  // vertex shader source code
  var vertCode = 'attribute vec3 coordinates;'+
    'attribute vec3 color;'+
    'varying vec3 vColor;'+
    'void main(void) {' +
       ' gl_Position = vec4(coordinates, 1.0);' +
       'vColor = color;'+
       'gl_PointSize = 3.5;'+
    '}';

  // Create a vertex shader object
  var vertShader = gl.createShader(gl.VERTEX_SHADER);

  // Attach vertex shader source code
  gl.shaderSource(vertShader, vertCode);

  // Compile the vertex shader
  gl.compileShader(vertShader);

    var fragCode = `
    #ifdef GL_OES_standard_derivatives
    #extension GL_OES_standard_derivatives : enable
    #endif

    precision mediump float;
    varying  vec3 vColor;

    void main()
    {
        float r = 0.0, delta = 0.0, alpha = 1.0;
        vec2 cxy = 2.0 * gl_PointCoord - 1.0;
        r = dot(cxy, cxy);
    #ifdef GL_OES_standard_derivatives
        delta = fwidth(r);
        alpha = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, r);
    #endif

    gl_FragColor = vec4(vColor, alpha);
    }`;
    gl.getExtension('OES_standard_derivatives');
  // Create fragment shader object
  var fragShader = gl.createShader(gl.FRAGMENT_SHADER);

  // Attach fragment shader source code
  gl.shaderSource(fragShader, fragCode);

  // Compile the fragmentt shader
  gl.compileShader(fragShader);

  // Create a shader program object to
  // store the combined shader program
  var shaderProgram = gl.createProgram();

  // Attach a vertex shader
  gl.attachShader(shaderProgram, vertShader);

  // Attach a fragment shader
  gl.attachShader(shaderProgram, fragShader);

  // Link both the programs
  gl.linkProgram(shaderProgram);

  // Use the combined shader program object
  gl.useProgram(shaderProgram);



  // Bind vertex buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);

  // Get the attribute location
  var coord = gl.getAttribLocation(shaderProgram, "coordinates");

  // point an attribute to the currently bound VBO
  gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);

  // Enable the attribute
  gl.enableVertexAttribArray(coord);

  // bind the color buffer
  gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);

  // get the attribute location
  var color = gl.getAttribLocation(shaderProgram, "color");

  // point attribute to the volor buffer object
  gl.vertexAttribPointer(color, 3, gl.FLOAT, false,0,0) ;

  // enable the color attribute
  gl.enableVertexAttribArray(color);


  // Clear the canvas
  gl.clearColor(1.0, 1.0, 1.0, 1.0);

  // Enable the depth test
  gl.disable(gl.DEPTH_TEST);

  // Clear the color buffer bit
  gl.clear(gl.COLOR_BUFFER_BIT);

  resize(gl.canvas);

   gl.viewport(0, 0, dim, dim);

  //Draw the triangle
  gl.drawArrays(gl.POINTS, 0, points.length);

}

function redraw(repoints){
  //OverviewtSNE(repoints);
  BetatSNE(repoints);
  //CosttSNE(repoints);
}

function handleLassoEnd(lassoPolygon) {
  var countLassoFalse = 0;
  KNNEnabled = true;
  if (toggleValue == false){
      for (var i = 0 ; i < points.length ; i ++) {
      x = points[i].x;
      y = points[i].y;
      if (d3.polygonContains(lassoPolygon, [x, y])){
          points[i].selected = true;
          points2d[i].selected = true;
      } else{
        countLassoFalse = countLassoFalse + 1;
        points[i].selected = false;
        points2d[i].selected = true;
      }
    }
    if (countLassoFalse == points.length){
      for (var i = 0 ; i < points.length ; i ++) {
        points[i].selected = true;
        points2d[i].selected = true;
      }
    }
    if (points.length - countLassoFalse <= 10 && points.length - countLassoFalse != 0){
      for (var i = 0 ; i < points.length ; i ++) {
        if (points[i].selected == true){
          points[i].starplot = true;
        }
      }
    } else{
      for (var i = 0 ; i < points.length ; i ++) {
        points[i].starplot = false;
      }
    }
    redraw(points);
  } else{
    click();
  }
 
}

// reset selected points when starting a new polygon
function handleLassoStart(lassoPolygon) {
  if (toggleValue == true){
  } else{
    KNNEnabled = false;
    for (var i = 0 ; i < points.length ; i ++) {
    points[i].selected = true;
    points[i].starplot = false;
    points2d[i].selected = true;
  }

  redraw(points);
  }
}


var svg,
    defs,
    gBrush,
    brush,
    main_xScale,
    mini_xScale,
    main_yScale,
    mini_yScale,
    main_xAxis,
    main_yAxis,
    mini_width,
    textScale;

            //Added only for the mouse wheel
            var zoomer = d3v3.behavior.zoom()
            .on("zoom", null);

         var main_margin = {top: 8, right: 10, bottom: 30, left: 100},
             main_width = 500 - main_margin.left - main_margin.right,
             main_height = 320 - main_margin.top - main_margin.bottom;
     
         var mini_margin = {top: 8, right: 10, bottom: 30, left: 10},
             mini_height = 320 - mini_margin.top - mini_margin.bottom;
             mini_width = 100 - mini_margin.left - mini_margin.right;
     
         svg = d3v3.select("#correlation").attr("class", "svgWrapper")
             .attr("width", main_width + main_margin.left + main_margin.right + mini_width + mini_margin.left + mini_margin.right)
             .attr("height", main_height + main_margin.top + main_margin.bottom)
             .call(zoomer)
             .on("wheel.zoom", scroll)
             .on("mousedown.zoom", null)
             .on("touchstart.zoom", null)
             .on("touchmove.zoom", null)
             .on("touchend.zoom", null);

function click(){

    let svgClick = d3.select('#modtSNEcanvas_svg');

    function drawCircle(x, y, size) {
      svgClick.append("circle")
            .attr('class', 'click-circle')
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", size); 
        Arrayx.push(x);
        Arrayy.push(y); 
    }
    svgClick.on('click', function() {
      var coords = d3.mouse(this);
      drawCircle(coords[0], coords[1], 4);
  });

  //if (Arrayx.length == 1){
  //}
  svgClick.on("contextmenu", function (d) {

            // Prevent the default mouse action. Allow right click to be used for the confirmation of our schema.
            d3.event.preventDefault();

            var line = d3.line().curve(d3.curveCardinal);

            for (var k = 0; k < Arrayx.length ; k++){
              Arrayxy[k] = [Arrayx[k], Arrayy[k]];
            }

            for (var loop = 0; loop < points.length ; loop++){
                allTransformPoints[loop] = [points[loop].x, points[loop].y, points[loop].id, points[loop].beta, points[loop].cost, points[loop].selected];
            }
          
            for (var k = 0; k < Arrayxy.length - 1; k++){
              path = svgClick.append("path")
              .datum(Arrayxy.slice(k, k+2))
              .attr("class", "SchemaCheck")
              .attr("d", line);
            }

            var line = svgClick.append("line");

            paths = svgClick.selectAll("path").filter(".SchemaCheck");

            var correlLimit = document.getElementById("param-corr-value").value;
            correlLimit = parseInt(correlLimit);
            if (paths.nodes().length == 0){
              alert("Please, provide one more point in order to create a line (i.e., path)!")
            } else{
              for (var m = 0; m < paths.nodes().length; m++) {
              for (var j = 0; j < allTransformPoints.length; j++){
                p = closestPoint(paths.nodes()[m], allTransformPoints[j]);
                XYDistId.push(p);
              }
            }

              for (var j = 0; j < allTransformPoints.length; j++){
                for (var m = 0; m < paths.nodes().length; m++) {
                if (m == 0){
                  minimum = XYDistId[j].distance;
                }
                else if (minimum > XYDistId[(m * allTransformPoints.length) + j].distance) {
                  minimum = XYDistId[(m * allTransformPoints.length) + j].distance;
                }
              }

              for (var l = 0; l < paths.nodes().length ; l++) {
                if (XYDistId[(l * allTransformPoints.length) + j].distance == minimum){
                  allTransformPoints[j].bucketID = l;
                }
              }
            }

            var arrays = [], size = allTransformPoints.length;
            while (XYDistId.length > 0) {
                arrays.push(XYDistId.splice(0, size));
            }
            
            var arraysCleared = [];
            for (var j = 0; j < allTransformPoints.length; j++){
              for (var m=0; m < arrays.length; m++) {
                if (allTransformPoints[j].bucketID == m){
                  arraysCleared.push(arrays[m][j].concat(allTransformPoints[j].bucketID, Arrayxy[m], arrays[m][j].distance, arrays[m][j].id));
                }
              }
            }
        
            for (var i=0; i<arraysCleared.length; i++) {
              if (arraysCleared[i][5] < correlLimit) {
                ArrayLimit.push(arraysCleared[i]);
              }
            }
            
            var temparray = [];
            var count = new Array(paths.nodes().length).fill(0);
            for (var m=0; m < paths.nodes().length; m++) {
              for (var i=0; i<ArrayLimit.length; i++) {
                if (ArrayLimit[i][2] == m){
                    count[m] = count[m] + 1;
                  temparray.push(ArrayLimit[i]);
                }
                // do whatever
              }
            }
            var arraysSplitted = [];
              for (var m=0; m < paths.nodes().length; m++) {
                    arraysSplitted.push(temparray.splice(0, count[m]));
            }
            
  

            for (var m=0; m < paths.nodes().length; m++) {
              arraysSplitted[m] = arraysSplitted[m].sort(function(a, b){
                      var dist = (a[0]-a[3]) * (a[0]-a[3]) + (a[1]-a[4]) * (a[1]-a[4]);
                      var distAgain = (b[0]-b[3]) * (b[0]-b[3]) + (b[1]-b[4]) * (b[1]-b[4]);
                      // Compare the 2 dates
                      if(dist < distAgain) return -1;
                      if(distAgain > dist) return 1;
                      return 0;
              });
            }

            var arraysConnected = [];
            if (paths.nodes().length == 1) {
                arraysConnected = arraysSplitted[0];
            } else {
              for (var m=0; m < paths.nodes().length - 1; m++) {
                arraysConnected = arraysSplitted[m].concat(arraysSplitted[m+1]);
              }
            }
            
            var Order = [];
            for (var temp = 0; temp < arraysConnected.length; temp++) {
              Order.push(arraysConnected[temp][6]);
            }
            
            for (var i = 0; i < points.length; i++){
              points[i].selected = false;
              for (var j = 0; j < ArrayLimit.length; j++){
                if (ArrayLimit[j][6] == points[i].id){
                  points[i].selected = true;
                }
              }
            }
            redraw(points);

            ArrayContainsDataFeatures = mapOrder(ArrayContainsDataFeatures, Order, 5);

            for (var i = 0; i < ArrayContainsDataFeatures.length; i++){
              for (var j = 0; j < arraysConnected.length; j++){
                if (ArrayContainsDataFeatures[i][5] == arraysConnected[j][6]){
                  ArrayContainsDataFeaturesLimit.push(ArrayContainsDataFeatures[i]);
                }
              }
            }
            
            for (var loop = 0; loop < ArrayContainsDataFeaturesLimit.length; loop++) {
              ArrayContainsDataFeaturesLimit[loop].push(loop);
            }

            for (var k = 0; k < Arrayxy.length - 1 ; k++){
              d3.select('#modtSNEcanvas_svg').append('line')
              .attr("x1", Arrayxy[k][0])
              .attr("y1", Arrayxy[k][1])
              .attr("x2", Arrayxy[k+1][0])
              .attr("y2", Arrayxy[k+1][1])
              .style("stroke","black")
              .style("stroke-width",2);
            } 

            var SignStore = [];
            const arrayColumn = (arr, n) => arr.map(x => x[n]);
            for (var temp = 0; temp < ArrayContainsDataFeaturesLimit[0].length - 2; temp++) {
              var tempData = new Array(
                arrayColumn(ArrayContainsDataFeaturesLimit, temp),
                arrayColumn(ArrayContainsDataFeaturesLimit, ArrayContainsDataFeaturesLimit[0].length - 1)
              );
              if (isNaN(pearsonCorrelation(tempData, 0, 1))) {
              } else{
                SignStore.push([temp, pearsonCorrelation(tempData, 0, 1)]);
                correlationResults.push(["Dimension "+temp, Math.abs(pearsonCorrelation(tempData, 0, 1))]);
              }
            }
            correlationResults = correlationResults.sort(
              function(a,b) {
              if (a[1] == b[1])
              return a[0] < b[0] ? -1 : 1;
              return a[1] < b[1] ? 1 : -1;
              }
            );

            for (var j = 0; j < correlationResults.length; j++) {
              for (var i = 0; i < SignStore.length; i++) {
                if (SignStore[i][1]*(-1) == correlationResults[j][1]) {
                  correlationResults[j][1] = parseInt(correlationResults[j][1] * 100) * (-1);
                  correlationResults[j].push(j);
                  //correlationResults[j][1] = correlationResults[j][1].toFixed(2)*(-1);
                }
                if (SignStore[i][1] == correlationResults[j][1]) {
                  correlationResults[j][1] = parseInt(correlationResults[j][1] * 100);
                  correlationResults[j].push(j);
                }
              }
            }
          /////////////////////////////////////////////////////////////
          ///////////////// Set-up SVG and wrappers ///////////////////
          /////////////////////////////////////////////////////////////
  

              var mainGroup = svg.append("g")
              .attr("class","mainGroupWrapper")
              .attr("transform","translate(" + main_margin.left + "," + main_margin.top + ")")
              .append("g") //another one for the clip path - due to not wanting to clip the labels
              .attr("clip-path", "url(#clip)")
              .style("clip-path", "url(#clip)")
              .attr("class","mainGroup")
  
              var miniGroup = svg.append("g")
                      .attr("class","miniGroup")
                      .attr("transform","translate(" + (main_margin.left + main_width + main_margin.right + mini_margin.left) + "," + mini_margin.top + ")");
          
              var brushGroup = svg.append("g")
                      .attr("class","brushGroup")
                      .attr("transform","translate(" + (main_margin.left + main_width + main_margin.right + mini_margin.left) + "," + mini_margin.top + ")");
      
          /////////////////////////////////////////////////////////////
          ////////////////////// Initiate scales //////////////////////
          /////////////////////////////////////////////////////////////

          main_xScale = d3v3.scale.linear().range([0, main_width]);
          mini_xScale = d3v3.scale.linear().range([0, mini_width]);
      
          main_yScale = d3v3.scale.ordinal().rangeBands([0, main_height], 0.4, 0);
          mini_yScale = d3v3.scale.ordinal().rangeBands([0, mini_height], 0.4, 0);
          //Based on the idea from: http://stackoverflow.com/questions/21485339/d3-brushing-on-grouped-bar-chart
          main_yZoom = d3v3.scale.linear()
          .range([0, main_height])
          .domain([0, main_height]);

          //Create x axis object
          main_xAxis = d3v3.svg.axis()
          .scale(main_xScale)
          .orient("bottom")
          .ticks(8)
          .outerTickSize(0);

          //Add group for the x axis
          d3v3.select(".mainGroupWrapper").append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(" + 0 + "," + (main_height + 5) + ")");

          //Create y axis object
          main_yAxis = d3v3.svg.axis()
          .scale(main_yScale)
          .orient("left")
          .tickSize(0)
          .outerTickSize(0);

          //Add group for the y axis
          mainGroup.append("g")
          .attr("class", "y axis")
          .attr("transform", "translate(-5,0)");

          /////////////////////////////////////////////////////////////
          /////////////////////// Update scales ///////////////////////
          /////////////////////////////////////////////////////////////
          //Update the scales
          main_xScale.domain([-100, 100]);
          mini_xScale.domain([-100, 100]);     
          main_yScale.domain(correlationResults.map(function(d) { return d[0]; }));
          mini_yScale.domain(correlationResults.map(function(d) { return d[0]; }));
          
          //Create the visual part of the y axis
          d3v3.select(".mainGroup").select(".y.axis").call(main_yAxis);
          d3v3.select(".mainGroupWrapper").select(".x.axis").call(main_xAxis);
      
          /////////////////////////////////////////////////////////////
          ///////////////////// Label axis scales /////////////////////
          /////////////////////////////////////////////////////////////
      
          textScale = d3v3.scale.linear()
            .domain([15,50])
            .range([12,6])
            .clamp(true);
            
          /////////////////////////////////////////////////////////////
          ///////////////////////// Create brush //////////////////////
          /////////////////////////////////////////////////////////////
         
          //What should the first extent of the brush become - a bit arbitrary this
          var brushExtent = parseInt(Math.max( 1, Math.min( 20, Math.round(correlationResults.length * 0.75) ) ));

          brush = d3v3.svg.brush()
            .y(mini_yScale)
            .extent([mini_yScale(correlationResults[0][0]), mini_yScale(correlationResults[brushExtent][0])])
            .on("brush", brushmove)

          //Set up the visual part of the brush
          gBrush = d3v3.select(".brushGroup").append("g")
          .attr("class", "brush")
          .call(brush);
          
          gBrush.selectAll(".resize")
            .append("line")
            .attr("x2", mini_width);

          gBrush.selectAll(".resize")
            .append("path")
            .attr("d", d3v3.svg.symbol().type("triangle-up").size(20))
            .attr("transform", function(d,i) { 
              return i ? "translate(" + (mini_width/2) + "," + 4 + ") rotate(180)" : "translate(" + (mini_width/2) + "," + -4 + ") rotate(0)"; 
            });
 
          gBrush.selectAll("rect")
            .attr("width", mini_width);

          //On a click recenter the brush window
          gBrush.select(".background")
            .on("mousedown.brush", brushcenter)
            .on("touchstart.brush", brushcenter);
          ///////////////////////////////////////////////////////////////////////////
          /////////////////// Create a rainbow gradient - for fun ///////////////////
          ///////////////////////////////////////////////////////////////////////////
      
          defs = svg.append("defs")
      
          //Create two separate gradients for the main and mini bar - just because it looks fun
          createGradient("gradient-main", "60%");
          createGradient("gradient-mini", "13%");
      
          //Add the clip path for the main bar chart
          defs.append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("x", -main_margin.left)
            .attr("width", main_width + main_margin.left)
            .attr("height", main_height);
      
          /////////////////////////////////////////////////////////////
          /////////////// Set-up the mini bar chart ///////////////////
          /////////////////////////////////////////////////////////////
      
          //The mini brushable bar
          //DATA JOIN
          var mini_bar = d3v3.select(".miniGroup").selectAll(".bar")
            .data(correlationResults, function(d) { return +d[2]; });

          //UDPATE
        mini_bar
          .attr("width", function(d) { return Math.abs(mini_xScale(d[1]) - mini_xScale(0)); })
          .attr("y", function(d,i) { return mini_yScale(d[0]); })
          .attr("height", mini_yScale.rangeBand())

        //ENTER
        mini_bar.enter().append("rect")
          .attr("class", "bar")
          .attr("x", function (d) { return mini_xScale(Math.min(0, d[1])); })
          .attr("width", function(d) { return Math.abs(mini_xScale(d[1]) - mini_xScale(0)); })
          .attr("y", function(d,i) { return mini_yScale(d[0]); })
          .attr("height", mini_yScale.rangeBand())
          .style("fill", "url(#gradient-mini)");

          //EXIT
          mini_bar.exit()
            .remove();

          //Start the brush
          //gBrush.call(brush.event);
          gBrush.call(brush.event);
        }
      });
            
}

//Function runs on a brush move - to update the big bar chart
function updateBarChart() {

  /////////////////////////////////////////////////////////////
  ////////// Update the bars of the main bar chart ////////////
  /////////////////////////////////////////////////////////////

  var bar = d3v3.select(".mainGroup").selectAll(".bar")
      .data(correlationResults, function(d) { return +d[2]; })
  //, function(d) { return d.key; });

  bar
    .attr("x", function (d) { return main_xScale(Math.min(0, d[1])); })
    .attr("width", function(d) { return Math.abs(main_xScale(d[1]) - main_xScale(0)); })
    .attr("y", function(d,i) { return main_yScale(d[0]); })
    .attr("height", main_yScale.rangeBand());
  
  //ENTER
  bar.enter().append("rect")
    .attr("class", "bar")
    .style("fill", "url(#gradient-main)")
    .attr("x", function (d) { return main_xScale(Math.min(0, d[1])); })
    .attr("width", function(d) { return Math.abs(main_xScale(d[1]) - main_xScale(0)); })
    .attr("y", function(d,i) { return main_yScale(d[0]); })
    .attr("height", main_yScale.rangeBand())
    .on("mouseover", () => {
      svg.select('.tooltip').style('display', 'none'); 
    })
    .on("mouseout", function(d){ 
      points.forEach(function (p) {
        p.DimON = null
      })
      BetatSNE(points);
      svg.select('.tooltip').style('display', 'none'); 
    })
    .on("mousemove", function(d) {
      points.forEach(function (p) {
        if (p.selected == true) {
          p.DimON = d[0];
        }
      })
      BetatSNE(points);
    });

  //EXIT
  bar.exit()
    .remove();
}//update

/////////////////////////////////////////////////////////////
////////////////////// Brush functions //////////////////////
/////////////////////////////////////////////////////////////

//First function that runs on a brush move
function brushmove() {

  var extent = brush.extent();

   //Reset the part that is visible on the big chart
   var originalRange = main_yZoom.range();
   main_yZoom.domain( extent );

  //Update the domain of the x & y scale of the big bar chart
  main_yScale.domain(correlationResults.map(function(d) { return d[0]; }));
  main_yScale.rangeBands( [ main_yZoom(originalRange[0]), main_yZoom(originalRange[1]) ], 0.4, 0);

  //Update the y axis of the big chart
  d3v3.select(".mainGroup")
    .select(".y.axis")
    .call(main_yAxis);
    
  //Which bars are still "selected"
  var selected = mini_yScale.domain()
    .filter(function(d) { return (extent[0] - mini_yScale.rangeBand () + 1e-2 <= mini_yScale(d)) && (mini_yScale(d) <= extent[1] - 1e-2); }); 

    //Update the colors of the mini chart - Make everything outside the brush grey
 d3.select(".miniGroup").selectAll(".bar")
    .style("fill", function(d, i) { return selected.indexOf(d[0]) > -1 ? "url(#gradient-mini)" : "#e0e0e0"; });

  //Update the label size
  d3v3.selectAll(".y.axis text")
    .style("font-size", textScale(selected.length));
  
  //Update the big bar chart
  updateBarChart();
  
}//brushmove

/////////////////////////////////////////////////////////////
////////////////////// Click functions //////////////////////
/////////////////////////////////////////////////////////////

  //Based on http://bl.ocks.org/mbostock/6498000
  //What to do when the user clicks on another location along the brushable bar chart
  function brushcenter() {
    var target = d3v3.event.target,
        extent = brush.extent(),
        size = extent[1] - extent[0],
        range = mini_yScale.range(),
        y0 = d3v3.min(range) + size / 2,
        y1 = d3.max(range) + mini_yScale.rangeBand() - size / 2,
        center = Math.max( y0, Math.min( y1, d3.mouse(target)[1] ) );

    d3v3.event.stopPropagation();

    gBrush
        .call(brush.extent([center - size / 2, center + size / 2]))
        .call(brush.event);

  }//brushcenter

  function scroll() {

    //Mouse scroll on the mini chart
    var extent = brush.extent(),
      size = extent[1] - extent[0],
      range = mini_yScale.range(),
      y0 = d3v3.min(range),
      y1 = d3v3.max(range) + mini_yScale.rangeBand(),
      dy = d3v3.event.deltaY,
      topSection;

    if ( extent[0] - dy < y0 ) { topSection = y0; } 
    else if ( extent[1] - dy > y1 ) { topSection = y1 - size; } 
    else { topSection = extent[0] - dy; }

    //Make sure the page doesn't scroll as well
    d3v3.event.stopPropagation();
    d3v3.event.preventDefault();

    gBrush
        .call(brush.extent([ topSection, topSection + size ]))
        .call(brush.event);

  }//scroll

/////////////////////////////////////////////////////////////
///////////////////// Helper functions //////////////////////
/////////////////////////////////////////////////////////////

//Create a gradient 
function createGradient(idName, endPerc) {

  var colorsBarChart = ['#7f3b08','#b35806','#e08214','#fdb863','#fee0b6','#d8daeb','#b2abd2','#8073ac','#542788','#2d004b']

  colorsBarChart.reverse();

  defs.append("linearGradient")
    .attr("id", idName)
    .attr("gradientUnits", "userSpaceOnUse")
    .attr("x1", "0%").attr("y1", "0%")
    .attr("x2", endPerc).attr("y2", "0%")
    .selectAll("stop") 
    .data(colorsBarChart)                  
    .enter().append("stop") 
    .attr("offset", function(d,i) { return i/(colorsBarChart.length-1); })   
    .attr("stop-color", function(d) { return d; });
}//createGradient

function mapOrder(array, order, key) {
  
  array.sort( function (a, b) {
    var A = a[key], B = b[key];
    if (order.indexOf(A) > order.indexOf(B)) {
      return 1;
    } else {
      return -1;
    }
    
  });
  
  return array;
};

    /**
   *  Calculate the person correlation score between two items in a dataset.
   *
   *  @param  {object}  prefs The dataset containing data about both items that
   *                    are being compared.
   *  @param  {string}  p1 Item one for comparison.
   *  @param  {string}  p2 Item two for comparison.
   *  @return {float}  The pearson correlation score.
   */
  function pearsonCorrelation(prefs, p1, p2) {
    var si = [];

    for (var key in prefs[p1]) {
      if (prefs[p2][key]) si.push(key);
    }

    var n = si.length;

    if (n == 0) return 0;

    var sum1 = 0;
    for (var i = 0; i < si.length; i++) sum1 += prefs[p1][si[i]];

    var sum2 = 0;
    for (var i = 0; i < si.length; i++) sum2 += prefs[p2][si[i]];

    var sum1Sq = 0;
    for (var i = 0; i < si.length; i++) {
      sum1Sq += Math.pow(prefs[p1][si[i]], 2);
    }

    var sum2Sq = 0;
    for (var i = 0; i < si.length; i++) {
      sum2Sq += Math.pow(prefs[p2][si[i]], 2);
    }

    var pSum = 0;
    for (var i = 0; i < si.length; i++) {
      pSum += prefs[p1][si[i]] * prefs[p2][si[i]];
    }

    var num = pSum - (sum1 * sum2 / n);
    var den = Math.sqrt((sum1Sq - Math.pow(sum1, 2) / n) *
        (sum2Sq - Math.pow(sum2, 2) / n));
        
    if (den == 0) return 0;

    return num / den;
  }

  function closestPoint(pathNode, point) {
    var pathLength = pathNode.getTotalLength(),
        precision = 8,
        best,
        bestLength,
        bestDistance = Infinity;

    // linear scan for coarse approximation
    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
      if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
        best = scan, bestLength = scanLength, bestDistance = scanDistance;
      }
    }
  
    // binary search for precise estimate
    precision /= 2;
    while (precision > 0.5) {
      var before,
          after,
          beforeLength,
          afterLength,
          beforeDistance,
          afterDistance;
      if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
        best = before, bestLength = beforeLength, bestDistance = beforeDistance;
      } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
        best = after, bestLength = afterLength, bestDistance = afterDistance;
      } else {
        precision /= 2;
      }
    }
  
    best = [best.x, best.y];
    best.distance = Math.sqrt(bestDistance);
    best.id = point[2];
    return best;
  
    function distance2(p) {
      var dx = p.x - point[0],
          dy = p.y - point[1];
      return dx * dx + dy * dy;
    }



// Points are represented as objects with x and y attributes.



 /* var svg = d3.select('#modtSNEcanvas_svgClick').append('svg')
    .attr('width', width)
    .attr('height', height)
    .on('mousemove', function() {
      x = d3.event.pageX;
      y = d3.event.pageY;
      console.log( d3.event.pageX, d3.event.pageY ) // log the mouse x,y position
    });


  var interactionSvgClick = d3.select("#modtSNEcanvas_svgClick").append("circle")
        .attr("transform", "translate(" + x + "," + y + ")")
        .attr("r", "3")
        .attr("class", "dot")
        .style('position', 'absolute')
        .style("cursor", "pointer");*/
      //.call(drag);
}
/*
// Define drag behavior
var drag = d3.drag()
    .on("drag", dragmove);

function dragmove(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
}
*/

function abbreviateNumber(value) {
  var newValue = value;
  if (value >= 1000) {
      var suffixes = ["", "k", "m", "b","t"];
      var suffixNum = Math.floor( (""+value).length/3 );
      var shortValue = '';
      for (var precision = 2; precision >= 1; precision--) {
          shortValue = parseFloat( (suffixNum != 0 ? (value / Math.pow(1000,suffixNum) ) : value).toPrecision(precision));
          var dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g,'');
          if (dotLessShortValue.length <= 2) { break; }
      }
      if (shortValue % 1 != 0)  shortNum = shortValue.toFixed(1);
      newValue = shortValue+suffixes[suffixNum];
  }
  return newValue;
}

function clearThree(obj){
  while(obj.children.length > 0){ 
    clearThree(obj.children[0])
    obj.remove(obj.children[0]);
  }
  if(obj.geometry) obj.geometry.dispose()
  if(obj.material) obj.material.dispose()
  if(obj.texture) obj.texture.dispose()
}   


var viewport3 = getViewport();
var vw3 = viewport3[0] * 0.2;
var margin = {top: 40, right: 100, bottom: 40, left: 190},
width = Math.min(vw3, window.innerWidth - 10) - margin.left - margin.right,
height = Math.min(width, window.innerHeight - margin.top - margin.bottom);

  var wrapData = [];

  ////////////////////////////////////////////////////////////// 
  //////////////////// Draw the Chart ////////////////////////// 
  ////////////////////////////////////////////////////////////// 

    
  var radarChartOptions = {
    w: width,
    h: height,
    margin: margin,
    maxValue: 100,
    roundStrokes: true
  };
  var colors;
  var IDS = [];
  //Call function to draw the Radar chart
  RadarChart("#starPlot", wrapData, colors, IDS, radarChartOptions);

function BetatSNE(points){
  
  selectedPoints = [];
  var findNearestTable = [];
  for (let m=0; m<points.length; m++){
   if (points[m].selected == true){
      selectedPoints.push(points[m]);
   }
  }
  if (KNNEnabled == true && selectedPoints.length != 0){
    var distsFull = dists;
    var dists2dFull = dists2d;

    for (var i=0; i<dists.length; i++){
      for (var j=0; j<dists.length; j++){
        if(dists[i][j] != null) {
          distsFull[j][i] = dists[i][j];
          dists2dFull[j][i] = dists2d[i][j];
        }
      }
    }

    var indexOrder = [];
    var indexOrder2d = [];
    var indices = new Array(selectedPoints.length);
    var indices2d = new Array(selectedPoints.length);

    var findNearest;
    var counter1;
    var counter2;

    var temp = [];
    var temp2 = [];

    var viewport = getViewport();
    var vw = viewport[0] * 0.5;
    var vh = viewport[1] * 0.042;
    var factor = Math.log10(points.length) * 4;
    if (factor == 0){
      factor = 1;
    }
    var maxKNN = Math.ceil(points.length / factor);

    selectedPoints.sort(function(a, b) {
      return parseFloat(a.id) - parseFloat(b.id);
    });
  
  
    for (k=maxKNN; k>0; k--){

      findNearest = 0;
      var indexOrderSliced = [];
      var indexOrderSliced2d = [];
      var count1 = new Array(selectedPoints.length).fill(0);
      var count2 = new Array(selectedPoints.length).fill(0);
      counter1 = 0;
      counter2 = 0;

      for (var i=0; i<selectedPoints.length; i++){

        temp[i] = 0;
        temp2[i] = 0;

        if (k == maxKNN){


            // temporary array holds objects with position and sort-value
            indices[i] = dists[i].map(function(el, i) {
                return [ i, el ];
            })
            var index = indices[i].indexOf(selectedPoints[i].id);
            if (index > -1) {
              indices[i].splice(index, 1);
            }
            // sorting the mapped array containing the reduced values
            indices[i].sort(function(a, b) {
              if (a[1] > b[1]) {
                return 1;
              }
              if (a[1] < b[1]) {
                return -1;
              }
              return 0;
            });
    
            indexOrder[i] = indices[i].map(function(value) { return value[0]; });

            // temporary array holds objects with position and sort-value
            indices2d[i] = dists2d[i].map(function(el, i) {
                return [ i, el ];
            })
            var index2d = indices2d[i].indexOf(selectedPoints[i].id);
            if (index2d > -1) {
              indices2d[i].splice(index2d, 1);
            }
            
            // sorting the mapped array containing the reduced values
            indices2d[i].sort(function(a, b) {
              if (a[1] > b[1]) {
                return 1;
              }
              if (a[1] < b[1]) {
                return -1;
              }
              return 0;
            });
            indexOrder2d[i] = indices2d[i].map(function(value) { return value[0]; });
          }
          indexOrderSliced[i] = indexOrder[i].slice(0,k);
          indexOrderSliced2d[i] = indexOrder2d[i].slice(0,k);

        for (var m=0; m < indexOrderSliced2d[i].length; m++){
          if (indexOrderSliced[i].includes(indexOrderSliced2d[i][m])){
            count1[i] = count1[i] + 1;
            temp[i] = temp[i] + 1;
          }
          if(indexOrderSliced[i][m] == indexOrderSliced2d[i][m]){
            count2[i] = count2[i] + 1;
            temp2[i] = temp2[i] + 1;

          }
        }  

        if (count1[i] != 0){
          counter1 = (count1[i] / temp[i]) + counter1;
        }
        if (count2[i] != 0){
          counter2 = (count2[i] / temp2[i]) + counter2;
        }

      }

        sumUnion = counter1 / selectedPoints.length;
        sumIntersection = counter2 / selectedPoints.length;
        if (sumUnion == 0){
          findNearest = 0;
        } else{
          findNearest = sumIntersection / sumUnion;
        }

        if (isNaN(findNearest)){
          findNearest = 0; 
        }
        findNearestTable.push(findNearest * vh * 2);
    }
    findNearestTable.reverse();

    var barPadding = 5;
        d3v3.select("#knnBarChart").selectAll("rect").remove();

    var svg2 = d3v3.select('#knnBarChart')
      .attr("class", "bar-chart");


    var barWidth = (vw / findNearestTable.length);

    var knnBarChartSVG = svg2.selectAll("rect")
      .data(findNearestTable)
      .enter()
      .append("rect")
      .attr("y", function(d) {
          return Math.round(vh*2 - d)
      })
      .attr("height", function(d) {
        return d;
      })
      .attr("width", barWidth - barPadding)
      .attr("transform", function (d, i) {
          var translate = [barWidth * i, 0];
          return "translate("+ translate +")";
      });
    }




    d3.select("#starPlot").selectAll('g').remove();

    if(selectedPoints.length <= 10){
  
      var FeatureWise = [];
  
      for (var j=0; j<Object.values(dataFeatures[0]).length; j++){
        for (var i=0;i<dataFeatures.length;i++){
          if (!isNaN(Object.values(dataFeatures[i])[j])){
            FeatureWise.push(Object.values(dataFeatures[i])[j]);
          }
        }
      }
  
      var sum = new Array(Object.values(dataFeatures[0]).length).fill(0);
  
      for (var j=0; j<Object.values(dataFeatures[0]).length; j++){
        var FeatureWiseSliced = FeatureWise.slice(0+(j*dataFeatures.length),dataFeatures.length+j*dataFeatures.length);
        for (var i=0; i<FeatureWiseSliced.length; i++){
          sum[j] = FeatureWiseSliced[i] + sum[j];
        }
      }
  
      var wrapData = [];
      var IDS = [];
      for (var i=0; i<selectedPoints.length; i++){
        var data = [];
        for (var j=0; j< Object.keys(dataFeatures[selectedPoints[i].id]).length; j++){
          if (!isNaN(Object.values(dataFeatures[i])[j])){
            if (Object.keys(dataFeatures[i])[j] == "name") {
              
            } else{
              data.push({axis:Object.keys(dataFeatures[i])[j],value:Math.abs(Object.values(dataFeatures[i])[j]*100/sum[j])});
            }
          }
        }
        wrapData.push(data);
        IDS.push(selectedPoints[i].id);
      }
        ////////////////////////////////////////////////////////////// 
        //////////////////// Draw the Chart ////////////////////////// 
        ////////////////////////////////////////////////////////////// 
        var colors = ['#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69','#fccde5','#d9d9d9','#bc80bd'];
        var colorScl = d3v3.scale.ordinal()
          .domain(IDS)
          .range(colors);

        var radarChartOptions = {
          w: width,
          h: height,
          margin: margin,
          levels: 10,
          roundStrokes: true,
        };
        //Call function to draw the Radar chart
        RadarChart("#starPlot", wrapData, colorScl, IDS, radarChartOptions);
  }

  if(step_counter == max_counter || step_counter == 1){
    if (step_counter == 1){
      d3.select("#modtSNEcanvas_svg").select("g").remove();
    }else{
      if (toggleValue == false){ 
        interactionSvg = d3.select("#modtSNEcanvas_svg")
        .attr("width", dimensions)
        .attr("height", dimensions)
        .style('position', 'absolute')
        .style('top', 0)
        .style('left', 0);
        var lassoInstance = lasso()
          .on('end', handleLassoEnd)
          .on('start', handleLassoStart);

        interactionSvg.call(lassoInstance);  
      }
     }
  }
  
 var ColSizeSelector = document.getElementById("param-neighborHood").value;

 if (ColSizeSelector == "color") {
  var max = (d3.max(final_dataset,function(d){ return d.beta; }));
  var min = (d3.min(final_dataset,function(d){ return d.beta; }));
  // colors
  var colorbrewer = ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"];
  var calcStep = (max-min)/7;
  var colorScale = d3.scaleLinear()
    .domain(d3.range(0, max+calcStep, calcStep))
    .range(colorbrewer);

  var maxSize1 = (d3.max(final_dataset,function(d){ return d.cost; }));
  var minSize1 = (d3.min(final_dataset,function(d){ return d.cost; }));

  var rscale1 = d3.scaleLinear()
    .domain([minSize1, maxSize1])
    .range([5,10]);

  var colorScale = d3.scaleLinear()
    .domain(d3.range(0, max+calcStep, calcStep))
    .range(colorbrewer);

  /*points = points.sort(function(a, b) {
    return a.beta - b.beta;
  })*/
  var labels_beta = [];
  var abbr_labels_beta = [];
  labels_beta = d3.range(0, max+calcStep, calcStep);
  for (var i=0; i<9; i++){
    labels_beta[i] = parseInt(labels_beta[i]);
    abbr_labels_beta[i] = abbreviateNumber(labels_beta[i]);
  }
  var svg = d3.select("#legend1");

    svg.append("g")
      .attr("class", "legendLinear")
      .attr("transform", "translate(10,15)");

    var legend = d3.legendColor()
      .labelFormat(d3.format(",.0f"))
      .cells(9)
      .labels([abbr_labels_beta[0],abbr_labels_beta[1],abbr_labels_beta[2],abbr_labels_beta[3],abbr_labels_beta[4],abbr_labels_beta[5],abbr_labels_beta[6],abbr_labels_beta[7],abbr_labels_beta[8]])
      .title("1 / sigma")
      .scale(colorScale);
      
    svg.select(".legendLinear")
      .call(legend);
} else {
var max = (d3.max(final_dataset,function(d){ return d.cost; }));
var min = (d3.min(final_dataset,function(d){ return d.cost; }));

var maxSize2 = (d3.max(final_dataset,function(d){ return d.beta; }));
var minSize2 = (d3.min(final_dataset,function(d){ return d.beta; }));

var rscale2 = d3.scaleLinear()
  .domain([minSize2, maxSize2])
  .range([5,10]);

var colorbrewer = ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"];
var calcStep = (max-min)/9;
var colorScale = d3.scaleLinear()
  .domain(d3.range(min, max, calcStep))
  .range(colorbrewer);
  
  /*points = points.sort(function(a, b) {
    return a.cost - b.cost;
  })*/

  var labels_cost = [];
  var abbr_labels_cost = [];
  labels_cost = d3.range(min, max, calcStep);
  for (var i=0; i<9; i++){
    labels_cost[i] = labels_cost[i].toFixed(5);
    abbr_labels_cost[i] = abbreviateNumber(labels_cost[i]);
  }

  var svg = d3.select("#legend1");

  svg.append("g")
      .attr("class", "legendLinear")
      .attr("transform", "translate(10,15)");

  var legend = d3.legendColor()
    .labelFormat(d3.format(",.5f"))
    .cells(9)
    .labels([abbr_labels_cost[0],abbr_labels_cost[1],abbr_labels_cost[2],abbr_labels_cost[3],abbr_labels_cost[4],abbr_labels_cost[5],abbr_labels_cost[6],abbr_labels_cost[7],abbr_labels_cost[8]])
    .title("KLD(P||Q)")
    .scale(colorScale);

  svg.select(".legendLinear")
    .call(legend);
}

let viz_width = dimensions;

window.addEventListener('resize', () => {
  dimensions = window.innerWidth;
  viz_width = dimensions;
  dimensions = window.innerHeight;

  renderer.setSize(dimensions, dimensions);
  camera.aspect = dimensions / dimensions;
  camera.updateProjectionMatrix();
})

let zoom = d3.zoom()
  .scaleExtent([getScaleFromZ(far), getScaleFromZ(near)])
  .on('zoom', () =>  {
    let d3_transform = d3.event.transform;
    zoomHandler(d3_transform);
  });

view = d3.select(renderer.domElement);
function setUpZoom() {
  view.call(zoom);    
  let initial_scale = getScaleFromZ(far);
  var initial_transform = d3.zoomIdentity.translate(viz_width/2, dimensions/2).scale(initial_scale);    
  zoom.transform(view, initial_transform);
  camera.position.set(0, 0, far);
}
setUpZoom();

var circle_sprite= new THREE.TextureLoader().load(
  "./textures/circle-sprite.png"
)

let pointsGeometry = new THREE.Geometry();

clearThree(scene);

// Increase/reduce size factor selected by the user
var limitdist = document.getElementById("param-lim-value").value;
limitdist = parseFloat(limitdist).toFixed(1);

let pointsMaterial = [];
let factorPlusSize = [];
for (var i=0; i<points.length; i++) {

  let vertex = new THREE.Vector3((((points[i].x/dimensions)*2) - 1)*dimensions, (((points[i].y/dimensions)*2) - 1)*dimensions*-1, 0);
  pointsGeometry.vertices.push(vertex);

  if (points[i].selected == false){
    var color = new THREE.Color("rgb(211, 211, 211)");
  } else if (points[i].DimON != null) {

    let temp = points[i].DimON.match(/\d+/)[0];

    var maxDim = (d3.max(points,function(d){ if(d.selected == true){return d[temp]}; }));
    var minDim = (d3.min(points,function(d){ if(d.selected == true){return d[temp]}; }));  

    let colorsBarChart = ['#fcfbfd','#efedf5','#dadaeb','#bcbddc','#9e9ac8','#807dba','#6a51a3','#54278f','#3f007d'];
    var calcStepDim = (maxDim-minDim)/8;
    var colorScale = d3.scaleLinear()
      .domain(d3.range(minDim, maxDim+calcStepDim, calcStepDim))
      .range(colorsBarChart);
    var color = new THREE.Color(colorScale(points[i][temp]));
      
  } else if(points[i].starplot == true){
    var color = new THREE.Color(colorScl(points[i].id));
  } else if (ColSizeSelector == "color") {
    var color = new THREE.Color(colorScale(points[i].beta));
  }
  else{
    var color = new THREE.Color(colorScale(points[i].cost));
  }
  if (ColSizeSelector == "color") {
    let sizePoint = rscale1(points[i].cost);
    factorPlusSize[i] = limitdist * sizePoint;
    pointsGeometry.colors.push(color);
    pointsMaterial[i] = new THREE.PointsMaterial({
      size: factorPlusSize[i],
      sizeAttenuation: false,
      vertexColors: THREE.VertexColors,
      map: circle_sprite,
      transparent: true
    });
  } else{
    let sizePoint = rscale2(points[i].beta);
    factorPlusSize[i] = limitdist * sizePoint;
    pointsGeometry.colors.push(color);
    pointsMaterial[i] = new THREE.PointsMaterial({
      size: factorPlusSize[i],
      sizeAttenuation: false,
      vertexColors: THREE.VertexColors,
      map: circle_sprite,
      transparent: true
    });
  }
  var particles = new THREE.Points(pointsGeometry, pointsMaterial[i]);
  scene.add(particles);
}

let tempSort = -1;

for (var i=0; i<points.length; i++){
if (points[i].DimON != null) {
  tempSort = points[i].DimON.match(/\d+/)[0];
}
}

if (tempSort != -1){
/*points = points.sort(function(a, b) {
    return a[tempSort] - b[tempSort];
})*/
}

var temporal = 0;
for (var j=0; j < points.length; j++){
if(points[j].DimON != null) {
      temporal = temporal + 1;
      var labels_dim = [];
      var abbr_labels_dim = [];
      labels_dim = d3.range(minDim, maxDim+calcStepDim, calcStepDim);

      for (var i=0; i<9; i++){
        labels_dim[i] = labels_dim[i].toFixed(2);
        abbr_labels_dim[i] = abbreviateNumber(labels_dim[i]);
      }
      d3.select("#legend1").selectAll('*').remove();
      var svg = d3.select("#legend1");

      svg.append("g")
        .attr("class", "legendLinear")
        .attr("transform", "translate(10,15)");
        
      var legend = d3.legendColor()
        .labelFormat(d3.format(",.0f"))
        .cells(9)
        .labels([abbr_labels_dim[0],abbr_labels_dim[1],abbr_labels_dim[2],abbr_labels_dim[3],abbr_labels_dim[4],abbr_labels_dim[5],abbr_labels_dim[6],abbr_labels_dim[7],abbr_labels_dim[8]])
        .title(points[j].DimON)
        .scale(colorScale);

      svg.select(".legendLinear")
        .call(legend);
      break;
    } 
}
// This is for the legend
for (var j=0; j < points.length; j++){
if(temporal == 0 && points[j].DimON == null){
    if (ColSizeSelector == "color"){
      d3.select("#legend1").selectAll('*').remove();
      var svg = d3.select("#legend1");
  
      svg.append("g")
        .attr("class", "legendLinear")
        .attr("transform", "translate(10,15)");
  
      var legend = d3.legendColor()
        .labelFormat(d3.format(",.0f"))
        .cells(9)
        .labels([abbr_labels_beta[0],abbr_labels_beta[1],abbr_labels_beta[2],abbr_labels_beta[3],abbr_labels_beta[4],abbr_labels_beta[5],abbr_labels_beta[6],abbr_labels_beta[7],abbr_labels_beta[8]])
        .title("1 / sigma")
        .scale(colorScale);
  
      svg.select(".legendLinear")
        .call(legend);
      break;
    } else {
      d3.select("#legend1").selectAll('*').remove();
      var svg = d3.select("#legend1");

      svg.append("g")
        .attr("class", "legendLinear")
        .attr("transform", "translate(10,15)");

      var legend = d3.legendColor()
        .labelFormat(d3.format(".4f"))
        .cells(9)
        .labels([abbr_labels_cost[0],abbr_labels_cost[1],abbr_labels_cost[2],abbr_labels_cost[3],abbr_labels_cost[4],abbr_labels_cost[5],abbr_labels_cost[6],abbr_labels_cost[7],abbr_labels_cost[8]])
        .title("KLD(P||Q)")
        .scale(colorScale);

      svg.select(".legendLinear")
        .call(legend);
      break;
    }
  }
}

function zoomHandler(d3_transform) {
  let scale = d3_transform.k;
  let x = -(d3_transform.x - viz_width/2) / scale;
  let y = (d3_transform.y - dimensions/2) / scale;
  let z = getZFromScale(scale);
  camera.position.set(x, y, z);
}

function getScaleFromZ (camera_z_position) {
  let half_fov = fov/2;
  let half_fov_radians = toRadians(half_fov);
  let half_fov_height = Math.tan(half_fov_radians) * camera_z_position;
  let fov_height = half_fov_height * 2;
  let scale = dimensions / fov_height; // Divide visualization height by height derived from field of view
  return scale;
}

function getZFromScale(scale) {
  let half_fov = fov/2;
  let half_fov_radians = toRadians(half_fov);
  let scale_height = dimensions / scale;
  let camera_z_position = scale_height / (2 * Math.tan(half_fov_radians));
  return camera_z_position;
}

function toRadians (angle) {
  return angle * (Math.PI / 180);
}

// Hover and tooltip interaction

raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 10;

view.on("mousemove", () => {
  let [mouseX, mouseY] = d3.mouse(view.node());
  let mouse_position = [mouseX, mouseY];
checkIntersects(mouse_position);
});

function mouseToThree(mouseX, mouseY) {
  return new THREE.Vector3(
    mouseX / viz_width * 2 - 1,
    -(mouseY / dimensions) * 2 + 1,
    1
  );
}
function checkIntersects(mouse_position) {
  let mouse_vector = mouseToThree(...mouse_position);
  raycaster.setFromCamera(mouse_vector, camera);
  let intersects = raycaster.intersectObject(particles);
  if (intersects[0]) {
    let sorted_intersects = sortIntersectsByDistanceToRay(intersects);
    let intersect = sorted_intersects[0];
    let index = intersect.index;
    let datum = points[index];
    highlightPoint(datum);
    showTooltip(mouse_position, datum);
  } else {
    removeHighlights();
    hideTooltip();
  }
}

function sortIntersectsByDistanceToRay(intersects) {
  return _.sortBy(intersects, "distanceToRay");
}

hoverContainer = new THREE.Object3D()
scene.add(hoverContainer);

function highlightPoint(datum) {
  removeHighlights();
  
  let geometry = new THREE.Geometry();
  geometry.vertices.push(
    new THREE.Vector3(
      (((datum.x/dimensions)*2) - 1)*dimensions,
      (((datum.y/dimensions)*2) - 1)*dimensions*-1,
      0
    )
  );

  if (all_labels[0] == undefined){
    var colorScaleCat = d3.scaleOrdinal(d3.schemeCategory10).domain(["No Category"]).range(["#0000ff"]);
  }
  else{
    var colorScaleCat = d3.scaleOrdinal(d3.schemeCategory10).domain(all_labels);
  }

  geometry.colors = [ new THREE.Color(colorScaleCat(datum.name)) ];

  let material = new THREE.PointsMaterial({
    size: 26,
    sizeAttenuation: false,
    vertexColors: THREE.VertexColors,
    map: circle_sprite,
    transparent: true
  });
  
  let point = new THREE.Points(geometry, material);
  hoverContainer.add(point);
}

function removeHighlights() {
  hoverContainer.remove(...hoverContainer.children);
}

view.on("mouseleave", () => {
  removeHighlights()
});

// Initial tooltip state
let tooltip_state = { display: "none" }
let tooltip_dimensions;
let tooltip_template = document.createRange().createContextualFragment(`<div id="tooltip" style="display: none; z-index: 2; position: absolute; pointer-events: none; font-size: 13px; width: 240px; text-align: center; line-height: 1; padding: 6px; background: white; font-family: sans-serif;">
  <div id="point_tip" style="padding: 4px; margin-bottom: 4px;"></div>
  <div id="group_tip" style="padding: 4px;"></div>
</div>`);
document.body.append(tooltip_template);

let $tooltip = document.querySelector('#tooltip');
let $point_tip = document.querySelector('#point_tip');
let $group_tip = document.querySelector('#group_tip');

function updateTooltip() {
  if (all_labels[0] == undefined){
    var colorScaleCat = d3.scaleOrdinal(d3.schemeCategory10).domain(["No Category"]).range(["#0000ff"]);
  }
  else{
    var colorScaleCat = d3.scaleOrdinal(d3.schemeCategory10).domain(all_labels);
  }
  $tooltip.style.display = tooltip_state.display;
  $tooltip.style.left = tooltip_state.left + 'px';
  $tooltip.style.top = tooltip_state.top + 'px';
  $point_tip.innerText = tooltip_state.name;
  $point_tip.style.background = colorScaleCat(tooltip_state.color);
  $group_tip.innerText = `Data set's features: ${tooltip_dimensions}`;
}

function showTooltip(mouse_position, datum) {
  let tooltip_width = 240;
  let x_offset = tooltip_width + tooltip_width;
  let y_offset = 30;
  tooltip_state.display = "block";
  tooltip_state.left = mouse_position[0] + x_offset;
  tooltip_state.top = mouse_position[1] + y_offset;
  if (all_labels[0] == undefined){
    tooltip_state.name = datum.id;
    tooltip_state.color = datum.id;
  } else{
    tooltip_state.name = datum.name + " (" + datum.id + ")";
    tooltip_state.color = datum.name;
  }
  tooltip_dimensions = [];
  for (var i=0; i < ArrayContainsDataFeaturesCleared.length; i++){
    if (datum.id == i){
      for (var j=0; j < ArrayContainsDataFeaturesCleared[i].length; j++){
        tooltip_dimensions.push(ArrayContainsDataFeaturesCleared[i][j]);
      }
    }
  }
  updateTooltip();
}

function hideTooltip() {
  tooltip_state.display = "none";
  updateTooltip();
}

  
}

  function getViewport() {

    var viewPortWidth;
    var viewPortHeight;
  
    // the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
    if (typeof window.innerWidth != 'undefined') {
      viewPortWidth = window.innerWidth,
      viewPortHeight = window.innerHeight
    }
  
  // IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
    else if (typeof document.documentElement != 'undefined'
    && typeof document.documentElement.clientWidth !=
    'undefined' && document.documentElement.clientWidth != 0) {
      viewPortWidth = document.documentElement.clientWidth,
      viewPortHeight = document.documentElement.clientHeight
    }
  
    // older versions of IE
    else {
      viewPortWidth = document.getElementsByTagName('body')[0].clientWidth,
      viewPortHeight = document.getElementsByTagName('body')[0].clientHeight
    }
    return [viewPortWidth, viewPortHeight];
 }

 

 /*
  var canvas = document.getElementById('modtSNEcanvas');
  var gl = canvas.getContext('webgl');
  // If we don't have a GL context, give up now

  if (!gl) {
    alert('Unable to initialize WebGL. Your browser or machine may not support it.');
    return;
  }

  var ColSizeSelector = document.getElementById("param-neighborHood").value;

  if (ColSizeSelector == "color") {
      var max = (d3.max(final_dataset,function(d){ return d.beta; }));
      var min = (d3.min(final_dataset,function(d){ return d.beta; }));
      // colors
      var colorbrewer = ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"];
      var calcStep = (max-min)/7;
      var colorScale = d3.scaleLinear()
        .domain(d3.range(0, max+calcStep, calcStep))
        .range(colorbrewer);
    
      var maxSize1 = (d3.max(final_dataset,function(d){ return d.cost; }));
      var minSize1 = (d3.min(final_dataset,function(d){ return d.cost; }));

      var rscale1 = d3.scaleLinear()
        .domain([minSize1, maxSize1])
        .range([5,10]);

      var colorScale = d3.scaleLinear()
        .domain(d3.range(0, max+calcStep, calcStep))
        .range(colorbrewer);

      points = points.sort(function(a, b) {
        return a.beta - b.beta;
      })
      var labels_beta = [];
      var abbr_labels_beta = [];
      labels_beta = d3.range(0, max+calcStep, calcStep);
      for (var i=0; i<9; i++){
        labels_beta[i] = parseInt(labels_beta[i]);
        abbr_labels_beta[i] = abbreviateNumber(labels_beta[i]);
      }
      var svg = d3.select("#legend1");

        svg.append("g")
          .attr("class", "legendLinear")
          .attr("transform", "translate(10,15)");
    
        var legend = d3.legendColor()
          .labelFormat(d3.format(",.0f"))
          .cells(9)
          .labels([abbr_labels_beta[0],abbr_labels_beta[1],abbr_labels_beta[2],abbr_labels_beta[3],abbr_labels_beta[4],abbr_labels_beta[5],abbr_labels_beta[6],abbr_labels_beta[7],abbr_labels_beta[8]])
          .title("1 / sigma")
          .scale(colorScale);
          
        svg.select(".legendLinear")
          .call(legend);
  } else {
    var max = (d3.max(final_dataset,function(d){ return d.cost; }));
    var min = (d3.min(final_dataset,function(d){ return d.cost; }));

    var maxSize2 = (d3.max(final_dataset,function(d){ return d.beta; }));
    var minSize2 = (d3.min(final_dataset,function(d){ return d.beta; }));

    var rscale2 = d3.scaleLinear()
      .domain([minSize2, maxSize2])
      .range([5,10]);

    var colorbrewer = ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"];
    var calcStep = (max-min)/9;
    var colorScale = d3.scaleLinear()
      .domain(d3.range(min, max, calcStep))
      .range(colorbrewer);
      
      points = points.sort(function(a, b) {
        return a.cost - b.cost;
      })

      var labels_cost = [];
      var abbr_labels_cost = [];
      labels_cost = d3.range(min, max, calcStep);
      for (var i=0; i<9; i++){
        labels_cost[i] = labels_cost[i].toFixed(5);
        abbr_labels_cost[i] = abbreviateNumber(labels_cost[i]);
      }

      var svg = d3.select("#legend1");

      svg.append("g")
          .attr("class", "legendLinear")
          .attr("transform", "translate(10,15)");

      var legend = d3.legendColor()
        .labelFormat(d3.format(",.5f"))
        .cells(9)
        .labels([abbr_labels_cost[0],abbr_labels_cost[1],abbr_labels_cost[2],abbr_labels_cost[3],abbr_labels_cost[4],abbr_labels_cost[5],abbr_labels_cost[6],abbr_labels_cost[7],abbr_labels_cost[8]])
        .title("KLD(P||Q)")
        .scale(colorScale);

      svg.select(".legendLinear")
        .call(legend);
  }
  
  let vertices = [];
  let colors = [];
  let sizes = new Float32Array(points.length);
  let tempSort = -1;

  for (var i=0; i<points.length; i++){
    if (points[i].DimON != null) {
      tempSort = points[i].DimON.match(/\d+/)[0];
    }
  }
  
  if (tempSort != -1){
    points = points.sort(function(a, b) {
        return a[tempSort] - b[tempSort];
    })
  }
  

  for (var i=0; i<points.length; i++){
    let singleObj = {};
    // convert the position from pixels to 0.0 to 1.0
   let zeroToOne = points[i].x / dimensions;
   let zeroToOne2 = points[i].y / dimensions;

   // convert from 0->1 to 0->2
   let zeroToTwo = zeroToOne * 2.0;
   let zeroToTwo2 = zeroToOne2 * 2.0;

   // convert from 0->2 to -1->+1 (clipspace)
   let clipSpace = zeroToTwo - 1.0;
   let clipSpace2 = zeroToTwo2 - 1.0;
    singleObj = clipSpace;
    vertices.push(singleObj);
    singleObj = clipSpace2 * -1;
    vertices.push(singleObj);
    singleObj = 0.0;
    vertices.push(singleObj);
  }

  for (var i=0; i<points.length; i++){
    let singleCol = {};

    if (ColSizeSelector == "color"){
      let singleSizeCol = rscale1(points[i].cost);
      sizes[i] = singleSizeCol;
    } else {
      let singleSizeCol = rscale2(points[i].beta);
      sizes[i] = singleSizeCol;
    }

    if (points[i].selected == false){
      let colval = d3.rgb(211,211,211);
      singleCol = colval.r/255;
      colors.push(singleCol);
      singleCol = colval.g/255;
      colors.push(singleCol);
      singleCol = colval.b/255;
      colors.push(singleCol);
    } else if (points[i].DimON != null) {
      
      let temp = points[i].DimON.match(/\d+/)[0];

      var maxDim = (d3.max(points,function(d){ if(d.selected == true){return d[temp]}; }));
      var minDim = (d3.min(points,function(d){ if(d.selected == true){return d[temp]}; }));  

      let colorsBarChart = ['#fcfbfd','#efedf5','#dadaeb','#bcbddc','#9e9ac8','#807dba','#6a51a3','#54278f','#3f007d'];
      var calcStepDim = (maxDim-minDim)/8;
      var colorScaleDim = d3.scaleLinear()
        .domain(d3.range(minDim, maxDim+calcStepDim, calcStepDim))
        .range(colorsBarChart);

      let colval = d3.rgb(colorScaleDim(points[i][temp]));
      singleCol = colval.r/255;
      colors.push(singleCol);
      singleCol = colval.g/255;
      colors.push(singleCol);
      singleCol = colval.b/255;
      colors.push(singleCol);
        
    } else if (ColSizeSelector == "color") {
      let colval = d3.rgb(colorScale(points[i].beta));
      singleCol = colval.r/255;
      colors.push(singleCol);
      singleCol = colval.g/255;
      colors.push(singleCol);
      singleCol = colval.b/255;
      colors.push(singleCol);
    }
    else{
      let colval = d3.rgb(colorScale(points[i].cost));
      singleCol = colval.r/255;
      colors.push(singleCol);
      singleCol = colval.g/255;
      colors.push(singleCol);
      singleCol = colval.b/255;
      colors.push(singleCol);
    }
  }

  var temporal = 0;
  for (var j=0; j < points.length; j++){
    if(points[j].DimON != null) {
          temporal = temporal + 1;
          var labels_dim = [];
          var abbr_labels_dim = [];
          labels_dim = d3.range(minDim, maxDim+calcStepDim, calcStepDim);

          for (var i=0; i<9; i++){
            labels_dim[i] = labels_dim[i].toFixed(2);
            abbr_labels_dim[i] = abbreviateNumber(labels_dim[i]);
          }
          d3.select("#legend1").selectAll('*').remove();
          var svg = d3.select("#legend1");

          svg.append("g")
            .attr("class", "legendLinear")
            .attr("transform", "translate(10,15)");
            
          var legend = d3.legendColor()
            .labelFormat(d3.format(",.0f"))
            .cells(9)
            .labels([abbr_labels_dim[0],abbr_labels_dim[1],abbr_labels_dim[2],abbr_labels_dim[3],abbr_labels_dim[4],abbr_labels_dim[5],abbr_labels_dim[6],abbr_labels_dim[7],abbr_labels_dim[8]])
            .title(points[j].DimON)
            .scale(colorScaleDim);

          svg.select(".legendLinear")
            .call(legend);
          break;
        } 
  }
  for (var j=0; j < points.length; j++){
    if(temporal == 0 && points[j].DimON == null){
        if (ColSizeSelector == "color"){
          d3.select("#legend1").selectAll('*').remove();
          var svg = d3.select("#legend1");
      
          svg.append("g")
            .attr("class", "legendLinear")
            .attr("transform", "translate(10,15)");
      
          var legend = d3.legendColor()
            .labelFormat(d3.format(",.0f"))
            .cells(9)
            .labels([abbr_labels_beta[0],abbr_labels_beta[1],abbr_labels_beta[2],abbr_labels_beta[3],abbr_labels_beta[4],abbr_labels_beta[5],abbr_labels_beta[6],abbr_labels_beta[7],abbr_labels_beta[8]])
            .title("1 / sigma")
            .scale(colorScale);
      
          svg.select(".legendLinear")
            .call(legend);
          break;
        } else {
          d3.select("#legend1").selectAll('*').remove();
          var svg = d3.select("#legend1");

          svg.append("g")
            .attr("class", "legendLinear")
            .attr("transform", "translate(10,15)");

          var legend = d3.legendColor()
            .labelFormat(d3.format(".4f"))
            .cells(9)
            .labels([abbr_labels_cost[0],abbr_labels_cost[1],abbr_labels_cost[2],abbr_labels_cost[3],abbr_labels_cost[4],abbr_labels_cost[5],abbr_labels_cost[6],abbr_labels_cost[7],abbr_labels_cost[8]])
            .title("KLD(P||Q)")
            .scale(colorScale);

          svg.select(".legendLinear")
            .call(legend);
          break;
        }
      }
    }
  // Create an empty buffer object and store vertex data
  var vertex_buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  // Create an empty buffer object and store color data
  var color_buffer = gl.createBuffer ();
  gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

  var size_buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, size_buffer);
  gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);

  // Increase/reduce size factor selected by the user
  var limitdist = document.getElementById("param-lim-value").value;
  limitdist = parseFloat(limitdist).toFixed(1);

  // vertex shader source code
  var vertCode = 'attribute vec3 coordinates;'+
    'attribute float vSizes;'+
    'attribute vec3 color;'+
    'varying vec3 vColor;'+
    'void main(void) {' +
       'gl_Position = vec4(coordinates, 1.0);' +
       'vColor = color;'+
       'gl_PointSize = '+ limitdist + '*vSizes;'+
    '}';

  // Create a vertex shader object
  var vertShader = gl.createShader(gl.VERTEX_SHADER);

  // Attach vertex shader source code
  gl.shaderSource(vertShader, vertCode);

  // Compile the vertex shader
  gl.compileShader(vertShader);

  // fragment shader source code
    var fragCode = `
    #ifdef GL_OES_standard_derivatives
    #extension GL_OES_standard_derivatives : enable
    #endif

    precision mediump float;
    varying  vec3 vColor;

    void main()
    {
        float r = 0.0, delta = 0.0, alpha = 1.0;
        vec2 cxy = 2.0 * gl_PointCoord - 1.0;
        r = dot(cxy, cxy);
    #ifdef GL_OES_standard_derivatives
        delta = fwidth(r);
        alpha = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, r);
    #endif

    gl_FragColor = vec4(vColor, alpha);
    gl_FragColor.rgb *= gl_FragColor.a;
    }`;

    gl.getExtension('OES_standard_derivatives');

  // Create fragment shader object
  var fragShader = gl.createShader(gl.FRAGMENT_SHADER);

  // Attach fragment shader source code
  gl.shaderSource(fragShader, fragCode);

  // Compile the fragmentt shader
  gl.compileShader(fragShader);

  // Create a shader program object to
  // store the combined shader program
  var shaderProgram = gl.createProgram();

  // Attach a vertex shader
  gl.attachShader(shaderProgram, vertShader);

  // Attach a fragment shader
  gl.attachShader(shaderProgram, fragShader);

  // Link both the programs
  gl.linkProgram(shaderProgram);

  // Use the combined shader program object
  gl.useProgram(shaderProgram);



  // Bind vertex buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);

  // Get the attribute location
  var coord = gl.getAttribLocation(shaderProgram, "coordinates");

  // point an attribute to the currently bound VBO
  gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);

  // Enable the attribute
  gl.enableVertexAttribArray(coord);

  // bind the color buffer
  gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);

  // get the attribute location
  var color = gl.getAttribLocation(shaderProgram, "color");

  // point attribute to the volor buffer object
  gl.vertexAttribPointer(color, 3, gl.FLOAT, false, 0, 0) ;

  // enable the color attribute
  gl.enableVertexAttribArray(color);

  gl.bindBuffer(gl.ARRAY_BUFFER, size_buffer);

  var vSize = gl.getAttribLocation(shaderProgram, "vSizes");

  gl.vertexAttribPointer(vSize, 1, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(vSize);

  // Clear the canvas
  gl.clearColor(1.0, 1.0, 1.0, 1.0);

  gl.enable( gl.BLEND );
  gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );

  gl.disable(gl.DEPTH_TEST);

  // Clear the color buffer bit
  gl.clear(gl.COLOR_BUFFER_BIT);

  resize(gl.canvas);

  gl.viewport(0, 0, dimensions, dimensions);
  
  //Draw the triangle
  gl.drawArrays(gl.POINTS, 0, points.length);
*/