// t-SNE Visualization and global variables // This variable is used when a new file is upload by a user. var new_file; // The basic variables in order to execute t-SNE (opt is perplexity and learning rate). var tsne; var opt; var step_counter; var max_counter; var runner; // These variables are initialized here in order to store the final dataset, the points, the cost, the cost for each iteration, the beta values, the positions, the 2D points positions, // In addition, there is an array which keeps the initial information of the points (i.e., initial state), the data features (with the label of the category plus the id of the point), the data features without the category (only numbers). var final_dataset; var points = []; var cost = []; var cost_each; var beta_all = []; var x_position = []; var y_position = []; var points2d = []; var ArrayContainsDataFeatures = []; var ArrayContainsDataFeaturesCleared = []; var InitialStatePoints = []; // The distances in the high dimensional space and in the 2D space. All the labels that were found in the selected data set. var dists; var dists2d; var all_labels; // These are the dimensions for the Overview view and the Main view var dim = document.getElementById('tSNEcanvas').offsetWidth; var dimensions = document.getElementById('modtSNEcanvas').offsetWidth; // Category = the name of the category if it exists. The user has to add an asterisk ("*") mark in order to let the program identify this feature as a label/category name. // ColorsCategorical = the categorical colors (maximum value = 10). var Category; var ColorsCategorical; // Schema Investigation // svgClick = Click a left mouse click in order to add a point. // prevRightClick = When right click is pressed prevent any other action. Lock the current schema. // if flagForSchema is false then send a message to the user that he/she has to: "Please, draw a schema first!"); var svgClick; var prevRightClick; var flagForSchema = false; // Save the parameters for the current analysis, save the overallCost, and store in the "input" variable all the points and points2D. var ParametersSet = []; var overallCost; var input; // This function is executed when the factory button is pressed in order to bring the visualization in the initial state. function FactoryReset(){ flagForSchema = false; d3.selectAll("#modtSNEcanvas_svg_Schema > *").remove(); d3.selectAll("#SvgAnnotator > *").remove(); d3.select("#data").select("input").remove(); // Remove the selection field. Arrayx = []; Arrayy = []; XYDistId = []; Arrayxy = []; DistanceDrawing1D = []; allTransformPoints = []; p; pFinal = []; paths; path; ArrayLimit = []; minimum; correlationResults = []; ArrayContainsDataFeaturesLimit = []; prevRightClick = false; for (var i=0; i < InitialStatePoints.length; i++){ InitialStatePoints[i].selected = true; InitialStatePoints[i].starplot = false; } redraw(InitialStatePoints); d3.selectAll("#correlation > *").remove(); d3.selectAll("#modtSNEcanvas_svg > *").remove(); d3.selectAll("#modtSNEcanvas_svg_Schema > *").remove(); d3.selectAll("#SvgAnnotator > *").remove(); d3.selectAll("#sheparheat > *").remove(); d3.selectAll("#knnBarChart > *").remove(); var oldcanvOver = document.getElementById('tSNEcanvas'); var contxOver = oldcanvOver.getContext('experimental-webgl'); contxOver.clear(contxOver.COLOR_BUFFER_BIT); scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff); d3.selectAll("#legend1 > *").remove(); d3.selectAll("#legend3 > *").remove(); d3.selectAll("#legend4 > *").remove(); lassoEnable(); Arrayx = []; Arrayy = []; XYDistId = []; Arrayxy = []; DistanceDrawing1D = []; allTransformPoints = []; p; pFinal = []; paths; path; ArrayLimit = []; minimum; correlationResults = []; ArrayContainsDataFeaturesLimit = []; document.getElementById("param-dataset").value = "iris.csv"; document.getElementById('file-input').value = ""; document.getElementById("ExecuteBut").innerHTML = "Execute new t-SNE analysis"; $("#cost").html(""); $("#datasetDetails").html(""); $("#kNNDetails").html(""); document.getElementById("param-perplexity-value").value = 30; document.getElementById("param-learningrate-value").value = 10; document.getElementById("param-maxiter-value").value = 500; document.getElementById("param-lim-value").value = 2; document.getElementById("param-corr-value").value = 150; document.getElementById("param-neighborHood").value = "color"; document.getElementById('selectionLabel').innerHTML = 'Size'; document.getElementById("param-distance").value = "euclideanDist"; document.getElementById("param-transform").value = "noTrans"; } // Load a previously executed analysis function. function loadAnalysis(){ document.getElementById('file-input').click(); document.getElementById("ExecuteBut").innerHTML = "Execute previous t-SNE analysis"; } // This function is being used when the user selects to upload a new data set. function getfile(file){ new_file = file; //uploaded data file } // Read the previous analysis, which the user wants to upload. function fetchVal(callback) { var file, fr; file = input.files[0]; fr = new FileReader(); fr.onload = function (e) { lines = e.target.result; callback(lines); }; fr.readAsText(file); } // Parse the data set var getData = function() { let format; let value; if (typeof window.FileReader !== 'function') { alert("The file API isn't supported on this browser yet."); } input = document.getElementById("file-input"); if (!input) { alert("Um, couldn't find the fileinput element."); } else if (!input.files) { alert("This browser doesn't seem to support the `files` property of file inputs."); } else if (!input.files[0]) { value = document.getElementById("param-dataset").value; // get the value of the data set format = document.getElementById("param-dataset").value.split("."); //get the format if (format[value.split(".").length-1] == "csv") { parseData("./data/"+value); }else{ parseData(new_file, init); } } else { fetchVal(function(lines){ AnalaysisResults = JSON.parse(lines); length = (AnalaysisResults.length - 7) / 2; ParametersSet = AnalaysisResults.slice(length*2+1, length*2+7); value = document.getElementById("param-dataset").value = ParametersSet[0]; 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(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", 1); } var ringNotes = []; var gAnnotationsAll = []; var AnnotationsAll = []; var draggable = []; function setReset(){ d3.selectAll("#correlation > *").remove(); d3.selectAll("#modtSNEcanvas_svg > *").remove(); lassoEnable(); flagForSchema = false; d3.selectAll("#modtSNEcanvas_svg_Schema > *").remove(); d3.selectAll("#SvgAnnotator > *").remove(); Arrayx = []; Arrayy = []; XYDistId = []; Arrayxy = []; DistanceDrawing1D = []; allTransformPoints = []; p; pFinal = []; paths; path; ArrayLimit = []; minimum; correlationResults = []; ArrayContainsDataFeaturesLimit = []; prevRightClick = false; for (var i=0; i < InitialStatePoints.length; i++){ InitialStatePoints[i].selected = true; InitialStatePoints[i].starplot = false; } redraw(InitialStatePoints); } function setReInitialize(){ if (document.getElementById('selectionLabel').innerHTML == 'Size'){ document.getElementById('selectionLabel').innerHTML = 'Color'; } else{ document.getElementById('selectionLabel').innerHTML = 'Size'; } 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); d3.select("#modtSNEcanvas_svg_Schema").style("z-index", 1); } function setLayerComp(){ d3.select("#modtSNEcanvas_svg").style("z-index", 2); d3.select("#modtSNEcanvas_svg_Schema").style("z-index", 1); d3.select("#modtSNEcanvas").style("z-index", 1); if (points.length){ lassoEnable(); } } function setLayerSche(){ d3.select("#modtSNEcanvas_svg_Schema").style("z-index", 2); d3.select("#modtSNEcanvas").style("z-index", 1); d3.select("#modtSNEcanvas_svg").style("z-index", 1); let c = 0; for (var i=0; i < points.length; i++){ points[i].selected = true; if (points[i].starplot == true){ c = c + 1; if (c == 1){ alert("The starplot visualization will be lost!"); } points[i].starplot = false; } } redraw(points); click(); } function lassoEnable(){ var 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); } 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{ // Get the checkbox. var checkBox = document.getElementById("controls"); // Unchecked! checkBox.checked = false; // Print a message to the user. 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; 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(); 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 that executes after data is successfully loaded function init(data, results_all, fields) { d3.selectAll("#correlation > *").remove(); d3.selectAll("#modtSNEcanvas_svg > *").remove(); d3.selectAll("#modtSNEcanvas_svg_Schema > *").remove(); d3.selectAll("#SvgAnnotator > *").remove(); d3.selectAll("#sheparheat > *").remove(); d3.selectAll("#knnBarChart > *").remove(); var oldcanvOver = document.getElementById('tSNEcanvas'); var contxOver = oldcanvOver.getContext('experimental-webgl'); contxOver.clear(contxOver.COLOR_BUFFER_BIT); scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff); d3.selectAll("#legend1 > *").remove(); d3.selectAll("#legend3 > *").remove(); d3.selectAll("#legend4 > *").remove(); lassoEnable(); Arrayx = []; Arrayy = []; XYDistId = []; Arrayxy = []; DistanceDrawing1D = []; allTransformPoints = []; p; pFinal = []; paths; path; ArrayLimit = []; minimum; correlationResults = []; ArrayContainsDataFeaturesLimit = []; prevRightClick = false; step_counter = 0; max_counter = document.getElementById("param-maxiter-value").value; opt = {}; var fields; fields.push("beta"); fields.push("cost"); 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; all_labels = []; dataFeatures.filter(function(obj) { var temp = []; temp.push(Object.keys(obj)); for (var object in temp[0]){ if(temp[0][object].indexOf("*") != -1){ Category = temp[0][object]; return Category; } } }); for (let k = 0; k < dataFeatures.length; k++){ ArrayContainsDataFeatures.push(Object.values(dataFeatures[k]).concat(k)); object = []; for (let j = 0; j < Object.keys(dataFeatures[k]).length; j++){ if(typeof(Object.values(dataFeatures[k])[j]) == "number" && Object.keys(dataFeatures[k])[j] != Category){ object.push(Object.values(dataFeatures[k])[j]); } } ArrayContainsDataFeaturesCleared.push(object); } var valCategExists = 0; for (var i=0; i 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(AnalaysisResults) { if (AnalaysisResults == ""){ 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]); } } else{ points = AnalaysisResults.slice(0,dataFeatures.length); points2d = AnalaysisResults.slice(dataFeatures.length,2*dataFeatures.length); overallCost = AnalaysisResults.slice(dataFeatures.length*2,dataFeatures.length*2+1); ParametersSet = AnalaysisResults.slice(dataFeatures.length*2+1, dataFeatures.length*2+7); $("#cost").html("Number of Iteration: " + ParametersSet[3] + ", Overall Cost: " + overallCost); $('#param-perplexity-value').text(ParametersSet[1]); $('#param-learningrate-value').text(ParametersSet[2]); $('#param-maxiter-value').text(ParametersSet[3]); document.getElementById("param-distance").value = ParametersSet[4]; document.getElementById("param-transform").value = ParametersSet[5]; } InitialStatePoints = points; function extend(obj, src) { for (var key in src) { if (src.hasOwnProperty(key)) obj[key] = src[key]; } return obj; } ShepardHeatMap(); OverviewtSNE(points); BetatSNE(points); } function ShepardHeatMap () { d3.selectAll("#sheparheat > *").remove(); 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 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" + 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,14)"); 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) { 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); } if (step_counter == max_counter){ updateEmbedding(AnalaysisResults); } } 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; } } function OverviewtSNE(points){ 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; } ColorsCategorical = ['#a6cee3','#fb9a99','#b2df8a','#33a02c','#1f78b4','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a']; if (all_labels[0] == undefined){ var colorScale = d3.scaleOrdinal().domain(["No Category"]).range(["#C0C0C0"]); } else{ var colorScale = d3.scaleOrdinal().domain(all_labels).range(ColorsCategorical); } 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; i1 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 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)); } } } ArrayLimit = []; for (var i=0; i 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][arraysConnected[0].length-1]); } for (var i = 0; i < points.length; i++){ points[i].selected = false; for (var j = 0; j < ArrayLimit.length; j++){ if (ArrayLimit[j][ArrayLimit[0].length-1] == points[i].id){ points[i].selected = true; } } } redraw(points); for (let k = 0; k < dataFeatures.length; k++){ ArrayContainsDataFeaturesCleared.push(ArrayContainsDataFeaturesCleared[k].concat(k)); } ArrayContainsDataFeaturesCleared = mapOrder(ArrayContainsDataFeaturesCleared, Order, arraysConnected[0].length-2); ArrayContainsDataFeaturesLimit = []; for (var i = 0; i < ArrayContainsDataFeaturesCleared.length; i++){ for (var j = 0; j < arraysConnected.length; j++){ if (ArrayContainsDataFeaturesCleared[i][ArrayContainsDataFeaturesCleared[0].length-1] == arraysConnected[j][arraysConnected[0].length-1]){ ArrayContainsDataFeaturesLimit.push(ArrayContainsDataFeaturesCleared[i]); } } } if (ArrayContainsDataFeaturesLimit.length == 0){ d3.selectAll("#correlation > *").remove(); d3.selectAll("#modtSNEcanvas_svg > *").remove(); flagForSchema = false; d3.selectAll("#modtSNEcanvas_svg_Schema > *").remove(); Arrayx = []; Arrayy = []; XYDistId = []; Arrayxy = []; DistanceDrawing1D = []; allTransformPoints = []; p; pFinal = []; paths; path; ArrayLimit = []; minimum; correlationResults = []; ArrayContainsDataFeaturesLimit = []; prevRightClick = false; for (var i=0; i < InitialStatePoints.length; i++){ InitialStatePoints[i].selected = true; InitialStatePoints[i].starplot = false; } alert("No points selected! Please, try to increase the correlation threshold."); redraw(InitialStatePoints); } else { for (var loop = 0; loop < ArrayContainsDataFeaturesLimit.length; loop++) { ArrayContainsDataFeaturesLimit[loop].push(loop); } var SignStore = []; correlationResults = []; 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([Object.keys(dataFeatures[0])[temp] + " (" + 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); } if (SignStore[i][1] == correlationResults[j][1]) { correlationResults[j][1] = parseInt(correlationResults[j][1] * 100); correlationResults[j].push(j); } } } } drawBarChart(); } } } function drawBarChart(){ d3.selectAll("#correlation > *").remove(); ///////////////////////////////////////////////////////////// ///////////////// 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); prevRightClick = true; } //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 = ['#67001f','#b2182b','#d6604d','#f4a582','#fddbc7','#d1e5f0','#92c5de','#4393c3','#2166ac','#053061']; 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; }); 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){ if (points.length) { selectedPoints = []; var findNearestTable = []; for (let m=0; m1; 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 -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(); var coun = 0; for (var i=0; i < selectedPoints.length; i++){ if (selectedPoints[i].starplot == true){ coun = coun + 1; } } if(selectedPoints.length <= 10 && coun > 0){ var FeatureWise = []; for (var j=0; j FeatureWiseSliced[i]){ min[j] = FeatureWiseSliced[i]; } } } var vectors = PCA.getEigenVectors(ArrayContainsDataFeaturesCleared); var PCAResults = PCA.computeAdjustedData(ArrayContainsDataFeaturesCleared,vectors[0]); var PCASelVec = []; PCASelVec = PCAResults.selectedVectors[0]; var len = PCASelVec.length; var indices = new Array(len); for (var i = 0; i < len; ++i) indices[i] = i; indices = indices.sort(function (a, b) { return PCASelVec[a] < PCASelVec[b] ? -1 : PCASelVec[a] > PCASelVec[b] ? 1 : 0; }); //const list = dataFeatures.sort((a,b) => a.index - b.index).map((dataFeatures, index, array) => dataFeatures[Category]) var wrapData = []; var IDS = []; for (var i=0; i { dimensions = window.innerWidth; 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(dimensions/2, dimensions/2).scale(initial_scale); zoom.transform(view, initial_transform); camera.position.set(0, 0, far); } //if(step_counter == max_counter){ setUpZoom(); //} var circle_sprite= new THREE.TextureLoader().load( "./textures/circle-sprite.png" ) 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; let geometry = new THREE.Geometry(); for (var i=0; i { let [mouseX, mouseY] = d3.mouse(view.node()); let mouse_position = [mouseX, mouseY]; checkIntersects(mouse_position); }); function mouseToThree(mouseX, mouseY) { return new THREE.Vector3( mouseX / dimensions * 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(particlesDuplic); if (intersects[0]) { if (ColSizeSelector == "color"){ points = points.sort(function(a, b) { return a.beta - b.beta; }) } else{ points = points.sort(function(a, b) { return a.cost - b.cost; }) } 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().domain(["No Category"]).range(["#C0C0C0"]); } else{ var colorScaleCat = d3.scaleOrdinal().domain(all_labels).range(ColorsCategorical); } geometry.colors = [ new THREE.Color(colorScaleCat(datum[Category])) ]; 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(``); 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().domain(["No Category"]).range(["#C0C0C0"]); } else{ var colorScaleCat = d3.scaleOrdinal().domain(all_labels).range(ColorsCategorical); } $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[Category]; $point_tip.style.background = colorScaleCat(tooltip_state.color); var tooltipComb = []; tooltipComb = "Data set's features: " + "\n"; if (tooltip_dimensions){ for (var i=0; i