Last active
May 31, 2021 17:07
-
-
Save DanielKoohmarey/201c89f0e270f080dedb5e5798f2f0a3 to your computer and use it in GitHub Desktop.
Single page offline web app for DNA phenotyping using multinomial logistic models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang=en> | |
<head> | |
<title>284 And Me</title> | |
<style> | |
p { | |
max-width: 800px; | |
margin: auto; | |
} | |
body { | |
margin: 0px; | |
text-align: center; | |
font-family: system-ui; | |
} | |
table { | |
margin: auto; | |
} | |
#drop_zone { | |
border: 5px solid grey; | |
width: 400px; | |
height: 200px; | |
border-radius: 5px; | |
margin: auto; | |
border-style: dashed; | |
margin-bottom: 20px; | |
font-style: italic; | |
color: grey; | |
margin-top: 20px; | |
} | |
#filename, #filetype { | |
font-style: italic; | |
} | |
#error { | |
color: red; | |
display: none; | |
} | |
h1 { | |
margin-top: 0px; | |
box-shadow: #3773cd 0px 5px 15px 0px; | |
background-color: skyblue; | |
color: white; | |
} | |
#iris-prediction, #hair-prediction { | |
font-weight: bold; | |
} | |
/* https://loading.io/css/ */ | |
#page-cover { | |
z-index: 99; | |
position: absolute; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
background-color: skyblue; | |
opacity: .5; | |
display: none; | |
} | |
.lds-grid { | |
display: none; | |
position: absolute; | |
top: 250px; | |
left: 0; | |
right: 0; | |
margin: auto; | |
width: 80px; | |
height: 80px; | |
z-index: 999; | |
} | |
.lds-grid div { | |
position: absolute; | |
width: 16px; | |
height: 16px; | |
border-radius: 50%; | |
background: white; | |
animation: lds-grid 1.2s linear infinite; | |
} | |
.lds-grid div:nth-child(1) { | |
top: 8px; | |
left: 8px; | |
animation-delay: 0s; | |
} | |
.lds-grid div:nth-child(2) { | |
top: 8px; | |
left: 32px; | |
animation-delay: -0.4s; | |
} | |
.lds-grid div:nth-child(3) { | |
top: 8px; | |
left: 56px; | |
animation-delay: -0.8s; | |
} | |
.lds-grid div:nth-child(4) { | |
top: 32px; | |
left: 8px; | |
animation-delay: -0.4s; | |
} | |
.lds-grid div:nth-child(5) { | |
top: 32px; | |
left: 32px; | |
animation-delay: -0.8s; | |
} | |
.lds-grid div:nth-child(6) { | |
top: 32px; | |
left: 56px; | |
animation-delay: -1.2s; | |
} | |
.lds-grid div:nth-child(7) { | |
top: 56px; | |
left: 8px; | |
animation-delay: -0.8s; | |
} | |
.lds-grid div:nth-child(8) { | |
top: 56px; | |
left: 32px; | |
animation-delay: -1.2s; | |
} | |
.lds-grid div:nth-child(9) { | |
top: 56px; | |
left: 56px; | |
animation-delay: -1.6s; | |
} | |
@keyframes lds-grid { | |
0%, 100% { | |
opacity: 1; | |
} | |
50% { | |
opacity: 0.5; | |
} | |
} | |
/* https://codepen.io/dlouise/pen/gLYaMg */ | |
.eye-ball { | |
margin-top: 20px; | |
margin-bottom: 20px; | |
display: inline-block; | |
height: 175px; | |
width: 175px; | |
border-radius: 50%; | |
background: #feffff; | |
position: relative; | |
background: radial-gradient(ellipse at center, #feffff 50%, #aaa 100%); | |
} | |
.iris { | |
height: 50px; | |
width: 50px; | |
border-radius: 50%; | |
width: 50px; | |
height: 50px; | |
padding: 20px; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin: -48px 0 0 -45px; | |
} | |
.iris.saddlebrown { | |
background: radial-gradient(ellipse at center, saddlebrown 48%, #002B04 100%); | |
} | |
.iris.deepskyblue { | |
background: radial-gradient(ellipse at center, deepskyblue 48%, #002B04 100%); | |
} | |
.iris.grey { | |
background: radial-gradient(ellipse at center, white 48%, #002B04 100%); | |
} | |
.pupil { | |
background-color: #000; | |
border-radius: 50%; | |
width: 39px; | |
height: 39px; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin: -21px 0 0 -20px; | |
} | |
.reflection { | |
position: relative; | |
height: 12px; | |
width: 12px; | |
background: #fff; | |
border-radius: 50%; | |
z-index: 1; | |
top: 60%; | |
left: 50%; | |
margin: -28px 0 0 5px; | |
opacity: 0.9; | |
} | |
/* https://codepen.io/DanielaValero/pen/QWbbvEo?editors=1100 */ | |
.skinColor { | |
background-color: skyblue; | |
} | |
.head { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: middle; | |
margin-top: 20px; | |
} | |
.neck { | |
width: 40px; | |
height: 25px; | |
margin-top: -25px; | |
z-index: 1000000; | |
position: relative; | |
} | |
.face { | |
width: 160px; | |
height: 180px; | |
border-radius: 50%; | |
margin-bottom: 15px; | |
box-shadow: inset 2px 30px #3773cd, inset -1px 30px #3773cd; | |
position: relative; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>284 And Me</h1> | |
<p> This client only application uses the <a href="https://pubmed.ncbi.nlm.nih.gov/20457092/">IrisPlex (Walsh et al 2010)</a> and <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3057002/">Model-based prediction of human hair color (Branicki et al 2011)</a> papers to determine which iris and hair colors have the highest probability of occuring given the input SNPs. At a high level, this is done by looking at select SNPs (relative to reference human assembly build 37) associated with certain colors and modeling the color using a multinomial logistic regression model to identify all possible color probabilities. Your data is secure & private as it never leaves your browser.</p> | |
<div id="drop_zone" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);"> | |
<p>Drag & drop exported 23andMe or AncestryDNA data to predict eye & hair color!</p> | |
</div> | |
<div id="error"></div> | |
<div>File loaded: <span id="filename"></span> File type: <span id="filetype"></span></div> | |
<br /> | |
<table> | |
<tr> | |
<td> | |
<div>Iris color prediction: <span id="iris-prediction"></span> (Probability: <span id="iris-probability"></span>%, missing SNPs: <span id="iris-missing"></span>)</div> | |
<div class="eye-ball"> | |
<div class="iris"> | |
<div class="pupil"></div> | |
<div class="reflection"></div> | |
</div> | |
</div> | |
</td> | |
<td> | |
<div style="margin-left:20px;">Hair color prediction: <span id="hair-prediction"></span> (Probability: <span id="hair-probability"></span>%, missing SNPs: <span id="hair-missing"></span>)</div> | |
<div class="head"> | |
<div class="face skinColor"> | |
</div> | |
<div class="neck skinColor"></div> | |
</div> | |
</td> | |
</tr> | |
</table> | |
<div id="page-cover" style="z-index:99"></div> | |
<div class="lds-grid"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div> | |
<script type="text/javascript"> | |
function enableLoading() { | |
document.getElementById('page-cover').style.display = "block"; | |
document.getElementsByClassName('lds-grid')[0].style.display = "block"; | |
} | |
function disableLoading() { | |
document.getElementById('page-cover').style.display = "none"; | |
document.getElementsByClassName('lds-grid')[0].style.display = "none"; | |
} | |
// map between rsid (i.e rs548049170 & genotype (i.e TT) | |
var snpData = {}; | |
const RSID_COL = 0; | |
const CHROM_COL = 1; | |
const POS_COL = 2; | |
const GENO_COL = 3; | |
// https://www.ncbi.nlm.nih.gov/pubmed/20457092 | |
// PS3 Part 2 | |
irisModelTable = { | |
'rsids': | |
{ | |
'rs12913832': | |
{ | |
'minor_allele': 'A', | |
'beta1': -4.81, | |
'beta2': -1.79 | |
}, | |
'rs1800407': | |
{ | |
'minor_allele': 'T', | |
'beta1': 1.40, | |
'beta2': 0.87 | |
}, | |
'rs12896399': | |
{ | |
'minor_allele': 'G', | |
'beta1': -0.58, | |
'beta2': -0.03 | |
}, | |
'rs16891982': | |
{ | |
'minor_allele': 'C', | |
'beta1': -1.30, | |
'beta2': -0.50 | |
}, | |
'rs1393350': | |
{ | |
'minor_allele': 'A', | |
'beta1': 0.47, | |
'beta2': 0.27 | |
}, | |
'rs12203592': | |
{ | |
'minor_allele': 'T', | |
'beta1': 0.70, | |
'beta2': 0.73 | |
}, | |
}, | |
'alpha1': 3.94, | |
'alpha2': .65 | |
} | |
function predictIrisColor(snpMap) { | |
sum_beta1x = 0; | |
sum_beta2x = 0; | |
missing = 0; | |
// iterate over snps and compute beta sum (allele count * beta) | |
for (const [rsid, params] of Object.entries(irisModelTable['rsids'])) { | |
if (!(rsid in snpMap)) { | |
console.log("Warning rsid not available: " + rsid); | |
missing++; | |
continue; | |
} | |
genotype = snpMap[rsid]; | |
// count the occurences of the minor allele in the genotype | |
for (var i = minor_allele_count = 0; | |
i < genotype.length; | |
minor_allele_count += +(params['minor_allele'] === genotype[i++])); | |
sum_beta1x += minor_allele_count * params['beta1']; | |
sum_beta2x += minor_allele_count * params['beta2']; | |
} | |
exp_alpha1 = Math.exp(irisModelTable['alpha1'] + sum_beta1x); | |
exp_alpha2 = Math.exp(irisModelTable['alpha2'] + sum_beta2x); | |
color_prob = {} | |
color_prob['blue'] = exp_alpha1 / (1 + exp_alpha1 + exp_alpha2); | |
color_prob['other'] = exp_alpha2 / (1 + exp_alpha1 + exp_alpha2); | |
color_prob['brown'] = 1 - (color_prob['blue'] + color_prob['other']); | |
color_prob['missing'] = missing; | |
return color_prob; | |
} | |
function testIrisModel() { | |
// preliminary model validation | |
testSnpDataBlue = { | |
'rs1393350': 'AA', | |
'rs12896399': 'TT', | |
'rs1800407': 'CC', | |
'rs12913832': 'GG', | |
'rs16891982': 'GG', | |
'rs12203592': 'CC' | |
}; | |
predicted = predictIrisColor(testSnpDataBlue); | |
delete predicted['missing']; | |
predicted_color = Object.keys(predicted).reduce((a, b) => predicted[a] > predicted[b] ? a : b); | |
console.log("Predicted color: " + predicted_color); | |
if ((predicted['blue'] == 0.968458267134707) && | |
(predicted['other'] == 0.02418434182474713) && | |
(predicted['brown'] == 0.007357391040545891)) { | |
console.log("Model passed blue validation."); | |
} | |
else { | |
console.log(predicted); | |
console.log("Model error! Check code."); | |
} | |
profSnpData = { | |
'rs1393350': 'GA', | |
'rs12896399': 'GG', | |
'rs1800407': 'CC', | |
'rs12913832': 'GG', | |
'rs16891982': 'GG', | |
'rs12203592': 'CC' | |
}; | |
predicted = predictIrisColor(profSnpData); | |
delete predicted['missing']; | |
predicted_color = Object.keys(predicted).reduce((a, b) => predicted[a] > predicted[b] ? a : b); | |
console.log("Predicted color: " + predicted_color); | |
if ((predicted['blue'] == 0.8846395587757137)) { | |
console.log("Model passed prof validation."); | |
} | |
else { | |
console.log(predicted); | |
console.log("Model error! Check code."); | |
} | |
} | |
//testIrisModel(); | |
// https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3057002/ | |
// Table 2 | |
hairModelTable = { | |
'rsids': | |
{ | |
'rs12913832': | |
{ | |
'effect_allele': 'T', | |
'beta1': -1.75, | |
'beta2': 0.10, | |
'beta3': -2.49 | |
}, | |
'rs12203592': | |
{ | |
'effect_allele': 'T', | |
'beta1': -1.29, | |
'beta2': -1.15, | |
'beta3': -1.13 | |
}, | |
'rs1042602': | |
{ | |
'effect_allele': 'A', | |
'beta1': 0.39, | |
'beta2': 0.30, | |
'beta3': 1.20 | |
}, | |
'rs4959270': | |
{ | |
'effect_allele': 'A', | |
'beta1': 0.77, | |
'beta2': 0.85, | |
'beta3': 1.15 | |
}, | |
'rs28777': | |
{ | |
'effect_allele': 'C', | |
'beta1': -1.69, | |
'beta2': -12.89, | |
'beta3': 0.10 | |
}, | |
'rs683': | |
{ | |
'effect_allele': 'C', | |
'beta1': 0.10, | |
'beta2': 0.58, | |
'beta3': -0.02 | |
}, | |
'rs1800407': | |
{ | |
'effect_allele': 'T', | |
'beta1': 0.49, | |
'beta2': -1.14, | |
'beta3': 0.19 | |
}, | |
'rs2402130': | |
{ | |
'effect_allele': 'G', | |
'beta1': -0.48, | |
'beta2': -0.09, | |
'beta3': -0.54 | |
}, | |
'rs12821256': | |
{ | |
'effect_allele': 'C', | |
'beta1': 0.69, | |
'beta2': 0.01, | |
'beta3': 0.87 | |
}, | |
'rs16891982': | |
{ | |
'effect_allele': 'C', | |
'beta1': -0.82, | |
'beta2': -11.78, | |
'beta3': -3.48 | |
}, | |
'rs2378249': | |
{ | |
'effect_allele': 'G', | |
'beta1': -0.18, | |
'beta2': -0.16, | |
'beta3': 0.40 | |
}, | |
}, | |
// https://www.quora.com/How-many-people-have-blond-brown-black-and-red-hair-in-the-United-States | |
// alpha1 = ln(pi_1/pi_4) = ln(probability blond / probability black) in target demographic | |
// Assume all US : red 0.3%, blond 22%, brown 33%, black 20% | |
'alpha1': 1.5,//Math.log(.541/.117), //1.5,//Math.log(.2/.2),//1.5, // blond | |
'alpha2': 1, //Math.log(.084/.117),//1,//Math.log(.11/.2),//1, //1, // brown | |
'alpha3': .25 //Math.log(.249/.117),//.25//Math.log(.03/.2)//.25, //.25 // red | |
} | |
// 4 hair color category prediction | |
// https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3057002/ | |
// Note we are missing 4 of 13 SNPs so will compare accuracy to online Hirisplex | |
function predictHairColor(snpMap) { | |
sum_beta1x = 0; // blond | |
sum_beta2x = 0; // brown | |
sum_beta3x = 0; // red | |
missing = 0; | |
// iterate over snps and compute beta sum (allele count * beta) | |
for (const [rsid, params] of Object.entries(hairModelTable['rsids'])) { | |
if (!(rsid in snpMap)) { | |
console.log("Warning rsid not available: " + rsid); | |
missing++; | |
continue; | |
} | |
genotype = snpMap[rsid]; | |
// count the occurences of the minor allele in the genotype | |
for (var i = minor_allele_count = 0; | |
i < genotype.length; | |
minor_allele_count += +(params['effect_allele'] === genotype[i++])); | |
sum_beta1x += minor_allele_count * params['beta1']; | |
sum_beta2x += minor_allele_count * params['beta2']; | |
sum_beta3x += minor_allele_count * params['beta3']; | |
} | |
exp_alpha1 = Math.exp(hairModelTable['alpha1'] + sum_beta1x); | |
exp_alpha2 = Math.exp(hairModelTable['alpha2'] + sum_beta2x); | |
exp_alpha3 = Math.exp(hairModelTable['alpha3'] + sum_beta3x); | |
color_prob = {}; | |
color_prob['blond'] = exp_alpha1 / (1 + exp_alpha1 + exp_alpha2 + exp_alpha3); | |
color_prob['brown'] = exp_alpha2 / (1 + exp_alpha1 + exp_alpha2 + exp_alpha3); | |
color_prob['red'] = exp_alpha3 / (1 + exp_alpha1 + exp_alpha2 + exp_alpha3); | |
color_prob['black'] = 1 - (color_prob['blond'] + color_prob['brown'] + color_prob['red']); | |
color_prob['missing'] = missing; | |
return color_prob; | |
} | |
function testHairModel() { | |
// feed SNPs manually into https://hirisplex.erasmusmc.nl/ from test 23Me file | |
// to compute expected color probabilities | |
/* blond hair 0.101 | |
brown hair 0.621 | |
red hair 0.005 | |
black hair 0.272 | |
light hair 0.271 | |
dark hair 0.729 */ | |
testSnpData23Me = { | |
'rs12913832': 'AG', | |
'rs12203592': 'CT', | |
'rs1042602': 'AC', | |
'rs4959270': 'AC', | |
'rs28777': 'AC', | |
'rs683': 'AC', | |
'rs1800407': 'CC', | |
//'rs2402130':'GG', | |
'rs12821256': 'TT', | |
'rs16891982': 'CG', | |
//'rs2378249':'CC', | |
}; | |
predicted = predictHairColor(testSnpData23Me); | |
delete predicted['missing']; | |
predicted_color = Object.keys(predicted).reduce((a, b) => predicted[a] > predicted[b] ? a : b); | |
console.log("Predicted color: " + predicted_color + ", expected black"); | |
console.log(predicted); | |
/*if((predicted['brown'] > .6) && | |
(predicted['black'] > .2) && | |
(predicted['red'] > .0001) && | |
(predicted['blond'] > 0.05)) | |
{ | |
console.log("Model passed brown hair validation."); | |
} | |
else | |
{ | |
console.log(predicted) ; | |
console.log("Model error! Check code."); | |
} | |
if((predicted['brown'] > predicted['black']) && | |
(predicted['black'] > predicted['blond']) && | |
(predicted['blond'] > predicted['red'])) | |
{ | |
console.log("Model passed brown hair validation."); | |
} | |
else | |
{ | |
console.log(predicted) ; | |
console.log("Model error! Check code."); | |
}*/ | |
// https://my.pgp-hms.org/public_genetic_data?data_type=23andMe | |
// https://bc638d37d91e9bb38cd39616ecd16963-89.collections.su92l.arvadosapi.com/_/genome_v5_Full_20200711220308.txt | |
// https://hirisplex.erasmusmc.nl/ fed data in from genome_Sharla_Kinman_v4_Full_20170627133322.txt | |
/* | |
blond hair 0.643 | |
brown hair 0.316 | |
red hair 0.011 | |
black hair 0.031 | |
light hair 0.962 | |
dark hair 0.038 */ | |
// https://6250c48ff92bfeede85509aefe8f83d0-103.collections.su92l.arvadosapi.com/_/genome_Sharla_Kinman_v4_Full_20170627133322.txt | |
//genome_Sharla_Kinman_v4_Full_20170627133322.txt | |
testSnpData23Me2 = { | |
'rs12913832': 'GG', | |
'rs12203592': 'CC', | |
'rs1042602': 'AC', | |
'rs4959270': 'CC', | |
'rs28777': 'AA', | |
'rs683': 'AA', | |
'rs1800407': 'CC', | |
'rs2402130': 'AA', | |
'rs12821256': 'CC', | |
'rs16891982': 'GG', | |
'rs2378249': 'AA', | |
}; | |
predicted = predictHairColor(testSnpData23Me2); | |
delete predicted['missing']; | |
predicted_color = Object.keys(predicted).reduce((a, b) => predicted[a] > predicted[b] ? a : b); | |
console.log("Predicted color: " + predicted_color + ", expected blond"); | |
console.log(predicted); | |
/*if((predicted['blond'] > .5) && | |
(predicted['brown'] > .2) && | |
(predicted['red'] < .2) && | |
(predicted['black'] < .2)) | |
{ | |
console.log("Model passed blond hair validation."); | |
} | |
else | |
{ | |
console.log(predicted) ; | |
console.log("Model error! Check code."); | |
}*/ | |
/* | |
blond hair 0.072 | |
brown hair 0.048 | |
red hair 0.879 | |
black hair 0.001 | |
light hair 0.989 | |
dark hair 0.011 | |
*/ | |
// https://54137a447f1c7a4a1f75594fda5d3d7b-102.collections.su92l.arvadosapi.com/_/genome_Jodi_Riggins_v5_Full_20180217093249.txt | |
// genome_Jodi_Riggins_v5_Full_20180217093249.txt | |
testSnpData23Me3 = { | |
'rs12913832': 'GG', | |
'rs12203592': 'CT', | |
'rs1042602': 'AC', | |
'rs4959270': 'AA', | |
'rs28777': 'AA', | |
'rs683': 'AC', | |
'rs1800407': 'CC', | |
//'rs2402130':'AA', | |
'rs12821256': 'TT', | |
'rs16891982': 'GG', | |
//'rs2378249':'AA', | |
}; | |
predicted = predictHairColor(testSnpData23Me3); | |
delete predicted['missing']; | |
predicted_color = Object.keys(predicted).reduce((a, b) => predicted[a] > predicted[b] ? a : b); | |
console.log("Predicted color: " + predicted_color + ", expected red"); | |
console.log(predicted); | |
/*if((predicted['red'] > .7) && | |
(predicted['blond'] < .2) && | |
(predicted['brown'] < .2) && | |
(predicted['black'] < .2)) | |
{ | |
console.log("Model passed blond hair validation."); | |
} | |
else | |
{ | |
console.log(predicted) ; | |
console.log("Model error! Check code."); | |
}*/ | |
} | |
//testHairModel(); | |
function processFile(file) { | |
error = document.getElementById('error').style.display = 'none'; | |
enableLoading(); | |
console.log("processing file: " + file.name); | |
document.getElementById("filename").innerHTML = file.name; | |
const reader = new FileReader(); | |
reader.onload = (event) => { | |
const file = event.target.result; | |
const allLines = file.split(/\r\n|\n/); | |
// validate file | |
valid = false; | |
if (allLines[0].indexOf("# This data file generated by 23andMe") == 0) { | |
// Reading line by line | |
allLines.forEach((line) => { | |
// skip comments | |
if (line[0] == "#") | |
return; | |
cols = line.split("\t"); | |
snpData[cols[RSID_COL]] = cols[GENO_COL]; | |
}); | |
valid = true; | |
document.getElementById("filetype").innerHTML = "23andMe"; | |
} | |
if (allLines[0].indexOf("#AncestryDNA raw data download") == 0) { | |
// Reading line by line | |
allLines.forEach((line) => { | |
// skip comments | |
if (line[0] == "#") | |
return; | |
cols = line.split("\t"); | |
snpData[cols[RSID_COL]] = cols[GENO_COL] + cols[GENO_COL + 1]; | |
}); | |
valid = true; | |
document.getElementById("filetype").innerHTML = "AncestryDNA"; | |
} | |
if (valid) { | |
// compute iris color prediction | |
color_probabilities = predictIrisColor(snpData); | |
console.log(color_probabilities); | |
missing = color_probabilities['missing']; | |
delete color_probabilities['missing']; | |
// find the color with highest probability | |
predicted_color = Object.keys(color_probabilities).reduce((a, b) => | |
color_probabilities[a] > color_probabilities[b] ? a : b); | |
document.getElementById('iris-prediction').innerHTML = predicted_color; | |
document.getElementById('iris-probability').innerHTML = Math.round(color_probabilities[predicted_color] * 100); | |
css_iris_color = { "blue": "deepskyblue", "brown": "saddlebrown", "other": "grey" }; | |
document.getElementsByClassName("iris")[0].classList.add(css_iris_color[predicted_color]); | |
document.getElementById('iris-prediction').style.color = css_iris_color[predicted_color]; | |
document.getElementById('iris-missing').innerHTML = missing; | |
document.getElementById('iris-missing').style.color = missing ? 'red' : 'green'; | |
// compute hair color prediction | |
color_probabilities = predictHairColor(snpData); | |
console.log(color_probabilities); | |
missing = color_probabilities['missing']; | |
delete color_probabilities['missing']; | |
// find the color with highest probability | |
predicted_color = Object.keys(color_probabilities).reduce((a, b) => | |
color_probabilities[a] > color_probabilities[b] ? a : b); | |
document.getElementById('hair-prediction').innerHTML = predicted_color; | |
document.getElementById('hair-probability').innerHTML = Math.round(color_probabilities[predicted_color] * 100); | |
css_iris_color = { "blond": "#efe07b", "brown": "saddlebrown", "red": "#d44848", "black": "black" }; | |
document.getElementById('hair-prediction').style.color = css_iris_color[predicted_color]; | |
document.getElementsByClassName('face')[0].style['box-shadow'] = "inset 2px 30px " + css_iris_color[predicted_color] + | |
", inset -1px 30px " + css_iris_color[predicted_color]; | |
document.getElementById('hair-missing').innerHTML = missing; | |
document.getElementById('hair-missing').style.color = missing ? 'red' : 'green'; | |
} | |
else { | |
error = document.getElementById('error'); | |
error.style.display = 'block'; | |
error.innerHTML = "Unexpected file format! Only 23AndMe & AncestryDNA files are supported."; | |
document.getElementById("filetype").innerHTML = "Unsupported"; | |
} | |
disableLoading(); | |
}; | |
reader.onerror = (event) => { | |
alert(event.target.error.name); | |
disableLoading(); | |
}; | |
reader.readAsText(file); | |
} | |
// Drag & drop functionality from https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop | |
function dragOverHandler(ev) { | |
// Prevent default behavior (Prevent file from being opened) | |
ev.preventDefault(); | |
} | |
function dropHandler(ev) { | |
// Prevent default behavior (Prevent file from being opened) | |
ev.preventDefault(); | |
if (ev.dataTransfer.items) { | |
// Use DataTransferItemList interface to access the file(s) | |
if (ev.dataTransfer.items[0].kind === 'file') { | |
var file = ev.dataTransfer.items[0].getAsFile(); | |
snpData = file; | |
processFile(file); | |
} | |
} else { | |
// Use DataTransfer interface to access the file(s) | |
if (ev.dataTransfer.files.length > 0) { | |
processFile(ev.dataTransfer.files[0]); | |
} | |
} | |
// Pass event to removeDragData for cleanup | |
removeDragData(ev) | |
} | |
function removeDragData(ev) { | |
if (ev.dataTransfer.items) { | |
// Use DataTransferItemList interface to remove the drag data | |
ev.dataTransfer.items.clear(); | |
} else { | |
// Use DataTransfer interface to remove the drag data | |
ev.dataTransfer.clearData(); | |
} | |
}</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment