diff --git a/css/style.css b/css/style.css index 64e3f5d..f71fce9 100755 --- a/css/style.css +++ b/css/style.css @@ -135,12 +135,22 @@ cursor: default; z-index: 2; } -/* Styling of the overview canvas */ -#tSNEcanvas { +.dot { + fill: steelblue; +} + +.bounding-rect { + stroke: red; + stroke-width: 1; + fill: transparent; +} + +#overviewRect { border: 1px solid black; width: 11vw; height: 11vw; position: absolute; + z-index: 1; } /* A little styling for knn's bar chart */ @@ -153,7 +163,7 @@ cursor: default; #hider { width: 48.7vw !important; - height: 2.8vw; + height: 3.2vw; background-color: white; position: absolute !important; z-index: 2; @@ -233,12 +243,22 @@ svg#legend3 { overflow: auto; } -/* Styling of the ShepardHeatmap */ #PlotCost { height: 3vw; width: 11vw; margin-left: -5px; text-align: left; + z-index: 1; +} + +#hider2 { + height: 3vw; + width: 11vw; + margin-left: -5px; + text-align: left; + background-color: white; + position: absolute !important; + z-index: 2; } /* ShepardHeatmap Styling for the different rectangles used there */ diff --git a/data/diabetes.csv b/data/diabetes.csv index 3d7914a..d5bfa5b 100644 --- a/data/diabetes.csv +++ b/data/diabetes.csv @@ -1,4 +1,4 @@ -Pregnan.;Glucose;BloodPres.;SkinThic.;Insulin;BMI;Diab.Pedig.Fun.;Age;Outcome* +Pregnan.;Glucose;BloodPres.;SkinThic.;Insulin;BMI;Diab.Pedig.;Age;Outcome* 6;148;72;35;0;33.6;0.627;50;Positive 1;85;66;29;0;26.6;0.351;31;Negative 8;183;64;0;0;23.3;0.672;32;Positive diff --git a/index.html b/index.html index fa06da4..5529c51 100755 --- a/index.html +++ b/index.html @@ -51,7 +51,7 @@
- +
@@ -116,6 +116,7 @@
+
@@ -159,7 +160,7 @@
- + 30
@@ -169,7 +170,7 @@
- + 500
@@ -216,11 +217,14 @@
+
- + +
diff --git a/js/tsne_vis.js b/js/tsne_vis.js index 35fd1e7..8470c90 100755 --- a/js/tsne_vis.js +++ b/js/tsne_vis.js @@ -15,7 +15,7 @@ var ArrayContainsDataFeaturesCleared = []; var ArrayContainsDataFeaturesClearedw var dists; var dists2d; var all_labels; var dist_list = []; var dist_list2d = []; var InitialFormDists = []; var InitialFormDists2D = []; // These are the dimensions for the Overview view and the Main view -var dim = document.getElementById('tSNEcanvas').offsetWidth; var dimensions = document.getElementById('modtSNEcanvas').offsetWidth; +var dim = document.getElementById('overviewRect').offsetWidth-2; 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). @@ -26,6 +26,8 @@ var returnVal = false; var ArrayWithCosts = []; var Iterations = []; +var VisiblePoints = []; + // This variable is for the kNN Bar Chart in order to store the first execution. var inside = 0; @@ -205,6 +207,7 @@ function setContinue(){ // This function allows the continuation of the analysis function setReset(){ // Reset only the filters which were applied into the data points. + emptyPCP(); // Clear d3 SVGs d3.selectAll("#correlation > *").remove(); d3.selectAll("#modtSNEcanvas_svg > *").remove(); @@ -356,6 +359,12 @@ function lassoEnable(){ // The main Layer becomes the correlation (barchart) } +function deleteAnnotations(){ + AnnotationsAll = []; + ringNotes = []; + d3.selectAll("#SvgAnnotator > *").remove(); +} + function setAnnotator(){ // Set a new annotation on top of the main visualization. vw2 = dimensions; @@ -472,13 +481,17 @@ function MainVisual(){ // data variable is all the columns except strings, undefined values, or "Version" plus beta and cost values." // fields variable is all the features (columns) plus beta and cost strings. function init(data, results_all, fields) { - + ArrayWithCosts = []; + Iterations = []; + VisiblePoints = []; + points = []; // Remove all previously drawn SVGs 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("#overviewRect > *").remove(); d3.selectAll("#knnBarChart > *").remove(); d3.selectAll("#costHist > *").remove(); d3.select("#starPlot").selectAll('g').remove(); @@ -488,10 +501,13 @@ function init(data, results_all, fields) { d3.select("#hider").style("z-index", 2); d3.select("#knnBarChart").style("z-index", 1); + d3.select("#hider2").style("z-index", 2); + d3.select("#PlotCost").style("z-index", 1); + // Clear the previous t-SNE overview canvas. - var oldcanvOver = document.getElementById('tSNEcanvas'); + /*var oldcanvOver = document.getElementById('tSNEcanvas'); var contxOver = oldcanvOver.getContext('experimental-webgl'); - contxOver.clear(contxOver.COLOR_BUFFER_BIT); + contxOver.clear(contxOver.COLOR_BUFFER_BIT);*/ // Clear the previously drawn main visualization canvas. scene = new THREE.Scene(); @@ -504,6 +520,7 @@ function init(data, results_all, fields) { // Enable again the lasso interaction. lassoEnable(); + emptyPCP(); // Empty all the Schema Investigation arrays. Arrayx = []; Arrayy = []; @@ -617,7 +634,6 @@ function init(data, results_all, fields) { updateEmbedding(AnalysisResults); }); } - } // Initialize distance matrix @@ -753,6 +769,52 @@ function computeDistances(data, distFunc, transFunc) { } +function OverallCostLineChart(){ + + d3.select("#hider2").style("z-index", -1); + d3.select("#PlotCost").style("z-index", 2); + + var trace1 = { + x: Iterations, + y: ArrayWithCosts, + mode: 'lines', + connectgaps: true, + marker: { + color: "rgb(0,128,0)", + line: { + color: "rgb(0, 0, 0)", + width: 0.5 + } + } + } + + var data = [trace1]; + + var layout = { + showlegend: false, + width: 285, + height: 80, + xaxis:{title: 'Iterations', + titlefont: { + size: 12, + color: 'black' + }}, + yaxis:{title: 'Ov. Cost', + titlefont: { + size: 12, + color: 'black' + }}, + margin: { + l: 40, + r: 15, + b: 26, + t: 5 + }, + }; + + Plotly.newPlot('PlotCost', data, layout,{displayModeBar:false}, {staticPlot: true}); +} + // Function that updates embedding function updateEmbedding(AnalysisResults) { @@ -780,45 +842,7 @@ function updateEmbedding(AnalysisResults) { points[i] = extend(points[i], ArrayContainsDataFeaturesCleared[i]); points[i] = extend(points[i], dataFeatures[i]); } - var trace1 = { - x: Iterations, - y: ArrayWithCosts, - mode: 'lines', - connectgaps: true, - marker: { - color: "rgb(0,128,0)", - line: { - color: "rgb(0, 0, 0)", - width: 1 - } - } - } - - var data = [trace1]; - - var layout = { - showlegend: false, - width: 285, - height: 80, - xaxis:{title: 'Iterations', - titlefont: { - size: 12, - color: 'black' - }}, - yaxis:{title: 'Ov. Cost', - titlefont: { - size: 12, - color: 'black' - }}, - margin: { - l: 40, - r: 15, - b: 26, - t: 5 - }, - }; - - Plotly.newPlot('PlotCost', data, layout,{displayModeBar:false}, {staticPlot: true}); + OverallCostLineChart(); } else{ if (flagAnalysis){ var length = (AnalysisResults.length - dataFeatures.length*2 - 7 - 2); @@ -1051,7 +1075,7 @@ function ShepardHeatMap () { var legend = d3.legendColor() // Legend color and title! .labelFormat(d3.format(",.0f")) - .cells(9) + .cells(7) .title("Number of Points") .scale(colorScale); @@ -1095,17 +1119,10 @@ function resize(canvas) { // This is being used in the WebGL t-SNE for the overv } function OverviewtSNE(points){ // The overview t-SNE function - - var canvas = document.getElementById('tSNEcanvas'); // WebGL & canvas - 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']; // Colors for the labels/categories if there are some! +//Make an SVG Container +d3.selectAll("#overviewRect > *").remove(); +ColorsCategorical = ['#a6cee3','#fb9a99','#b2df8a','#33a02c','#1f78b4','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a']; // Colors for the labels/categories if there are some! if (all_labels[0] == undefined){ var colorScale = d3.scaleOrdinal().domain(["No Category"]).range(["#00000"]); // If no category then grascale. @@ -1113,6 +1130,7 @@ function OverviewtSNE(points){ // The overview t-SNE function } else{ var colorScale = d3.scaleOrdinal().domain(all_labels).range(ColorsCategorical); // We use the color scale here! } + d3.select("#legend2").select("svg").remove(); // Create the legend2 which is for the overview panel. var svg = d3.select("#legend2").append("svg"); @@ -1128,175 +1146,75 @@ function OverviewtSNE(points){ // The overview t-SNE function 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 { + temp = temp + 1; + let d3_transform = d3.event.transform; zoomHandler(d3_transform); + if (temp > 2){ + var frustum = new THREE.Frustum(); + var cameraViewProjectionMatrix = new THREE.Matrix4(); + + // every time the camera or objects change position (or every frame) + + camera.updateMatrixWorld(); // make sure the camera matrix is updated + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + cameraViewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + frustum.setFromMatrix( cameraViewProjectionMatrix ); + + // frustum is now ready to check all the objects you need + VisiblePoints = []; + for (var l = 0; l { - let [mouseX, mouseY] = d3.mouse(view.node()); - let mouse_position = [mouseX, mouseY]; - checkIntersects(mouse_position); - }); +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 / 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"){ +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.beta - b.beta; + return a.cost - b.cost; }) - } 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(); } + 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(); - function sortIntersectsByDistanceToRay(intersects) { - return _.sortBy(intersects, "distanceToRay"); + 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); } - hoverContainer = new THREE.Object3D() - scene.add(hoverContainer); + geometry.colors = [ new THREE.Color(colorScaleCat(datum[Category])) ]; - 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 - ) - ); + 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); } - - geometry.colors = [ new THREE.Color(colorScaleCat(datum[Category])) ]; - - let material = new THREE.PointsMaterial({ - size: 35, - sizeAttenuation: false, - vertexColors: THREE.VertexColors, - map: circle_sprite, - transparent: true - }); + $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 { - 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