mirror of
https://github.com/haniffalab/scRNA-seq_analysis.git
synced 2024-10-23 08:29:24 -07:00
775 lines
31 KiB
HTML
Executable file
775 lines
31 KiB
HTML
Executable file
<!doctype html>
|
|
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
|
|
<title>Interactive heatmap</title>
|
|
<meta name="description" content="Interactive heatmap and dot plot">
|
|
<style type = 'text/css'>
|
|
::-webkit-scrollbar {
|
|
-webkit-appearance: none;
|
|
width: 2px;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
border-radius: 1px;
|
|
background-color: rgba(0,0,0,.5);
|
|
box-shadow: 0 0 1px rgba(255,255,255,.5);
|
|
}
|
|
#cell-menu {
|
|
overflow-x: auto;
|
|
white-space: nowrap;
|
|
margin: .5em;
|
|
padding: .5em;
|
|
}
|
|
.menuContainer {
|
|
border-width: thin;
|
|
border-style: solid;
|
|
margin: 4px;
|
|
padding: 10px;
|
|
}
|
|
#instructionBtn {
|
|
margin-bottom: 1em;
|
|
font-size: 1em;
|
|
background-color: #00bfff;
|
|
border: none;
|
|
color: white;
|
|
padding: 10px 10px;
|
|
text-align: center;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
#end_div {
|
|
margin-bottom: 3em;
|
|
}
|
|
#instructions_div {
|
|
margin:0; padding:0;
|
|
}
|
|
body {
|
|
font-family: Avenir, Arial, sans-serif;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<input id = 'instructionBtn' type = 'button' value = 'Show description and instructions' onclick = 'showHideInstructions(value)' />
|
|
<div id = 'instructionBoard'>
|
|
<div id = 'dataset_div'><b>Data set: </b></div>
|
|
<div><b>Description: </b>An interactive environment for exploring the expression of multiple genes in a data set using dot plots and heatmaps.</div>
|
|
<div>
|
|
<b>Instructions: </b>
|
|
<div id = 'instructions_div'><ul>
|
|
<li>Switch between cell types on x axis and gene names on x axis using the drop-down menu at "Layout"</li>
|
|
<li>Switch between dot plot and heatmap using the drop down menu at "Plot type"</li>
|
|
<li>The "Cell type selection menu" allows switching on/off of particular cell types. Changes will be reflected in the plots immediately. If there are too many cell types, use to horizontal scrollbar to navigate the list of cell types.</li>
|
|
<li>In the "Cell type selection menu" there is also the possibility to write or copy/paste the list desired cell types</li>
|
|
<li>The "Gene selection menu" contains functionalities for editing the list of genes that are plotted</li>
|
|
<li>The "Add new gene" adds a randomly chosen gene to the list. The plots are redraw to include the additional gene</li>
|
|
<li>Alternatively a list of genes can be pasted in the text box at right of the button mentioned above. The list can have the gene names separated by comma, space, semicolon or tab</li>
|
|
<li>Each gene being plotted has a dedicated field which indicates its name and a "Remove" button</li>
|
|
<li>By changing the gene name in any of this boxes, if the new typed name is a valid gene name then the plots will be update to reflect this change</li>
|
|
<li><b>Finally the user can click-chose any gene or cell type directly on the plot and reorder them by dragging</b></li>
|
|
<li>Plots can be saved as png files by right clicking on plot area and choosing "Save as"</li>
|
|
<li>At the bottom of the page there is a list of genes and a list of cell types used in the plot. These lists can be copy/pasted and saved for easy restoring of the plots in a future session.</li>
|
|
</ul></div>
|
|
</div>
|
|
<div><b>Applications: </b>FACS sorting panel design; data annotation; cell type validation; exploring groups of genes relevant to a function (e.g. cycling genes)</div>
|
|
<div id = 'end_div'><hr/></div>
|
|
</div>
|
|
<div>Layout: <select onchange = 'setLayout(this.value)'>
|
|
<option value = 'xcells'>'Cells on X; Genes on Y'<option>
|
|
<option value = 'xgenes'>'Cells on Y; Genes on X'<option>
|
|
</select>
|
|
Plot type: <select onchange = 'setPlotType(this.value)'>
|
|
<option value = 'spotplot'>Dot plot</option>
|
|
<option value = 'heatplot'>Heatmap</option>
|
|
</select>
|
|
</div>
|
|
<div class = 'menuContainer'>
|
|
<b>Cell type selection menu</b><hr>
|
|
Toggle on/off each cell type individually:
|
|
<div id ='cell-menu'></div>
|
|
Or enter here a list of cell types here and press enter:
|
|
<input type ='text' id = 'cellTypeListReader' size = '20' />
|
|
</div>
|
|
<div class = 'menuContainer'><div><b>Gene selection menu</b></div><hr>
|
|
<input type = 'button' value = 'Add new gene' onclick = 'generateNewGeneButton()'/>
|
|
or enter a list of genes here and press enter: <input type ='text' id = 'geneListReader' size = '20' /><hr>
|
|
<div id = 'gene-menu'></div></div>
|
|
<div><canvas id = 'interactive-canvas' width = '700' height = '300' ></canvas></div>
|
|
<div>
|
|
<div>List of cell types:
|
|
<ul><li id = 'output_div_cells' ></li></ul>
|
|
</div>
|
|
<div>List of genes:
|
|
<ul><li id = 'output_div_genes' ></li></ul>
|
|
</div>
|
|
</div>
|
|
<script type = 'text/javascript'>
|
|
// global parameters
|
|
var canvas = document.getElementById('interactive-canvas'),
|
|
context = canvas.getContext('2d'),
|
|
layout = 'xcells', //other value is xgenes
|
|
plotType = 'spotplot', // other value is heatmap
|
|
selectedCellTypes = [], // a named array. names are cell names, and values are index position on the axis
|
|
selectedGenes = [], // a named array. names are gene names, and values are index position on the axis
|
|
row_coordinates = [], // placeholder for row positions
|
|
col_coordinates = [], // placeholder for column positions
|
|
padX = null, // number of pixels at the left of the plot to fit the longest ylabel
|
|
padY = null, // number of pixels at bottom of the plot to fit the longest xlabel
|
|
canvasW = 700,
|
|
canvasH = 400,
|
|
cellMenu = document.getElementById('cell-menu'),
|
|
geneMenu = document.getElementById('gene-menu'),
|
|
geneListReader = document.getElementById('geneListReader'),
|
|
cellTypeListReader = document.getElementById('cellTypeListReader'),
|
|
instructionBtn = document.getElementById('instructionBtn'),
|
|
instructionBoard = document.getElementById('instructionBoard'),
|
|
dataset_div = document.getElementById('dataset_div'),
|
|
output_div_cells = document.getElementById('output_div_cells'),
|
|
output_div_genes = document.getElementById('output_div_genes'),
|
|
partyStarted = false, // set to true when document loaded and the procedure have been put in placed
|
|
tileDim = 30,
|
|
labelsFont = '20px Avenir', // font for wrinting plot labels
|
|
colorStart = '#98f5ff', // color-start for the heatmap color gradient
|
|
colorMiddle = '#D2691E', // color-middle for the heatmap color gradient
|
|
colorStop = '#8b008b', // color-stop for the heatmap color gradoent
|
|
mouseXStart = null, // placeholder for the X position where a mouse click triggered an event
|
|
mouseYStart = null, // placeholder for the Y position where a mouse click triggered an event
|
|
mouseXCurrent = null, // placeholder for the cursor X position during a drag event
|
|
mouseYCurrent = null, // placeholder for the cursor Y position during a drag event
|
|
choosenRowIndex = null, // placeholder to store index of row during dragging
|
|
choosenColIndex = null, // placeholder to store index of column during dragging
|
|
selectedRowPos = null, // placeholder for the position of the selected row. Used for updating its corresponding real row position during dragging
|
|
selectedColPos = null; // placeholder for the position of the selected col. Used for updating its corresponding real col position during dragging
|
|
|
|
// function to convert hex color string to rgb values
|
|
function hexColorStringToRGB(hex_string){
|
|
var r = parseInt('0x' + hex_string.slice(1, 3)),
|
|
g = parseInt('0x' + hex_string.slice(3, 5)),
|
|
b = parseInt('0x' + hex_string.slice(5, 7));
|
|
return {'r':r, 'g':g, "b":b}
|
|
}
|
|
|
|
// conver the heatmap colors from hex strings to rgb values. Values are to be usedfor interpolation when computing the colors for heatmap
|
|
colorStart = hexColorStringToRGB(colorStart);
|
|
colorMiddle = hexColorStringToRGB(colorMiddle);
|
|
colorStop = hexColorStringToRGB(colorStop);
|
|
|
|
function lengthOfNamedArray(namedArray){
|
|
var nal = 0;
|
|
for (key in namedArray){nal++}
|
|
return nal;
|
|
}
|
|
|
|
// data placeholders
|
|
expression_data = [] // an array of arrays (2D matrix) -> for each gene name insert one array with values for all the cell names
|
|
cell_names_order = [] // array containing cell names in the same order as they appear in the above matrix
|
|
// insert data here
|
|
|
|
// put the name of the dataset
|
|
dataset_div.innerHTML += dataset_name;
|
|
|
|
// hide instructions/description
|
|
instructionBoard.style.display = 'none'
|
|
|
|
// compute the number of genes - useful later in the script
|
|
// and get the gene nanes - use during the script for data handling
|
|
var noGenes = 0,
|
|
gene_names = [];
|
|
for (key in expression_data){noGenes += 1; gene_names.push(key)}
|
|
|
|
// populate selectedCellTypes
|
|
cell_names.forEach(function(cell_name, index){selectedCellTypes[cell_name] = index})
|
|
|
|
// function to show/hide description and instructions
|
|
function showHideInstructions(val){
|
|
if (val == 'Show description and instructions'){
|
|
instructionBtn.value = 'Hide description and instructions'
|
|
instructionBoard.style.display = 'block'
|
|
}else{
|
|
instructionBtn.value = 'Show description and instructions'
|
|
instructionBoard.style.display = 'none'
|
|
}
|
|
}
|
|
|
|
// function to allocate position for each row and each column
|
|
// this function also resize the canvas to fit the number of columns and rows
|
|
// when ever there is a re-allocation of columns and row the canvas should be redraw - therefore this function ends with a draw() call
|
|
function allocateRowAndColumnsPositions(){
|
|
// if layout is xcells, the the number of columns is the number of selected cells and the number of rows is the number of selected genes
|
|
var rowLabels = [],
|
|
colLabels = [];
|
|
if (layout == 'xcells'){
|
|
var ncols = lengthOfNamedArray(selectedCellTypes),
|
|
nrows = lengthOfNamedArray(selectedGenes);
|
|
for (key in selectedCellTypes){colLabels.push(key)}
|
|
for (key in selectedGenes){rowLabels.push(key)}
|
|
}else { // else, just the other way around
|
|
var nrows = lengthOfNamedArray(selectedCellTypes),
|
|
ncols = lengthOfNamedArray(selectedGenes);
|
|
for (key in selectedCellTypes){rowLabels.push(key)}
|
|
for (key in selectedGenes){colLabels.push(key)}
|
|
}
|
|
// update padX based on row labels
|
|
context.font = labelsFont;
|
|
padX = 5 + rowLabels.map(function(name){return context.measureText(name).width}).reduce(function(num1, num2){return Math.max(num1, num2)})
|
|
// update padY based on column labels
|
|
padY = 5 + 0.69 * colLabels.map(function(name){return context.measureText(name).width}).reduce(function(num1, num2){return Math.max(num1, num2)})
|
|
// update canvas width and height
|
|
canvasW = padX + tileDim * ncols
|
|
canvasH = padY + tileDim * nrows
|
|
canvas.width = canvasW;
|
|
canvas.height = canvasH
|
|
context = canvas.getContext('2d')
|
|
// update the col and row coordinates
|
|
col_coordinates = []
|
|
row_coordinates = []
|
|
for(i = 0; i < ncols; i++){col_coordinates.push(padX + tileDim * i)}
|
|
for(i = 0; i < nrows; i++){row_coordinates.push(tileDim * i)}
|
|
// and finally draw the canvas
|
|
draw()
|
|
}
|
|
|
|
function setLayout(value){
|
|
layout = value
|
|
allocateRowAndColumnsPositions()
|
|
}
|
|
|
|
function setPlotType(value){
|
|
plotType = value;
|
|
draw()
|
|
}
|
|
|
|
function updateSelectedCellTypes(){
|
|
// must handle addition and removal of cell types differently
|
|
var newSelectedCellTypes = [],
|
|
cell_buttons = cellMenu.children;
|
|
for(i = 0; i < cell_buttons.length; i++){
|
|
var spanID = cell_buttons[i];
|
|
if (spanID.children[0].checked){
|
|
newSelectedCellTypes[spanID.children[0].value] = 0
|
|
}
|
|
}
|
|
if (lengthOfNamedArray(selectedCellTypes) > lengthOfNamedArray(newSelectedCellTypes)){ // condition for removal of cell type
|
|
// must get the position of the removed cell name
|
|
// then decrement all position above it by 1
|
|
var disjointPosition = 0;
|
|
for (key in selectedCellTypes){
|
|
if (!(key in newSelectedCellTypes)){
|
|
disjointPosition = selectedCellTypes[key]
|
|
}
|
|
}
|
|
//now update the newCellSelectedCellTypes position
|
|
for(key in newSelectedCellTypes){
|
|
var position = selectedCellTypes[key]
|
|
if (position > disjointPosition){
|
|
newSelectedCellTypes[key] = position - 1
|
|
}else{
|
|
newSelectedCellTypes[key] = position
|
|
}
|
|
}
|
|
selectedCellTypes = newSelectedCellTypes;
|
|
}else if(lengthOfNamedArray(selectedCellTypes) < lengthOfNamedArray(newSelectedCellTypes)){
|
|
// must get the additional cell name
|
|
// then insert the additional cell name at the end of selectedCellTypes and assign it increment of the the highest Position
|
|
var insertedCellType = null;
|
|
for (key in newSelectedCellTypes){
|
|
if (!(key in selectedCellTypes)){insertedCellType = key}
|
|
}
|
|
var highestPos = 0;
|
|
for(key in selectedCellTypes){highestPos = Math.max(highestPos, selectedCellTypes[key])}
|
|
selectedCellTypes[insertedCellType] = highestPos + 1;
|
|
}
|
|
// and the block of instruction sends with a call to allocateRowAndColumnsPositions:
|
|
allocateRowAndColumnsPositions()
|
|
}
|
|
|
|
// this function inserts a random gene on the table.
|
|
// because it changes the workable data, at the end it calls the allocateRowAndColumnsPositions()
|
|
// which in turn call draw()
|
|
function generateNewGeneButton(){
|
|
expressionVariance = 0
|
|
while (expressionVariance < .3 * cell_names.length){
|
|
var index = parseInt(Math.random() * noGenes),
|
|
gene_name = gene_names[index]
|
|
expressionMean = expression_data[gene_name].reduce(function(a, b){return a + b}) / cell_names.length
|
|
diffs = expression_data[gene_name].map(function(a){return Math.abs(a - expressionMean)}),
|
|
expressionVariance = diffs.reduce(function(a, b){return a + b })
|
|
// if gene already present in list, loop some more - otherwise there will be problems
|
|
if (gene_name in selectedGenes){expressionVariance = 0}
|
|
}
|
|
// loop through all current selected genes and get the highest position, increment by 1 and assign position to the latest gene
|
|
highestPos = -1
|
|
for (key in selectedGenes){highestPos = Math.max(highestPos, selectedGenes[key])}
|
|
highestPos += 1
|
|
selectedGenes[gene_name] = highestPos;
|
|
updateGeneButtons();
|
|
if (partyStarted){
|
|
allocateRowAndColumnsPositions()
|
|
}
|
|
}
|
|
|
|
// this function is called whenever a change to the list of selected genes was made, rather than
|
|
// having a separate function for inserting new buttons and a function for removing buttons
|
|
function updateGeneButtons(){
|
|
geneMenu.innerHTML = ''
|
|
var divContainers = []
|
|
for (key in selectedGenes){
|
|
var genePanel = document.createElement('span')
|
|
geneSelector = document.createElement('input'),
|
|
geneRemoval = document.createElement('input');
|
|
geneSelector.type = 'text'
|
|
geneSelector.value = key;
|
|
geneSelector.gene_tag = key;
|
|
geneSelector.onkeypress = function(event){
|
|
if (event.which == 13){
|
|
var enteredValue = event.target.value;
|
|
if (gene_names.indexOf(enteredValue) != - 1){
|
|
if (!(enteredValue in selectedGenes)){ // case gene not in selectedGens already
|
|
// update the selectedGenes array
|
|
var newSelectedGenes = []
|
|
for(key in selectedGenes){
|
|
if (key != event.target.gene_tag){
|
|
newSelectedGenes[key] = selectedGenes[key]
|
|
}else{
|
|
newSelectedGenes[enteredValue] = selectedGenes[key]
|
|
}
|
|
}
|
|
// update the gene buttons and the plot
|
|
selectedGenes = newSelectedGenes
|
|
updateGeneButtons()
|
|
draw()
|
|
}else{ // case gene already in selected genes
|
|
// generate a message for the user
|
|
|
|
// restore value
|
|
event.target.value = event.target.gene_tag
|
|
}
|
|
}else{
|
|
// gene names was not found. a message will be created for the user to know
|
|
|
|
// restore value
|
|
event.target.value = event.target.gene_tag
|
|
}
|
|
}
|
|
}
|
|
geneRemoval.type = 'button'
|
|
geneRemoval.value = 'Remove'
|
|
geneRemoval.gene_tag = key
|
|
geneRemoval.onclick = function(event){
|
|
removeGeneButton(event.target.gene_tag)
|
|
}
|
|
genePanel.appendChild(geneSelector)
|
|
genePanel.appendChild(geneRemoval);
|
|
divContainers.push(genePanel)
|
|
}
|
|
for (key in selectedGenes){geneMenu.appendChild(divContainers[selectedGenes[key]])}
|
|
}
|
|
|
|
function removeGeneButton(gene_to_be_removed){
|
|
var disjointPos = selectedGenes[gene_to_be_removed],
|
|
newSelectedGenes = [];
|
|
for (key in selectedGenes){
|
|
if(key != gene_to_be_removed){
|
|
newSelectedGenes[key] = selectedGenes[key]
|
|
}
|
|
}
|
|
for (key in newSelectedGenes){
|
|
if(newSelectedGenes[key] > disjointPos){
|
|
newSelectedGenes[key] -= 1
|
|
}
|
|
}
|
|
selectedGenes = newSelectedGenes;
|
|
updateGeneButtons()
|
|
allocateRowAndColumnsPositions()
|
|
}
|
|
|
|
function draw(){
|
|
// update the output
|
|
output_genes = ''
|
|
for (selectedGene in selectedGenes){
|
|
output_genes = output_genes + selectedGene + ", "
|
|
}
|
|
output_genes = output_genes.substring(0, output_genes.length - 2);
|
|
|
|
output_cells = ''
|
|
for (selectedCellType in selectedCellTypes){
|
|
output_cells = output_cells + selectedCellType + ', '
|
|
}
|
|
output_cells = output_cells.substring(0, output_cells.length - 2);
|
|
|
|
output_div_cells.innerHTML = output_cells
|
|
output_div_genes.innerHTML = output_genes
|
|
|
|
// first clear the canvas
|
|
context.fillStyle = '#ffffff' // should change to white at the end
|
|
context.fillRect(0, 0, canvas.width, canvas.height)
|
|
// set parameters for label writting
|
|
context.textAlign = 'right'
|
|
context.textBaseline = 'middle'
|
|
context.fillStyle = 'black'
|
|
context.font = labelsFont;
|
|
context.save()
|
|
// draw if layout is cells on x axis
|
|
if (layout == 'xcells'){
|
|
// write the xlabels (i.e. cell names)
|
|
for(key in selectedCellTypes){
|
|
var col_position = selectedCellTypes[key];
|
|
col_position = col_coordinates[col_position];
|
|
context.translate(col_position + tileDim / 2, canvasH - padY + 6)
|
|
context.rotate( - .23 * Math.PI)
|
|
context.fillText(key, 0, 0)
|
|
context.resetTransform()
|
|
}
|
|
// write the ylabels (i.e. gene names)
|
|
for (key in selectedGenes){
|
|
var row_position = selectedGenes[key]
|
|
row_position = row_coordinates[row_position]
|
|
context.translate(padX - 3, row_position + tileDim / 2)
|
|
context.fillText(key, 0, -5)
|
|
context.resetTransform()
|
|
}
|
|
}
|
|
// draw if layout is cells on y axis
|
|
else if(layout = 'xgenes'){
|
|
for (key in selectedCellTypes){
|
|
var row_position = selectedCellTypes[key]
|
|
row_position = row_coordinates[row_position]
|
|
context.translate(padX - 3, row_position + tileDim / 2)
|
|
context.fillText(key, 0, -5)
|
|
context.resetTransform()
|
|
}
|
|
for (key in selectedGenes){
|
|
var col_position = selectedGenes[key];
|
|
col_position = col_coordinates[col_position];
|
|
context.translate(col_position + tileDim / 2, canvasH - padY + 6)
|
|
context.rotate( - .23 * Math.PI)
|
|
context.fillText(key, 0, 0)
|
|
context.resetTransform()
|
|
}
|
|
}
|
|
// draw the data viz part
|
|
for (var cell_name in selectedCellTypes){
|
|
var cell_name_pos = selectedCellTypes[cell_name]
|
|
for (var gene_name in selectedGenes){
|
|
var gene_name_pos = selectedGenes[gene_name]
|
|
var expressionVal = getExpressionValue(cell_name, gene_name)
|
|
if (layout == 'xcells'){
|
|
var x_coord = selectedCellTypes[cell_name],
|
|
y_coord = selectedGenes[gene_name];
|
|
}else{
|
|
var y_coord = selectedCellTypes[cell_name],
|
|
x_coord = selectedGenes[gene_name];
|
|
}
|
|
x_coord = col_coordinates[x_coord] + tileDim / 2
|
|
y_coord = row_coordinates[y_coord] + tileDim / 2
|
|
if (plotType == 'spotplot'){
|
|
context.fillStyle = colorCodeValue(expressionVal)
|
|
context.beginPath()
|
|
context.arc(x_coord, y_coord, 5 * Math.sqrt(expressionVal), 0, 2 * Math.PI, false)
|
|
context.fill()
|
|
context.closePath()
|
|
}else{
|
|
context.strokeStyle = 'black'
|
|
context.fillStyle = colorCodeValue(expressionVal)
|
|
context.lineWidth = 0.5;
|
|
context.beginPath()
|
|
context.rect(x_coord - tileDim / 2 + .5, y_coord - tileDim / 2 + .5, tileDim, tileDim)
|
|
context.closePath()
|
|
context.fill()
|
|
context.stroke()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getExpressionValue(cell_name, gene_name){
|
|
cell_name_index = cell_names.indexOf(cell_name)
|
|
return expression_data[gene_name][cell_name_index]
|
|
}
|
|
|
|
function colorCodeValue(val){
|
|
if (val < 4.5){
|
|
var r = colorStart.r * (4.5 - val)/4.5 + colorMiddle.r * (val) / 4.5,
|
|
g = colorStart.g * (4.5 - val)/4.5 + colorMiddle.g * (val) / 4.5,
|
|
b = colorStart.b * (4.5 - val)/4.5 + colorMiddle.b * (val) / 4.5;
|
|
}else{
|
|
var r = colorMiddle.r * (9 - val) / 4.5 + colorStop.r * (val - 4.5) / 4.5,
|
|
g = colorMiddle.g * (9 - val) / 4.5 + colorStop.g * (val - 4.5) / 4.5,
|
|
b = colorMiddle.b * (9 - val) / 4.5 + colorStop.b * (val - 4.5) / 4.5;
|
|
}
|
|
r = parseInt(r); g = parseInt(g); b = parseInt(b);
|
|
return 'rgb(' + r + ', ' + g + ', '+ b + ')';
|
|
}
|
|
|
|
// function that takes an event as input and return x, y values of mouse cursor
|
|
function getEventCoordinates(event){
|
|
var canvasRect = canvas.getBoundingClientRect(),
|
|
X = event.clientX - canvasRect.x,
|
|
Y = event.clientY - canvasRect.y;
|
|
return [X, Y]
|
|
}
|
|
|
|
// function call for row dragging by mouse movement
|
|
function dragRow(event){
|
|
var coordinates = getEventCoordinates(event);
|
|
mouseYCurrent = coordinates[1]
|
|
row_coordinates[choosenRowIndex] = selectedRowPos - mouseYStart + mouseYCurrent;
|
|
draw()
|
|
|
|
}
|
|
|
|
function stopRowDragging(event){
|
|
canvas.removeEventListener('mousemove', dragRow)
|
|
canvas.removeEventListener('mouseup', stopRowDragging)
|
|
dragRow(event)
|
|
if (layout == 'xcells'){
|
|
selectedGenes = sortArraybyAnotherArray(selectedGenes, row_coordinates)
|
|
}else{
|
|
selectedCellTypes = sortArraybyAnotherArray(selectedCellTypes, row_coordinates)
|
|
}
|
|
allocateRowAndColumnsPositions()
|
|
updateGeneButtons()
|
|
}
|
|
|
|
function dragCol(event){
|
|
var coordinates = getEventCoordinates(event);
|
|
mouseXCurrent = coordinates[0]
|
|
col_coordinates[choosenColIndex] = selectedColPos - mouseXStart + mouseXCurrent;
|
|
draw()
|
|
}
|
|
|
|
function stopColDragging(event){
|
|
canvas.removeEventListener('mousemove', dragCol)
|
|
canvas.removeEventListener('mouseup', stopColDragging)
|
|
if (layout == 'xgenes'){
|
|
selectedGenes = sortArraybyAnotherArray(selectedGenes, col_coordinates)
|
|
}else{
|
|
selectedCellTypes = sortArraybyAnotherArray(selectedCellTypes, col_coordinates)
|
|
}
|
|
allocateRowAndColumnsPositions()
|
|
updateGeneButtons()
|
|
}
|
|
|
|
// sorts a named array by a second array of values
|
|
function sortArraybyAnotherArray(namedArr, valArr){
|
|
var names = [],
|
|
newNamedArr = [],
|
|
sortedValues = valArr.slice()
|
|
sortedValues.sort(function(a, b){return a - b})
|
|
for(key in namedArr){names.push(key)}
|
|
for (var i=0; i<valArr.length; i++){
|
|
var nameIndex = valArr.indexOf(sortedValues[i])
|
|
newNamedArr[names[nameIndex]] = i
|
|
}
|
|
return newNamedArr
|
|
}
|
|
|
|
// instructions to be execute upon complete page load
|
|
window.onload = function(){
|
|
// fire procedure for making the cell menu
|
|
cell_names.forEach(function(val,i){
|
|
var cell_span = document.createElement('span'),
|
|
cell_input = document.createElement('input'),
|
|
tag_span = document.createElement('span');
|
|
tag_span.innerHTML = val
|
|
tag_span.style.paddingRight = '.8em'
|
|
cell_input.type = 'checkbox'
|
|
cell_input.checked = true
|
|
cell_input.value = val
|
|
cell_input.onchange = updateSelectedCellTypes
|
|
cell_span.appendChild(cell_input)
|
|
cell_span.appendChild(tag_span)
|
|
cellMenu.appendChild(cell_span)
|
|
})
|
|
|
|
// fire procedure for making gene selection menu
|
|
for (i=0; i < 10; i++){generateNewGeneButton()}
|
|
// everything should be ready to get the interactivity started
|
|
partyStarted = true
|
|
// after making cell and gene menus populate placeholders for genes and cell types
|
|
updateSelectedCellTypes()
|
|
// allocate rows and columns - implicitly draws the canvas
|
|
allocateRowAndColumnsPositions()
|
|
// add event listeners
|
|
canvas.addEventListener('mousedown', function(event){
|
|
var coordinates = getEventCoordinates(event),
|
|
clickX = coordinates[0],
|
|
clickY = coordinates[1];
|
|
// if selection is in rows
|
|
if (clickX < padX && clickY < (canvasH - padY)){
|
|
var rowDists = row_coordinates.map(function(val){return val + tileDim / 2})
|
|
.map(function(val){return Math.abs(clickY - val)}),
|
|
smallestDist = rowDists.reduce(function(a, b){return Math.min(a, b)});
|
|
// store chosen row index and Y position of click event firing
|
|
choosenRowIndex = rowDists.indexOf(smallestDist);
|
|
mouseYStart = clickY;
|
|
selectedRowPos = row_coordinates[choosenRowIndex]
|
|
// add event listener for mouse movement
|
|
canvas.addEventListener('mousemove', dragRow)
|
|
canvas.addEventListener('mouseup', stopRowDragging);
|
|
}else if(clickY > (canvasH - padY)){ // check is selection is in columns
|
|
var trueX = clickX + (clickY - canvasH + padY) * Math.tan(Math.PI / 2 -.23 * Math.PI),
|
|
colDist = col_coordinates.map(function(val){return val + tileDim / 2})
|
|
.map(function(val){return Math.abs(trueX - val)})
|
|
smallestDist = colDist.reduce(function(a, b){return Math.min(a, b)})
|
|
choosenColIndex = colDist.indexOf(smallestDist)
|
|
withinLabel = true; // if click is within label at choosen index
|
|
if(withinLabel){
|
|
mouseXStart = clickX
|
|
selectedColPos = col_coordinates[choosenColIndex]
|
|
canvas.addEventListener('mousemove', dragCol)
|
|
canvas.addEventListener('mouseup', stopColDragging);
|
|
}
|
|
}
|
|
})
|
|
// add event listener to cell type gene list reader
|
|
cellTypeListReader.onkeypress = function(event){
|
|
if (event.which == 13){
|
|
var cellType_list_input = cellTypeListReader.value
|
|
cellTypeListReader.value = '';
|
|
cellType_list_input = cellType_list_input.replace(/, /g, 'splitpoint')
|
|
.replace(/,/g, 'splitpoint')
|
|
.replace(/; /g, 'splitpoint')
|
|
.replace(/;/g, 'splitpoint')
|
|
.replace(/\t /g, 'splitpoint')
|
|
.replace(/\t/g, 'splitpoint')
|
|
.split('splitpoint');
|
|
// remove all the empty strings in cell types lists
|
|
new_cellType_list_input = []
|
|
for(var i=0;i<cellType_list_input.length;i++){
|
|
if(cellType_list_input[i] != ''){
|
|
new_cellType_list_input.push(cellType_list_input[i])
|
|
}
|
|
}
|
|
|
|
cellType_list_input = new_cellType_list_input;
|
|
// check if all gene in the list exist in the data
|
|
var allInList = true,
|
|
cellType_not_found = [];
|
|
for(var i=0;i<cellType_list_input.length;i++){
|
|
if (cell_names.indexOf(cellType_list_input[i]) == -1){
|
|
allInList = false;
|
|
cellType_not_found.push(cellType_list_input[i])
|
|
}
|
|
}
|
|
// check for repeats
|
|
var counter_array = []
|
|
for (var i=0;i<cellType_list_input.length;i++){
|
|
celltype_name = cellType_list_input[i]
|
|
if (celltype_name in counter_array){
|
|
counter_array[celltype_name] += 1
|
|
}else {
|
|
counter_array[celltype_name] = 1
|
|
}
|
|
}
|
|
var duplicates = [],
|
|
duplicatesExist = false;
|
|
for(key in counter_array){if(counter_array[key] > 1){duplicates.push(key); duplicatesExist = true}}
|
|
if (duplicatesExist){
|
|
msg = ''
|
|
for (var i=0; i < duplicates.length; i++){
|
|
msg = msg + duplicates[i] + ', '
|
|
}
|
|
msg = 'There are duplicates: ' + msg
|
|
cellTypeListReader.value = msg;
|
|
return 1;
|
|
}
|
|
// if all are in list and the are not repeats render the plot and make the gene buttons
|
|
if (allInList && cellType_list_input.length > 0){
|
|
selectedCellTypes = []
|
|
for (var i=0;i<cellType_list_input.length;i++){
|
|
selectedCellTypes[cellType_list_input[i]] = i
|
|
}
|
|
allocateRowAndColumnsPositions()
|
|
updateGeneButtons()
|
|
// update the cell types radio buttons
|
|
cell_buttons = cellMenu.children;
|
|
for(var i=0;i<cell_buttons.length;i++){
|
|
var spanID = cell_buttons[i].children[0];
|
|
if(cellType_list_input.indexOf(cellMenu.children[i].children[0].value) >= 0 ){
|
|
spanID.checked = true;
|
|
}else{
|
|
spanID.checked = false;
|
|
}
|
|
}
|
|
}else{
|
|
var msg = '';
|
|
for (i=0;i<cellType_not_found.length;i++){
|
|
msg = msg + cellType_not_found[i] + ', '
|
|
}
|
|
msg = 'Cell types not found: ' + msg;
|
|
cellTypeListReader.value = msg;
|
|
}
|
|
// if nothing was passed:
|
|
if(cellType_list_input.length == 0){
|
|
cellTypeListReader.value = ''
|
|
}
|
|
}
|
|
}
|
|
|
|
// add event listener to the gene list reader text area so that it can take list of genes as input for selected genes
|
|
geneListReader.onkeypress = function(event){
|
|
if (event.which == 13){
|
|
var gene_list_input = geneListReader.value
|
|
geneListReader.value = ''
|
|
gene_list_input = gene_list_input.replace(/ /g, 'splitpoint')
|
|
.replace(/,/g, 'splitpoint')
|
|
.replace(/;/g, 'splitpoint')
|
|
.replace(/\t/g, 'splitpoint')
|
|
.split('splitpoint');
|
|
// remove all the empty strings from the gene list
|
|
new_gene_list_input = []
|
|
for(var i=0;i<gene_list_input.length;i++){if(gene_list_input[i] != ''){new_gene_list_input.push(gene_list_input[i])}}
|
|
gene_list_input = new_gene_list_input;
|
|
// check if all gene in the list exist in the data
|
|
var allInList = true,
|
|
gene_not_found = [];
|
|
for(var i=0;i<gene_list_input.length;i++){
|
|
if (gene_names.indexOf(gene_list_input[i]) == -1){allInList = false; gene_not_found.push(gene_list_input[i])}
|
|
}
|
|
// check for repeats in the gene list
|
|
var counter_array = []
|
|
for (var i = 0; i < gene_list_input.length; i++){
|
|
gene_name = gene_list_input[i]
|
|
if (gene_name in counter_array){counter_array[gene_name] += 1}else{counter_array[gene_name] = 1}
|
|
}
|
|
var duplicates = [],
|
|
duplicatesExist = false;
|
|
for(key in counter_array){if(counter_array[key] > 1){duplicates.push(key); duplicatesExist = true}}
|
|
if (duplicatesExist){
|
|
msg = ''
|
|
for (var i=0; i < duplicates.length; i++){
|
|
msg = msg + duplicates[i] + ', '
|
|
}
|
|
msg = 'There are duplicates: ' + msg
|
|
geneListReader.value = msg;
|
|
return 1;
|
|
}
|
|
// if all are in list and the are not repeats render the plot and make the gene buttons
|
|
if (allInList && gene_list_input.length > 0){
|
|
selectedGenes = []
|
|
for (var i=0;i<gene_list_input.length;i++){
|
|
selectedGenes[gene_list_input[i]] = i
|
|
}
|
|
allocateRowAndColumnsPositions()
|
|
updateGeneButtons()
|
|
}
|
|
else{
|
|
var msg = '';
|
|
for (i=0;i<gene_not_found.length;i++){
|
|
msg = msg + gene_not_found[i] + ', '
|
|
}
|
|
msg = 'Genes not found: ' + msg;
|
|
geneListReader.value = msg;
|
|
}
|
|
if(gene_list_input.length == 0){
|
|
geneListReader.value = ''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|