From 80c4e2915b2417dff84f58504a7434e753e91ee0 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 00:19:27 -0400 Subject: [PATCH 01/14] - Added responsiveness to heatmap legend - Heatmap itself is still not responsive (a work in progress) - Heatmap no longer extends rightward beyond width of tabs - Updated some aesthetics of heatmap section (font sizes, labels' text) - Updated comments to be more instructive --- main/js/plots/createHeatmap.js | 140 +++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 57 deletions(-) diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index b404612..6b76351 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -13,10 +13,9 @@ * @returns {undefined} */ const createHeatmap = async function (expressionData, clinicalAndMutationData, divObject) { - ///// BUILD SVG OBJECTS ///// /////////////////////////////////// -// SAMPLE TRACK SELECTOR SETUP +// 1) SAMPLE TRACK SELECTOR SETUP /////////////////////////////////// // Create div for clinical feature sample track variable selector as scrolling check box list @@ -28,13 +27,14 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d div_optionsPanels.attr("id", "optionsPanels"); div_optionsPanels.attr("class", "col s3"); div_optionsPanels.style("margin-top", "30px"); - div_optionsPanels.style("margin-left", "20px"); + div_optionsPanels.style("padding-left", "30px"); + // div_optionsPanels.style("margin-left", "20px"); var div_clinSelect = div_optionsPanels.append('div'); div_clinSelect.attr("id", "heatmapPartitionSelector"); div_clinSelect.append('text') .style('font-size', '14px') .style('font-weight', 'bold') - .text('Select sample tracks'); + .text('Select column annotations'); div_clinSelect.append('br') div_clinSelect .append('div') @@ -47,13 +47,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .append('div') .attr('class', 'clin_selector'); let div_selectBody = div_clinSelect.select('.clin_selector'); // body for check vbox list - // var selectedText = div_clinSelect.append('text') // text to update what variables selected - // .style('font-size', '16px'); - // div_clinSelect.append('div') - // .append('button') // button to update heatmap, define update function below - // .attr('type', 'button') - // .attr('class', 'updateHeatmapButton') - // .text('Update heatmap'); + // functions to get check box selection and update text var choices; @@ -96,7 +90,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d }); /////////////////////////////////// -// SORT SELECTOR SETUP +// 2) SORT SELECTOR SETUP /////////////////////////////////// // Create div for sorting options (checkboxes) @@ -126,13 +120,16 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d /////////////////////////////////// -// HEATMAP SETUP +// 3) HEATMAP SETUP /////////////////////////////////// - ///// BUILD SVG OBJECTS ///// - // Set up dimensions for heatmap: - var margin = { top: 80, right: 20, space: 5, bottom: 30, left: 50},//100 }, - frameWidth = 1200, + // Create div for ALL components of the heatmap + var heatmapCol = gridRow.append('div'); + heatmapCol.attr("class", "col s9"); + + // Define dimensions for setting up svgs + var margin = { top: 40, bottom: 30, left: 65, right: 30, space: 5}, + frameWidth = 950, heatWidth = frameWidth - margin.left - margin.right, legendWidth = 50, heatHeight = 300, @@ -141,38 +138,44 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d frameHeight = margin.top + heatHeight + margin.space + dendHeight + margin.bottom; xAxisHeight = frameHeight - 5 yAxisHeight = Math.round(frameHeight / 1.5) - // Create svg object frame for the plots - var heatmapCol = gridRow.append('div'); - heatmapCol.attr("class", "col s8"); - //Place heatmap in right-hand column + + // Create svg for ALL components of the heatmap var svg_frame = heatmapCol.append('svg') - .attr('width', frameWidth) + .attr('width', '100%') .attr('height', frameHeight); - // Add title listing cohorts - // Get unique cohort IDs (for title) - const cohortIDs = d3.map(expressionData, d => d.cohort).keys(); - svg_frame.append("text") - .attr('id', 'heatmapTitle') - .attr("x", margin.left) - .attr("y", margin.top - 25) - .style("font-size", "26px") - .text("Gene Expression Heatmap for " + cohortIDs.join(' and ')); - - svg_frame.append("text") - .attr('id', 'heatmapXAxisLabel') - .style("font-size", "14px") - .attr("transform", `translate(${Math.round(frameWidth / 2)},${xAxisHeight})`) - .text("Patient Samples"); + /////////////////////////////////// + + // Set up x-axis label + // Function to update text position based on SVG width + var updateTextPosition = async function() { + const svgWidth = svg_frame.node().getBoundingClientRect().width; // Get the actual width of the SVG + svg_frame.select('#heatmapXAxisLabel') + .attr("x", svgWidth / 2) // Center text horizontally + .attr("y", frameHeight-5) // Position text vertically + .attr("text-anchor", "middle") // Center text horizontally + .text("Patient Barcode"); + } + // Append text to SVG (only if it doesn't exist yet) + if (svg_frame.select('#heatmapXAxisLabel').empty()) { + svg_frame.append("text") + .attr('id', 'heatmapXAxisLabel') + .style("font-size", "14px") + .attr("text-anchor", "middle") // Center text horizontally + .text("Patient Samples"); + } + // Set up y-axis label svg_frame.append("text") .attr('id', 'heatmapYAxisLabel') .attr("text-anchor", "start") .style("font-size", "14px") .attr("transform", `translate(15,${yAxisHeight}),rotate(-90)`) - .text("Transcripts"); + .text("Gene"); - // Add nested svg for dendrogram + /////////////////////////////////// + + // Add nested svg for dendrogram (is only created on toggle though) var svg_dendrogram = svg_frame .append("svg") .attr("class", "dendrogram") @@ -181,11 +184,11 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .attr("x", margin.left) .attr("y", margin.top); - // Add nested svg for sampletrack + // Add nested svg for sampletrack (is only created on check of boxes though) var svg_sampletrack = svg_frame .append("svg") .attr("class", "sampletrack") - .attr("width", frameWidth) + .attr("width", '100%') .attr("height", margin.space + sampTrackHeight) .attr("y", margin.top + dendHeight + margin.space) .append("g") @@ -195,7 +198,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d var svg_heatmap = svg_frame .append("svg") .attr("class", "heatmap") - .attr("width", frameWidth) + .attr("width", '100%') .attr("height", heatHeight + margin.space + margin.bottom) .attr("y", margin.top + dendHeight + margin.space + sampTrackHeight + margin.space) .append("g") @@ -209,9 +212,13 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") - .style('width', frameWidth + 'px'); + .style('width', '100%'); div_tooltip.html("\xa0\xa0Hover over an element to use tooltip."); + var spacer = heatmapCol + .append("div") + .style("height", "20px") + // Add div for sample track legend var div_sampLegend = heatmapCol .append("div") @@ -220,12 +227,13 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") - .style('width', frameWidth + 'px') + .style('width', heatWidth) .style('height', frameHeight/2); div_sampLegend .append("text") - .style("font-size", "18px") - .text("Clinical Feature Sample Tracks Legend:"); + .style("font-size", "14px") + .style("font-weight", "bold") + .text("Column Annotations Legend"); var svg_sampLegend = div_sampLegend .append("div") .attr('id', 'legend') @@ -233,13 +241,15 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .style('overflow', 'scroll') .append("svg") .attr("class", "sampLegend") - .attr("width", frameWidth) + .attr("width", '100%') .attr("height", sampTrackHeight + 2 * margin.space) .append("g") - .attr("transform", "translate(" + margin.space + "," + margin.space*3 + ")"); - + .attr("transform", "translate(" + margin.space + "," + margin.space*3 + ")"); - ///// DATA PROCESSING ///// + /////////////////////////////////// + // 4) PROCESSING OF HEATMAP VALUES + /////////////////////////////////// + // Set the columns to be the set of TCGA participant barcodes 'barcodes' and the rows to be the set of genes called 'geneID' // Get unique TCGA IDs var unique_ids = d3.map(expressionData, d => d.tcga_participant_barcode).keys(); @@ -297,7 +307,6 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d }; sortGroups(); - ///// Build the Axis and Color Scales Below ///// // Build x scale for heatmap let x = d3.scaleBand() @@ -355,9 +364,10 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d }; } + /////////////////////////////////// + // 5) CREATE TOOLTIP + /////////////////////////////////// - - ///// Build the Mouseover Tool Functions ///// // Three functions that change the tooltip when user hover / move / leave a cell let mouseover = function (d) { // Make tooltip appear and color heatmap object black @@ -409,11 +419,13 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d ///// Build the Heatmap, Legend, and Dendrogram Below ///// + // Append the y-axis to the heatmap: svg_heatmap.append("g") .style("font-size", 9.5) .call(d3.axisLeft(y).tickSize(0)) .select(".domain").remove(); + // Build the Legend: svg_heatmap.selectAll() .data(zArr) @@ -424,13 +436,14 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .attr("width", legendWidth / 2) .attr("height", 1 + (heatHeight / zArr.length)) .style("fill", d => colorScale_exp(d)); + // Append the z-axis to the legend: svg_heatmap.append("g") .style("font-size", 10) .attr("transform", "translate(" + heatWidth + ",0)") .call(d3.axisRight().scale(zScale).tickSize(5).ticks(5)); - ///// Update function for creating plot with new order (clustering), new sample tracks + // Update function for creating plot with new order (clustering), new sample tracks function updateHeatmap() { // Build new x scale based on barcodes (in case re-sorted) x = x.domain(barcodes); @@ -547,7 +560,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .append("text") .attr("x", v.x) .attr("alignment-baseline", "hanging") - .style("font-size", "15px") + .style("font-size", "14px") .attr("text-decoration", "underline") .text(v.varname + ":"); if (v.vartype == "categorical" ) { // if categorical, then make boxes for categorical labels @@ -665,12 +678,25 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d yAxisHeight = Math.round(frameHeight / 1.5) xAxisHeight = frameHeight - 5 } + const svgWidth = svg_frame.node().getBoundingClientRect().width; // Get the actual width of the SVG + svg_frame.select("#heatmapYAxisLabel") .attr("transform", `translate(15,${yAxisHeight}),rotate(-90)`) svg_frame.select("#heatmapXAxisLabel") - .attr("transform", `translate(${Math.round(frameWidth / 2)},${xAxisHeight})`) + .attr("x", svgWidth / 2) // Center text horizontally + .attr("y", frameHeight-5) // Position text vertically // apply new frameHeight (adjusting for dendrogram and # sample tracks) svg_frame.attr('height', frameHeight); }; updateHeatmap(); -}; \ No newline at end of file + + updateTextPosition(); + + // Optionally, update text position on window resize + window.addEventListener('resize', updateTextPosition); + +}; + + + + \ No newline at end of file From 69565c4ca94aeb027604712444cbae69e2b0d74f Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 00:51:00 -0400 Subject: [PATCH 02/14] Update violin plots' formatting - Shortened and centered title - Added expression value to y-axis - Updated font sizes --- main/js/plots/createViolinPlot.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/main/js/plots/createViolinPlot.js b/main/js/plots/createViolinPlot.js index 6412c86..196d72a 100644 --- a/main/js/plots/createViolinPlot.js +++ b/main/js/plots/createViolinPlot.js @@ -17,6 +17,7 @@ let tooltipNum = 0; * @returns {undefined} */ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFields) { + //get the num of the div so that the id of everything else matches. Will be used later when creating svg and tooltip let divNum = violinDiv.id[violinDiv.id.length - 1]; @@ -188,8 +189,8 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .attr("x",-(height / 2.0)) .attr("dy", "1em") .style("text-anchor", "middle") - .style("font-size", "12px") - .text("Expression Level"); + .style("font-size", "9px") + .text("Expression Level (log2)"); // Build and Show the X scale. It is a band scale like for a boxplot: each group has an dedicated RANGE on the axis. This range has a length of x.bandwidth var x = d3.scaleBand() @@ -205,10 +206,10 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .call(wrap, x.bandwidth()) .style("font-size", "8px"); - svgObject.append("text") - .attr("transform", "translate(" + width/2 + ", " + (height + margin.top + 50) + ")") - .text("Cohort") - .style("font-size", "12px"); + // svgObject.append("text") + // .attr("transform", "translate(" + width/2 + ", " + (height + margin.top + 50) + ")") + // .text("Cohort") + // .style("font-size", "12px"); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -433,13 +434,13 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .attr("stroke-width", x.bandwidth()/500); } - // Add title to graph + //Add title to graph svgObject.append("text") - .attr("x", 0) + .attr("x", width/2) .attr("y", -25) - .attr("text-anchor", "left") - .style("font-size", "12px") - .text("Gene Expression Violin Plot for "+ curPlot); + .attr("text-anchor", "middle") + .style("font-size", "10px") + .text(curPlot); }; From 68e0a4ad9b139c3628ee3c4f8517fef642f22216 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 14:07:34 -0400 Subject: [PATCH 03/14] Add text to display number of samples in final cohort --- main/js/afterSubmit.js | 35 ++++++++++++++++++++++++++++++- main/js/plots/createHeatmap.js | 38 ++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/main/js/afterSubmit.js b/main/js/afterSubmit.js index 955c5ef..9b463bd 100644 --- a/main/js/afterSubmit.js +++ b/main/js/afterSubmit.js @@ -193,7 +193,40 @@ const buildViolinPlot = function(geneQuery, expressionData) { var violinLoaderDiv = document.getElementById("violinLoaderDiv"); violinLoaderDiv.classList.remove("loader"); - //Setup Materialize Grid + /////////////////////////////////// + // DISPLAY NUMBER OF SAMPLES IN COHORT + /////////////////////////////////// + + var meme = document.getElementById("violinLoaderDiv"); + + var numCohortBarcodes2 = document.createElement("div"); + numCohortBarcodes2.id = "numCohortBarcodes2"; + numCohortBarcodes2.className = "row"; + + meme.appendChild(numCohortBarcodes2); + + let displayNumberSamplesInCohort2 = function () { + let existingPara = document.getElementById("numSamplesInCohortText2"); + if (existingPara) { + existingPara.remove(); + } + // build label: + let para = document.createElement("p"); + // Style the paragraph + para.style.textAlign = 'center'; + para.style.color = '#4db6ac'; + para.style.fontFamily = 'Georgia, "Times New Roman", Times, serif'; + para.id = "numSamplesInCohortText2"; + + para.innerText = "Number of samples in cohort: " + (d3.map(expressionData, d => d.tcga_participant_barcode).keys()).length; + numCohortBarcodes2.appendChild(para); + }; + + displayNumberSamplesInCohort2() + + /////////////////////////////////// + + // Setup Materialize Grid addDivInside("violinGridRow", violinLoaderDiv.id); var gridRow = document.getElementById("violinGridRow"); gridRow.classList.add("row"); diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index 6b76351..1f8bf92 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -14,9 +14,39 @@ */ const createHeatmap = async function (expressionData, clinicalAndMutationData, divObject) { -/////////////////////////////////// -// 1) SAMPLE TRACK SELECTOR SETUP -/////////////////////////////////// + /////////////////////////////////// + // 0) DISPLAY NUMBER OF SAMPLES IN COHORT + /////////////////////////////////// + + var numCohortBarcodes = divObject.append("div"); + numCohortBarcodes.attr("id", "numCohortBarcodes").attr("class", "row"); + + var numCohortBarcodesElement = numCohortBarcodes.node(); + + let displayNumberSamplesInCohort = async function () { + let existingPara = document.getElementById("numSamplesInCohortText"); + if (existingPara) { + existingPara.remove(); + } + // build label: + let string = ""; + let para; + string = (d3.map(expressionData, d => d.tcga_participant_barcode).keys()).length + para = document.createElement("P"); + para.setAttribute( + "style", + 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' + ); + para.setAttribute("id", "numSamplesInCohortText"); + para.innerText = "Number of samples in cohort: " + string; + numCohortBarcodesElement.appendChild(para); + }; + + displayNumberSamplesInCohort() + + /////////////////////////////////// + // 1) SAMPLE TRACK SELECTOR SETUP + /////////////////////////////////// // Create div for clinical feature sample track variable selector as scrolling check box list // Note that we are using the Grid system for Materialize @@ -128,7 +158,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d heatmapCol.attr("class", "col s9"); // Define dimensions for setting up svgs - var margin = { top: 40, bottom: 30, left: 65, right: 30, space: 5}, + var margin = { top: 10, bottom: 30, left: 65, right: 30, space: 5}, frameWidth = 950, heatWidth = frameWidth - margin.left - margin.right, legendWidth = 50, From ebb6c459a9a225432d1546eb8f06a8f7dfd1a223 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 00:19:27 -0400 Subject: [PATCH 04/14] - Added responsiveness to heatmap legend - Heatmap itself is still not responsive (a work in progress) - Heatmap no longer extends rightward beyond width of tabs - Updated some aesthetics of heatmap section (font sizes, labels' text) - Updated comments to be more instructive --- main/js/plots/createHeatmap.js | 140 +++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 57 deletions(-) diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index b404612..6b76351 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -13,10 +13,9 @@ * @returns {undefined} */ const createHeatmap = async function (expressionData, clinicalAndMutationData, divObject) { - ///// BUILD SVG OBJECTS ///// /////////////////////////////////// -// SAMPLE TRACK SELECTOR SETUP +// 1) SAMPLE TRACK SELECTOR SETUP /////////////////////////////////// // Create div for clinical feature sample track variable selector as scrolling check box list @@ -28,13 +27,14 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d div_optionsPanels.attr("id", "optionsPanels"); div_optionsPanels.attr("class", "col s3"); div_optionsPanels.style("margin-top", "30px"); - div_optionsPanels.style("margin-left", "20px"); + div_optionsPanels.style("padding-left", "30px"); + // div_optionsPanels.style("margin-left", "20px"); var div_clinSelect = div_optionsPanels.append('div'); div_clinSelect.attr("id", "heatmapPartitionSelector"); div_clinSelect.append('text') .style('font-size', '14px') .style('font-weight', 'bold') - .text('Select sample tracks'); + .text('Select column annotations'); div_clinSelect.append('br') div_clinSelect .append('div') @@ -47,13 +47,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .append('div') .attr('class', 'clin_selector'); let div_selectBody = div_clinSelect.select('.clin_selector'); // body for check vbox list - // var selectedText = div_clinSelect.append('text') // text to update what variables selected - // .style('font-size', '16px'); - // div_clinSelect.append('div') - // .append('button') // button to update heatmap, define update function below - // .attr('type', 'button') - // .attr('class', 'updateHeatmapButton') - // .text('Update heatmap'); + // functions to get check box selection and update text var choices; @@ -96,7 +90,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d }); /////////////////////////////////// -// SORT SELECTOR SETUP +// 2) SORT SELECTOR SETUP /////////////////////////////////// // Create div for sorting options (checkboxes) @@ -126,13 +120,16 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d /////////////////////////////////// -// HEATMAP SETUP +// 3) HEATMAP SETUP /////////////////////////////////// - ///// BUILD SVG OBJECTS ///// - // Set up dimensions for heatmap: - var margin = { top: 80, right: 20, space: 5, bottom: 30, left: 50},//100 }, - frameWidth = 1200, + // Create div for ALL components of the heatmap + var heatmapCol = gridRow.append('div'); + heatmapCol.attr("class", "col s9"); + + // Define dimensions for setting up svgs + var margin = { top: 40, bottom: 30, left: 65, right: 30, space: 5}, + frameWidth = 950, heatWidth = frameWidth - margin.left - margin.right, legendWidth = 50, heatHeight = 300, @@ -141,38 +138,44 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d frameHeight = margin.top + heatHeight + margin.space + dendHeight + margin.bottom; xAxisHeight = frameHeight - 5 yAxisHeight = Math.round(frameHeight / 1.5) - // Create svg object frame for the plots - var heatmapCol = gridRow.append('div'); - heatmapCol.attr("class", "col s8"); - //Place heatmap in right-hand column + + // Create svg for ALL components of the heatmap var svg_frame = heatmapCol.append('svg') - .attr('width', frameWidth) + .attr('width', '100%') .attr('height', frameHeight); - // Add title listing cohorts - // Get unique cohort IDs (for title) - const cohortIDs = d3.map(expressionData, d => d.cohort).keys(); - svg_frame.append("text") - .attr('id', 'heatmapTitle') - .attr("x", margin.left) - .attr("y", margin.top - 25) - .style("font-size", "26px") - .text("Gene Expression Heatmap for " + cohortIDs.join(' and ')); - - svg_frame.append("text") - .attr('id', 'heatmapXAxisLabel') - .style("font-size", "14px") - .attr("transform", `translate(${Math.round(frameWidth / 2)},${xAxisHeight})`) - .text("Patient Samples"); + /////////////////////////////////// + + // Set up x-axis label + // Function to update text position based on SVG width + var updateTextPosition = async function() { + const svgWidth = svg_frame.node().getBoundingClientRect().width; // Get the actual width of the SVG + svg_frame.select('#heatmapXAxisLabel') + .attr("x", svgWidth / 2) // Center text horizontally + .attr("y", frameHeight-5) // Position text vertically + .attr("text-anchor", "middle") // Center text horizontally + .text("Patient Barcode"); + } + // Append text to SVG (only if it doesn't exist yet) + if (svg_frame.select('#heatmapXAxisLabel').empty()) { + svg_frame.append("text") + .attr('id', 'heatmapXAxisLabel') + .style("font-size", "14px") + .attr("text-anchor", "middle") // Center text horizontally + .text("Patient Samples"); + } + // Set up y-axis label svg_frame.append("text") .attr('id', 'heatmapYAxisLabel') .attr("text-anchor", "start") .style("font-size", "14px") .attr("transform", `translate(15,${yAxisHeight}),rotate(-90)`) - .text("Transcripts"); + .text("Gene"); - // Add nested svg for dendrogram + /////////////////////////////////// + + // Add nested svg for dendrogram (is only created on toggle though) var svg_dendrogram = svg_frame .append("svg") .attr("class", "dendrogram") @@ -181,11 +184,11 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .attr("x", margin.left) .attr("y", margin.top); - // Add nested svg for sampletrack + // Add nested svg for sampletrack (is only created on check of boxes though) var svg_sampletrack = svg_frame .append("svg") .attr("class", "sampletrack") - .attr("width", frameWidth) + .attr("width", '100%') .attr("height", margin.space + sampTrackHeight) .attr("y", margin.top + dendHeight + margin.space) .append("g") @@ -195,7 +198,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d var svg_heatmap = svg_frame .append("svg") .attr("class", "heatmap") - .attr("width", frameWidth) + .attr("width", '100%') .attr("height", heatHeight + margin.space + margin.bottom) .attr("y", margin.top + dendHeight + margin.space + sampTrackHeight + margin.space) .append("g") @@ -209,9 +212,13 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") - .style('width', frameWidth + 'px'); + .style('width', '100%'); div_tooltip.html("\xa0\xa0Hover over an element to use tooltip."); + var spacer = heatmapCol + .append("div") + .style("height", "20px") + // Add div for sample track legend var div_sampLegend = heatmapCol .append("div") @@ -220,12 +227,13 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") - .style('width', frameWidth + 'px') + .style('width', heatWidth) .style('height', frameHeight/2); div_sampLegend .append("text") - .style("font-size", "18px") - .text("Clinical Feature Sample Tracks Legend:"); + .style("font-size", "14px") + .style("font-weight", "bold") + .text("Column Annotations Legend"); var svg_sampLegend = div_sampLegend .append("div") .attr('id', 'legend') @@ -233,13 +241,15 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .style('overflow', 'scroll') .append("svg") .attr("class", "sampLegend") - .attr("width", frameWidth) + .attr("width", '100%') .attr("height", sampTrackHeight + 2 * margin.space) .append("g") - .attr("transform", "translate(" + margin.space + "," + margin.space*3 + ")"); - + .attr("transform", "translate(" + margin.space + "," + margin.space*3 + ")"); - ///// DATA PROCESSING ///// + /////////////////////////////////// + // 4) PROCESSING OF HEATMAP VALUES + /////////////////////////////////// + // Set the columns to be the set of TCGA participant barcodes 'barcodes' and the rows to be the set of genes called 'geneID' // Get unique TCGA IDs var unique_ids = d3.map(expressionData, d => d.tcga_participant_barcode).keys(); @@ -297,7 +307,6 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d }; sortGroups(); - ///// Build the Axis and Color Scales Below ///// // Build x scale for heatmap let x = d3.scaleBand() @@ -355,9 +364,10 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d }; } + /////////////////////////////////// + // 5) CREATE TOOLTIP + /////////////////////////////////// - - ///// Build the Mouseover Tool Functions ///// // Three functions that change the tooltip when user hover / move / leave a cell let mouseover = function (d) { // Make tooltip appear and color heatmap object black @@ -409,11 +419,13 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d ///// Build the Heatmap, Legend, and Dendrogram Below ///// + // Append the y-axis to the heatmap: svg_heatmap.append("g") .style("font-size", 9.5) .call(d3.axisLeft(y).tickSize(0)) .select(".domain").remove(); + // Build the Legend: svg_heatmap.selectAll() .data(zArr) @@ -424,13 +436,14 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .attr("width", legendWidth / 2) .attr("height", 1 + (heatHeight / zArr.length)) .style("fill", d => colorScale_exp(d)); + // Append the z-axis to the legend: svg_heatmap.append("g") .style("font-size", 10) .attr("transform", "translate(" + heatWidth + ",0)") .call(d3.axisRight().scale(zScale).tickSize(5).ticks(5)); - ///// Update function for creating plot with new order (clustering), new sample tracks + // Update function for creating plot with new order (clustering), new sample tracks function updateHeatmap() { // Build new x scale based on barcodes (in case re-sorted) x = x.domain(barcodes); @@ -547,7 +560,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d .append("text") .attr("x", v.x) .attr("alignment-baseline", "hanging") - .style("font-size", "15px") + .style("font-size", "14px") .attr("text-decoration", "underline") .text(v.varname + ":"); if (v.vartype == "categorical" ) { // if categorical, then make boxes for categorical labels @@ -665,12 +678,25 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d yAxisHeight = Math.round(frameHeight / 1.5) xAxisHeight = frameHeight - 5 } + const svgWidth = svg_frame.node().getBoundingClientRect().width; // Get the actual width of the SVG + svg_frame.select("#heatmapYAxisLabel") .attr("transform", `translate(15,${yAxisHeight}),rotate(-90)`) svg_frame.select("#heatmapXAxisLabel") - .attr("transform", `translate(${Math.round(frameWidth / 2)},${xAxisHeight})`) + .attr("x", svgWidth / 2) // Center text horizontally + .attr("y", frameHeight-5) // Position text vertically // apply new frameHeight (adjusting for dendrogram and # sample tracks) svg_frame.attr('height', frameHeight); }; updateHeatmap(); -}; \ No newline at end of file + + updateTextPosition(); + + // Optionally, update text position on window resize + window.addEventListener('resize', updateTextPosition); + +}; + + + + \ No newline at end of file From a2d8843e1c77f7fbc01487a2f5d417f0ad4065be Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 00:51:00 -0400 Subject: [PATCH 05/14] Update violin plots' formatting - Shortened and centered title - Added expression value to y-axis - Updated font sizes --- main/js/plots/createViolinPlot.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/main/js/plots/createViolinPlot.js b/main/js/plots/createViolinPlot.js index 6412c86..196d72a 100644 --- a/main/js/plots/createViolinPlot.js +++ b/main/js/plots/createViolinPlot.js @@ -17,6 +17,7 @@ let tooltipNum = 0; * @returns {undefined} */ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFields) { + //get the num of the div so that the id of everything else matches. Will be used later when creating svg and tooltip let divNum = violinDiv.id[violinDiv.id.length - 1]; @@ -188,8 +189,8 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .attr("x",-(height / 2.0)) .attr("dy", "1em") .style("text-anchor", "middle") - .style("font-size", "12px") - .text("Expression Level"); + .style("font-size", "9px") + .text("Expression Level (log2)"); // Build and Show the X scale. It is a band scale like for a boxplot: each group has an dedicated RANGE on the axis. This range has a length of x.bandwidth var x = d3.scaleBand() @@ -205,10 +206,10 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .call(wrap, x.bandwidth()) .style("font-size", "8px"); - svgObject.append("text") - .attr("transform", "translate(" + width/2 + ", " + (height + margin.top + 50) + ")") - .text("Cohort") - .style("font-size", "12px"); + // svgObject.append("text") + // .attr("transform", "translate(" + width/2 + ", " + (height + margin.top + 50) + ")") + // .text("Cohort") + // .style("font-size", "12px"); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -433,13 +434,13 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .attr("stroke-width", x.bandwidth()/500); } - // Add title to graph + //Add title to graph svgObject.append("text") - .attr("x", 0) + .attr("x", width/2) .attr("y", -25) - .attr("text-anchor", "left") - .style("font-size", "12px") - .text("Gene Expression Violin Plot for "+ curPlot); + .attr("text-anchor", "middle") + .style("font-size", "10px") + .text(curPlot); }; From 958e5d87789951953d4fc2f0c26b08b6a9b8ee73 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 14:07:34 -0400 Subject: [PATCH 06/14] Add text to display number of samples in final cohort --- main/js/afterSubmit.js | 35 ++++++++++++++++++++++++++++++- main/js/plots/createHeatmap.js | 38 ++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/main/js/afterSubmit.js b/main/js/afterSubmit.js index 955c5ef..9b463bd 100644 --- a/main/js/afterSubmit.js +++ b/main/js/afterSubmit.js @@ -193,7 +193,40 @@ const buildViolinPlot = function(geneQuery, expressionData) { var violinLoaderDiv = document.getElementById("violinLoaderDiv"); violinLoaderDiv.classList.remove("loader"); - //Setup Materialize Grid + /////////////////////////////////// + // DISPLAY NUMBER OF SAMPLES IN COHORT + /////////////////////////////////// + + var meme = document.getElementById("violinLoaderDiv"); + + var numCohortBarcodes2 = document.createElement("div"); + numCohortBarcodes2.id = "numCohortBarcodes2"; + numCohortBarcodes2.className = "row"; + + meme.appendChild(numCohortBarcodes2); + + let displayNumberSamplesInCohort2 = function () { + let existingPara = document.getElementById("numSamplesInCohortText2"); + if (existingPara) { + existingPara.remove(); + } + // build label: + let para = document.createElement("p"); + // Style the paragraph + para.style.textAlign = 'center'; + para.style.color = '#4db6ac'; + para.style.fontFamily = 'Georgia, "Times New Roman", Times, serif'; + para.id = "numSamplesInCohortText2"; + + para.innerText = "Number of samples in cohort: " + (d3.map(expressionData, d => d.tcga_participant_barcode).keys()).length; + numCohortBarcodes2.appendChild(para); + }; + + displayNumberSamplesInCohort2() + + /////////////////////////////////// + + // Setup Materialize Grid addDivInside("violinGridRow", violinLoaderDiv.id); var gridRow = document.getElementById("violinGridRow"); gridRow.classList.add("row"); diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index 6b76351..1f8bf92 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -14,9 +14,39 @@ */ const createHeatmap = async function (expressionData, clinicalAndMutationData, divObject) { -/////////////////////////////////// -// 1) SAMPLE TRACK SELECTOR SETUP -/////////////////////////////////// + /////////////////////////////////// + // 0) DISPLAY NUMBER OF SAMPLES IN COHORT + /////////////////////////////////// + + var numCohortBarcodes = divObject.append("div"); + numCohortBarcodes.attr("id", "numCohortBarcodes").attr("class", "row"); + + var numCohortBarcodesElement = numCohortBarcodes.node(); + + let displayNumberSamplesInCohort = async function () { + let existingPara = document.getElementById("numSamplesInCohortText"); + if (existingPara) { + existingPara.remove(); + } + // build label: + let string = ""; + let para; + string = (d3.map(expressionData, d => d.tcga_participant_barcode).keys()).length + para = document.createElement("P"); + para.setAttribute( + "style", + 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' + ); + para.setAttribute("id", "numSamplesInCohortText"); + para.innerText = "Number of samples in cohort: " + string; + numCohortBarcodesElement.appendChild(para); + }; + + displayNumberSamplesInCohort() + + /////////////////////////////////// + // 1) SAMPLE TRACK SELECTOR SETUP + /////////////////////////////////// // Create div for clinical feature sample track variable selector as scrolling check box list // Note that we are using the Grid system for Materialize @@ -128,7 +158,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d heatmapCol.attr("class", "col s9"); // Define dimensions for setting up svgs - var margin = { top: 40, bottom: 30, left: 65, right: 30, space: 5}, + var margin = { top: 10, bottom: 30, left: 65, right: 30, space: 5}, frameWidth = 950, heatWidth = frameWidth - margin.left - margin.right, legendWidth = 50, From 0d00630bcad0a77ff6b8525cf9b857f851c9d731 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 17:01:21 -0400 Subject: [PATCH 07/14] Update min and max values displayed in violin plots - Because, previously, these were getting cut off in the visualization --- main/js/plots/createViolinPlot.js | 57 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/main/js/plots/createViolinPlot.js b/main/js/plots/createViolinPlot.js index 196d72a..0dbdaf4 100644 --- a/main/js/plots/createViolinPlot.js +++ b/main/js/plots/createViolinPlot.js @@ -18,7 +18,7 @@ let tooltipNum = 0; */ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFields) { - //get the num of the div so that the id of everything else matches. Will be used later when creating svg and tooltip + // Get the num of the div so that the id of everything else matches. Will be used later when creating svg and tooltip let divNum = violinDiv.id[violinDiv.id.length - 1]; let clinicalData = ""; @@ -26,7 +26,10 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi clinicalData = await cache.get('rnaSeq', 'clinicalData'); clinicalData = clinicalData.clinicalData; } - //Set up violin curve colors + + // -------------------------------------------------------------- + + // Set up violin curve colors var colors = ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00", "#ffff33","#a65628","#f781bf","#999999"]; function shuffle(array) { @@ -35,6 +38,8 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi shuffle(colors); var violinCurveColors = []; + // -------------------------------------------------------------- + // Set up the figure dimensions: var margin = {top: 10, right: 30, bottom: 10, left: 40}, width = 505 - margin.left - margin.right, @@ -43,28 +48,28 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi // Filter out null values: dataInput = dataInput.filter(patientData => patientData.expression_log2 != null); - //Filter out data that does not belong to curPlot + // Filter out data that does not belong to curPlot dataInput = dataInput.filter(patientData => patientData.gene == curPlot); - //checking for filtered data length is greater than 0 + // Checking for filtered data length is greater than 0 if(dataInput.length <= 0) { return; } var myGroups; - //Add new field to data for purpose of creating keys and populating myGroups + // Add new field to data for purpose of creating keys and populating myGroups if(facetByFields.length > 0) { for(var i = 0; i < dataInput.length; i++) { - //Get matching index in clinicalData for current patient index in dataInput + // Get matching index in clinicalData for current patient index in dataInput var patientIndex = findMatchByTCGABarcode(dataInput[i], clinicalData); if(patientIndex >= 0) { - //Add parentheses for formatting purposes + // Add parentheses for formatting purposes var keyToFacetBy = dataInput[i]['cohort'] + " ("; - //Iterate over the clinical fields to facet by to create keyToFacetBy + // Iterate over the clinical fields to facet by to create keyToFacetBy for(var fieldIndex = 0; fieldIndex < facetByFields.length; fieldIndex++) { - //Append additional JSON field to data for the purpose of creating a key to facet by + // Append additional JSON field to data for the purpose of creating a key to facet by let clinicalField = facetByFields[fieldIndex]; keyToFacetBy += clinicalData[patientIndex][clinicalField] if(fieldIndex < facetByFields.length-1) { @@ -72,12 +77,12 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi } } keyToFacetBy += ")"; - //We now create the 'facetByFieldKey' attribute in the JSON data + // We now create the 'facetByFieldKey' attribute in the JSON data dataInput[i]["facetByFieldKey"] = keyToFacetBy; } else { - //Handle edge case for 'NA' + // Handle edge case for 'NA' dataInput[i]["facetByFieldKey"] = dataInput[i]['cohort'] + " (NA)"; } } @@ -88,7 +93,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi myGroups = d3.map(dataInput, function(d){return d['cohort'];}).keys(); } - //Compute counts for each violin curve group that will be generated + // Compute counts for each violin curve group that will be generated let myGroupCounts = {}; for(let index = 0; index < myGroups.length; index++) { let group = myGroups[index]; @@ -118,7 +123,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi myGroups.sort(); - //Populate violinCurveColors + // Populate violinCurveColors var colorsArrIndex = 0; for(var index = 0; index < myGroups.length; index++) { @@ -137,15 +142,15 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //Spacing between plots + // Spacing between plots let ySpacing = margin.top; - //matching the num of div to num of svg in the div + // Matching the num of div to num of svg in the div let svgID = "svgViolinPlot" + divNum; let svgDivId = `svgViolin${divNum}`; - //create the svg element for the violin plot - //let svgObject = d3.select(violinDiv).append("svg") + // Create the svg element for the violin plot + // let svgObject = d3.select(violinDiv).append("svg") let svgObject = d3.select("#" + svgDivId).append("svg") //.attr("viewBox", `0 -50 1250 475`) // This line makes the svg responsive .attr("viewBox", `0 -35 505 300`) // This line makes the svg responsive @@ -177,12 +182,12 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi // Build and Show the Y scale var y = d3.scaleLinear() - .domain([minExpressionLevel, maxExpressionLevel]) + .domain([minExpressionLevel-2, maxExpressionLevel+2]) .range([height, 0]); svgObject.append("g").call( d3.axisLeft(y)) .style("font-size", "8px"); - //Append y-axis label + // Append y-axis label svgObject.append("text") .attr("transform", "rotate(-90)") .attr("y", -margin.left) @@ -192,7 +197,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .style("font-size", "9px") .text("Expression Level (log2)"); - // Build and Show the X scale. It is a band scale like for a boxplot: each group has an dedicated RANGE on the axis. This range has a length of x.bandwidth + // Build and show the X scale. It is a band scale like for a boxplot: each group has an dedicated RANGE on the axis. This range has a length of x.bandwidth var x = d3.scaleBand() .range([0, width]) .domain(myGroups) @@ -367,7 +372,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .range([0, x.bandwidth()]) .domain([-maxNum ,maxNum]) - //xVals will store the specific x-coordinates to place the box-and-whisker plots for each violin curve + // xVals will store the specific x-coordinates to place the box-and-whisker plots for each violin curve var xVals = []; //colorsIndex is used to cycle through violinCurveColors to assign each violin curve a color var colorsIndex = 0; @@ -401,10 +406,10 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .y(function(d){ return(y(d[0])) } ) .curve(d3.curveCatmullRom)) // This makes the line smoother to give the violin appearance. Try d3.curveStep to see the difference - //Embed box-and-whisker plot inside of each violin curve + // Embed box-and-whisker plot inside of each violin curve for(var index = 0; index < sumstat.length; index++) { - //Adding whisker on the box-and-whisker plot + // Adding whisker on the box-and-whisker plot svgObject.append("line") .attr("x1", xVals[index]) .attr("x2", xVals[index]) @@ -413,7 +418,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .attr("stroke", "black") .attr("stroke-width", x.bandwidth()/500); - //Adding rectangle for each box-and-whisker plot + // Adding rectangle for each box-and-whisker plot var rectWidth = x.bandwidth()/25; svgObject.append("rect") .attr("x", xVals[index] - rectWidth/2) @@ -424,7 +429,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .style("stroke-width", x.bandwidth()/500) .attr("fill", "none"); - //Median line for box-and-whisker plot + // Median line for box-and-whisker plot svgObject.append("line") .attr("x1", xVals[index] - rectWidth/2) .attr("x2", xVals[index] + rectWidth/2) @@ -434,7 +439,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .attr("stroke-width", x.bandwidth()/500); } - //Add title to graph + // Add title to graph svgObject.append("text") .attr("x", width/2) .attr("y", -25) From d86127033d3e7ef75074c52ead3a0c4e5855c3bb Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 17:10:20 -0400 Subject: [PATCH 08/14] Decrease top margin of violin plots section --- main/js/plots/createViolinPlot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/js/plots/createViolinPlot.js b/main/js/plots/createViolinPlot.js index f484371..2386d56 100644 --- a/main/js/plots/createViolinPlot.js +++ b/main/js/plots/createViolinPlot.js @@ -41,7 +41,7 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi // -------------------------------------------------------------- // Set up the figure dimensions: - var margin = {top: 10, right: 30, bottom: 10, left: 40}, + var margin = {top: 0, right: 30, bottom: 10, left: 40}, width = 505 - margin.left - margin.right, height = 200 - margin.top - margin.bottom; From 947031d0d01c93ef486df47a9d48063411a3c4ce Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Wed, 21 Aug 2024 22:31:45 -0400 Subject: [PATCH 09/14] Represent clinical features with numerical values as histograms in data explore section - Previously, many of these clinical features were represented as pie charts, which made the values difficult to read --- main/js/fillSelectBoxes.js | 44 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/main/js/fillSelectBoxes.js b/main/js/fillSelectBoxes.js index fe248b6..101c1ba 100644 --- a/main/js/fillSelectBoxes.js +++ b/main/js/fillSelectBoxes.js @@ -330,17 +330,39 @@ let fillClinicalSelectBox = async function () { let temp = {name: currentFeature, type: "", isSelected: false}; let checkIfClinicalFeatureArrayIsNumeric = async function() { - var numbers = /^[0-9/.]+$/; - var continuousMap = allClinicalData.map(x => { - try { - x[currentFeature].match(numbers) - } catch(error) {} // Ignore error - }); - var nullCount = continuousMap.filter(x => x == null).length; - var totalCount = continuousMap.length; - var percentContinuous = nullCount / totalCount; - let continuousFeaturesArr = ["days_to_death", "cervix_suv_results", "days_to_last_followup", "date_of_initial_pathologic_diagnosis", "number_of_lymph_nodes", "years_to_birth", "karnofsky_performance_score"]; // Array of features that should be considered continuous - if((percentContinuous < 0.75 && (currentFeature != 'vital_status')) || continuousFeaturesArr.includes(currentFeature)) + let continuousFeaturesArr = [ + "age_began_smoking_in_years", + "age_at_diagnosis", + "cervix_suv_results", + "date_of_initial_pathologic_diagnosis", + "days_to_death", + "days_to_last_followup", + "days_to_last_known_alive", + "days_to_psa", + "days_to_submitted_specimen_dx", + "gleason_score", + "height_cm_at_diagnosis", + "initial_pathologic_dx_year", + "karnofsky_performance_score", + "lymph_nodes_examined", + "lymph_nodes_examined_he_count", + "number_of_lymph_nodes", + "number_pack_years_smoked", + "pregnancies_count_ectopic", + "pregnancies_count_live_birth", + "pregnancies_count_stillbirth", + "pregnancies_count_total", + "pregnancy_spontaneous_abortion_count", + "pregnancy_therapeutic_abortion_count", + "psa_value", + "tobacco_smoking_history", + "tobacco_smoking_pack_years_smoked", + "tobacco_smoking_year_stopped", + "weight_kg_at_diagnosis", + "years_to_birth", + "year_of_tobacco_smoking_onset" + ]; // Array of features that should be considered continuous + if(continuousFeaturesArr.includes(currentFeature)) temp.type = "continuous"; else temp.type = "categorical"; From 2c86a1739d9da26b86b59272e32766849dd9c000 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Fri, 23 Aug 2024 23:45:07 -0400 Subject: [PATCH 10/14] Remove 'FPPP' from tumor types dropdown - It does not contain any patient barcodes/expression data --- main/js/fillSelectBoxes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/js/fillSelectBoxes.js b/main/js/fillSelectBoxes.js index 101c1ba..9f877be 100644 --- a/main/js/fillSelectBoxes.js +++ b/main/js/fillSelectBoxes.js @@ -12,7 +12,8 @@ * @returns {undefined} */ const fillCancerTypeSelectBox = async function () { - const cancerTypesQuery = await firebrowse.fetchCohorts(); + const cancerTypesQuery_1 = await firebrowse.fetchCohorts(); + const cancerTypesQuery = cancerTypesQuery_1.reduce((acc, item) => item.cohort !== 'FPPP' ? [...acc, item] : acc, []); cancerTypesQuery.sort(); let selectBox = document.getElementById("cancerTypeMultipleSelection"); for (let i = 0; i < cancerTypesQuery.length; i++) { From f52b1aa1d4d2f01189e933f1a28822f072bb1937 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Sat, 24 Aug 2024 00:55:27 -0400 Subject: [PATCH 11/14] DIsable certain options from clinical feature dropdown - Removed 'date' and 'tool' because they yield the same value across all tumor types - Removed 'tcga_participant_barcode' because it does not align with the purpose of the data explore section at the moment --- main/js/fillSelectBoxes.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main/js/fillSelectBoxes.js b/main/js/fillSelectBoxes.js index 9f877be..67cd4c8 100644 --- a/main/js/fillSelectBoxes.js +++ b/main/js/fillSelectBoxes.js @@ -299,7 +299,11 @@ let fillClinicalSelectBox = async function () { } } } - + + const unwantedKeys = new Set(['date', 'tcga_participant_barcode', 'tool']); + clinicalKeys[0] = clinicalKeys[0].filter(item => !unwantedKeys.has(item)); + + let intersectedFeatures; if(clinicalKeys.length > 1) for(let i = 0; i < clinicalKeys.length - 1; i++) { From 8ea27650e7a0c92144ba97212d14d24dd27d1883 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Thu, 29 Aug 2024 10:50:16 -0400 Subject: [PATCH 12/14] Remove 'date', 'tcga_participant_barcode', and 'tool' from sample track/partition lists for heatmap and violin plots - For same reason as why they were removed from clinical feature select dropdown --- main/js/plots/createHeatmap.js | 4 ++++ main/js/plots/createViolinPlot.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index 1f8bf92..7b776dc 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -111,6 +111,10 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d // populate clinical feature sample track variable selector // get unique clinical features var clin_vars = Object.keys(clinicalAndMutationData[0]).sort(); + + const unwantedKeys = new Set(['date', 'tcga_participant_barcode', 'tool']); + clin_vars = clin_vars.filter(item => !unwantedKeys.has(item)); + clin_vars.forEach(el => renderCB(div_selectBody, el)); // automatically check off selected boxes from clinical query box diff --git a/main/js/plots/createViolinPlot.js b/main/js/plots/createViolinPlot.js index 2386d56..38e7a63 100644 --- a/main/js/plots/createViolinPlot.js +++ b/main/js/plots/createViolinPlot.js @@ -580,6 +580,10 @@ let createViolinPartitionBox = async function(partitionDivId, geneQuery) let var_opts = partitionVars; // make a checkbox for each option + + const unwantedKeys = new Set(['date', 'tcga_participant_barcode', 'tool']); + var_opts = var_opts.filter(item => !unwantedKeys.has(item)); + var_opts.forEach(el => renderCB(div_body,el)) update(); From e204341f6109f0e4eeea78b170a35b08c0b59f70 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Tue, 15 Oct 2024 22:06:58 -0400 Subject: [PATCH 13/14] Increase font size of heatmap sample track labels --- main/js/plots/createHeatmap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index 7b776dc..54c4016 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -581,7 +581,7 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d svg_sampletrack.select('#sampLabels').remove(); // first remove previous labels svg_sampletrack.append("g") .attr('id', 'sampLabels') - .style('font-size', 9.5) + .style('font-size', 10.5) .call(d3.axisRight(y_samp).tickSize(0)) .attr("transform", "translate(" + (heatWidth-legendWidth) + ",0)") .select(".domain").remove(); From 99ea087f13b68438b3463520b1b38869cd486500 Mon Sep 17 00:00:00 2001 From: Erika Nemeth Date: Tue, 22 Oct 2024 18:03:20 -0400 Subject: [PATCH 14/14] Revise based on Kevin's review --- main/js/afterSubmit.js | 4 ++-- main/js/fillSelectBoxes.js | 33 +++++++++++++++---------------- main/js/plots/createHeatmap.js | 7 +++---- main/js/plots/createViolinPlot.js | 5 ----- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/main/js/afterSubmit.js b/main/js/afterSubmit.js index 9b463bd..beeee28 100644 --- a/main/js/afterSubmit.js +++ b/main/js/afterSubmit.js @@ -205,7 +205,7 @@ const buildViolinPlot = function(geneQuery, expressionData) { meme.appendChild(numCohortBarcodes2); - let displayNumberSamplesInCohort2 = function () { + let displayNumberSamplesInCohort = function () { let existingPara = document.getElementById("numSamplesInCohortText2"); if (existingPara) { existingPara.remove(); @@ -222,7 +222,7 @@ const buildViolinPlot = function(geneQuery, expressionData) { numCohortBarcodes2.appendChild(para); }; - displayNumberSamplesInCohort2() + displayNumberSamplesInCohort() /////////////////////////////////// diff --git a/main/js/fillSelectBoxes.js b/main/js/fillSelectBoxes.js index 67cd4c8..68f14be 100644 --- a/main/js/fillSelectBoxes.js +++ b/main/js/fillSelectBoxes.js @@ -12,19 +12,19 @@ * @returns {undefined} */ const fillCancerTypeSelectBox = async function () { - const cancerTypesQuery_1 = await firebrowse.fetchCohorts(); - const cancerTypesQuery = cancerTypesQuery_1.reduce((acc, item) => item.cohort !== 'FPPP' ? [...acc, item] : acc, []); - cancerTypesQuery.sort(); + const cancerTypesQuery = await firebrowse.fetchCohorts(); + const cancerTypesQueryNoFPPP = cancerTypesQuery.reduce((acc, item) => item.cohort !== 'FPPP' ? [...acc, item] : acc, []); + cancerTypesQueryNoFPPP.sort(); let selectBox = document.getElementById("cancerTypeMultipleSelection"); - for (let i = 0; i < cancerTypesQuery.length; i++) { + for (let i = 0; i < cancerTypesQueryNoFPPP.length; i++) { let currentOption = document.createElement("option"); - currentOption.value = cancerTypesQuery[i]["cohort"]; + currentOption.value = cancerTypesQueryNoFPPP[i]["cohort"]; currentOption.text = "(" + - cancerTypesQuery[i]["cohort"] + + cancerTypesQueryNoFPPP[i]["cohort"] + ") " + - cancerTypesQuery[i]["description"]; - currentOption.id = cancerTypesQuery[i]["cohort"]; + cancerTypesQueryNoFPPP[i]["description"]; + currentOption.id = cancerTypesQueryNoFPPP[i]["cohort"]; selectBox.appendChild(currentOption); } let cancerTypeSelectedOptions = localStorage @@ -89,29 +89,29 @@ let displayNumberSamples = async function () { }; orderedCountQuery = orderThings(formatted_numbersOfSamples, myCohort, 'cohort') // build label: - let string = ""; + let numSamplesLabel = ""; let para; for (let i = 0; i < orderedCountQuery.length; i++) { - if (string == "") { - string += orderedCountQuery[i].cohort + ": " + orderedCountQuery[i].mrnaseq; + if (numSamplesLabel == "") { + numSamplesLabel += orderedCountQuery[i].cohort + ": " + orderedCountQuery[i].mrnaseq; para = document.createElement("P"); para.setAttribute( "style", 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' ); para.setAttribute("id", "numSamplesText"); - para.innerText = "Number of samples: " + string; + para.innerText = "Number of samples: " + numSamplesLabel; cancerQuerySelectBox.appendChild(para); } else { document.getElementById("numSamplesText").remove(); - string += ", " + orderedCountQuery[i].cohort + + numSamplesLabel += ", " + orderedCountQuery[i].cohort + ": " + orderedCountQuery[i].mrnaseq; para.setAttribute( "style", 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' ); para.setAttribute("id", "numSamplesText"); - para.innerText = "Number of samples: " + string; + para.innerText = "Number of samples: " + numSamplesLabel; cancerQuerySelectBox.appendChild(para); } } @@ -303,7 +303,6 @@ let fillClinicalSelectBox = async function () { const unwantedKeys = new Set(['date', 'tcga_participant_barcode', 'tool']); clinicalKeys[0] = clinicalKeys[0].filter(item => !unwantedKeys.has(item)); - let intersectedFeatures; if(clinicalKeys.length > 1) for(let i = 0; i < clinicalKeys.length - 1; i++) { @@ -335,7 +334,7 @@ let fillClinicalSelectBox = async function () { let temp = {name: currentFeature, type: "", isSelected: false}; let checkIfClinicalFeatureArrayIsNumeric = async function() { - let continuousFeaturesArr = [ + const CONTINUOUS_FEATURES = [ "age_began_smoking_in_years", "age_at_diagnosis", "cervix_suv_results", @@ -367,7 +366,7 @@ let fillClinicalSelectBox = async function () { "years_to_birth", "year_of_tobacco_smoking_onset" ]; // Array of features that should be considered continuous - if(continuousFeaturesArr.includes(currentFeature)) + if(CONTINUOUS_FEATURES.includes(currentFeature)) temp.type = "continuous"; else temp.type = "categorical"; diff --git a/main/js/plots/createHeatmap.js b/main/js/plots/createHeatmap.js index 54c4016..d82c8d5 100644 --- a/main/js/plots/createHeatmap.js +++ b/main/js/plots/createHeatmap.js @@ -29,16 +29,16 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d existingPara.remove(); } // build label: - let string = ""; + let numSamplesLabel = ""; let para; - string = (d3.map(expressionData, d => d.tcga_participant_barcode).keys()).length + numSamplesLabel = (d3.map(expressionData, d => d.tcga_participant_barcode).keys()).length para = document.createElement("P"); para.setAttribute( "style", 'text-align: center; color: #4db6ac; font-family: Georgia, "Times New Roman", Times, serif' ); para.setAttribute("id", "numSamplesInCohortText"); - para.innerText = "Number of samples in cohort: " + string; + para.innerText = "Number of samples in cohort: " + numSamplesLabel; numCohortBarcodesElement.appendChild(para); }; @@ -58,7 +58,6 @@ const createHeatmap = async function (expressionData, clinicalAndMutationData, d div_optionsPanels.attr("class", "col s3"); div_optionsPanels.style("margin-top", "30px"); div_optionsPanels.style("padding-left", "30px"); - // div_optionsPanels.style("margin-left", "20px"); var div_clinSelect = div_optionsPanels.append('div'); div_clinSelect.attr("id", "heatmapPartitionSelector"); div_clinSelect.append('text') diff --git a/main/js/plots/createViolinPlot.js b/main/js/plots/createViolinPlot.js index 38e7a63..b549635 100644 --- a/main/js/plots/createViolinPlot.js +++ b/main/js/plots/createViolinPlot.js @@ -211,11 +211,6 @@ const createViolinPlot = async function(dataInput, violinDiv, curPlot, facetByFi .call(wrap, x.bandwidth()) .style("font-size", "8px"); - // svgObject.append("text") - // .attr("transform", "translate(" + width/2 + ", " + (height + margin.top + 50) + ")") - // .text("Cohort") - // .style("font-size", "12px"); - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////// Build the Axis and Color Scales Above ///////////////////////////////////////////////////