1/* Frozen GTEx descriptions and intron identity mapping, for research/region views. */ 2(() => { 3 'use strict'; 4 const instances = new WeakMap(); 5 const names = {eqtl_pip_overview:'eQTL continuous PIP overview',eqtl_absz_fdr_hits:'eQTL abs(Z) n20 FDR hits', 6 sqtl_tissue_overview:'sQTL intron n10 overview',sqtl_adjustment_transitions:'sQTL same-intron transitions',intron_identity:'sQTL intron identity map'}; 7 const node=(tag,text)=>{const n=document.createElement(tag);if(text!=null)n.textContent=String(text);return n;}; 8 function style(){ 9 if(document.getElementById('gtex-description-style'))return; 10 const s=node('style');s.id='gtex-description-style'; 11 s.textContent='.gtex-descriptions{min-width:0;border:1px solid #cbd5e1;border-radius:8px;padding:1rem;margin:1rem 0;background:#fff}.gtex-descriptions h3{font-size:1rem;margin:0 0 .5rem}.gtex-descriptions p,.gtex-descriptions summary{font-size:.82rem;line-height:1.5}.gtex-descriptions .gtex-controls{display:flex;flex-wrap:wrap;gap:.7rem;margin:.7rem 0;font-size:.8rem;align-items:center}.gtex-descriptions label{display:flex;flex-direction:column;gap:.2rem}.gtex-descriptions select{max-width:100%;min-width:100px}.gtex-descriptions .gtex-table{overflow:auto;max-width:100%}.gtex-descriptions table{border-collapse:collapse;font-size:.76rem;white-space:nowrap}.gtex-descriptions th,.gtex-descriptions td{padding:.4rem;border-bottom:1px solid #e2e8f0;text-align:left}.gtex-descriptions pre{font-size:.75rem;white-space:pre-wrap;overflow-wrap:anywhere}'; 12 document.head.append(s); 13 } 14 function mount(target,context={}){ 15 if(!target)return; 16 const old=instances.get(target);if(old)old.destroy(); 17 let dead=false,generation=0;const controllers=new Set(),urls=[]; 18 const args={source:context.source,gene:context.gene||'',kind:context.descriptionKind||'',page:1}; 19 const instance={destroy(){dead=true;generation++;controllers.forEach(c=>c.abort());urls.forEach(u=>URL.revokeObjectURL(u));target.replaceChildren();target.classList.remove('gtex-descriptions');if(instances.get(target)===instance)instances.delete(target);}}; 20 instances.set(target,instance);target.replaceChildren(); 21 if(!['gtex_eqtl','gtex_sqtl'].includes(args.source))return instance; 22 style();target.classList.add('gtex-descriptions'); 23 const heading=node('h3','GTEx: original source descriptions'),status=node('p'),body=node('div');target.append(heading,status,body); 24 const alive=token=>!dead&&generation===token; 25 async function request(extra={}){ 26 const controller=new AbortController();controllers.add(controller); 27 try{ 28 const response=await window.Evo2Request.fetch('/api/research/gtex/descriptions',{method:'POST',headers:{'content-type':'application/json'}, 29 body:JSON.stringify({...args,...extra}),signal:controller.signal}); 30 if(!response.ok){let d;try{d=await response.json();}catch(e){}throw Error(d&&d.error||'GTEx descriptions are unavailable (HTTP '+response.status+').');} 31 return response; 32 }finally{controllers.delete(controller);} 33 } 34 function download(text,extension){ 35 const u=URL.createObjectURL(new Blob([text],{type:extension==='csv'?'text/csv':'application/json'}));urls.push(u); 36 const a=node('a');a.href=u;a.download='evo2_gtex_'+args.kind+'.'+extension;document.body.append(a);a.click();a.remove(); 37 } 38 function selectControl(parent,title,key,values,current,all=true){ 39 const label=node('label',title),select=node('select');select.setAttribute('aria-label',title); 40 if(all){const o=node('option','All original groups');o.value='';select.append(o);} 41 values.forEach(value=>{const o=node('option',key==='kind'?(names[value]||value):value);o.value=value;select.append(o);}); 42 select.value=current||'';select.onchange=()=>{args[key]=select.value;args.page=1;if(key==='kind')for(const field of ['tissue','configuration','analysis','test','threshold'])delete args[field];load();}; 43 label.append(select);parent.append(label); 44 } 45 function render(data,token){ 46 body.replaceChildren();args.kind=data.query.kind; 47 const controls=node('div');controls.className='gtex-controls';
48 const kinds=data.kinds.length?data.kinds:Object.keys(names).filter(k=>args.source==='gtex_eqtl'?k.startsWith('eqtl'):!k.startsWith('eqtl')); 49 selectControl(controls,'Original result type','kind',kinds,args.kind,false); 50 for(const [key,values] of Object.entries(data.facets||{}))selectControl(controls,key,key,values,args[key],true); 51 body.append(controls); 52 const source=data.source||{}; 53 heading.textContent=(source.name||'GTEx')+' · '+(source.release||'release unverified')+' · shared freeze '+(source.freeze||'unverified'); 54 if(!data.file){status.textContent='These original descriptions are not integrated. This does not mean a negative result.';return;} 55 const d=data.definitions||{}; 56 status.textContent=(d.label||names[args.kind])+' · '+data.n_total.toLocaleString('en-US')+' matching original rows.'; 57 if(data.message)body.append(node('p',data.message)); 58 for(const text of [d.unit,d.scope,d.sample_scope,d.coordinate_system])if(text)body.append(node('p',text)); 59 if(args.kind!=='intron_identity'&&args.gene)body.append(node('p','Current gene: '+args.gene+'. The overview keeps its original source denominator; it is not recomputed for this gene or its selected windows.')); 60 if(d.before_stage)body.append(node('p','Before: '+d.before_stage+' / '+d.before_test+'. After: '+d.after_stage+' / '+d.after_test+'. Original pairwise transition counts; missing or untestable results remain distinct.')); 61 body.append(node('p','P: '+d.p_definition+'. FDR: '+d.fdr_family+'.')); 62 const box=node('div');box.className='gtex-table';const table=node('table'),head=node('thead'),tr=node('tr'); 63 ['Source row',...data.file.columns].forEach(c=>tr.append(node('th',c)));head.append(tr);table.append(head); 64 const tbody=node('tbody'); 65 data.results.forEach(r=>{const line=node('tr');line.append(node('td',r.source_row));data.file.columns.forEach(k=>{const v=r.original[k];line.append(node('td',v==null?'Not reported':typeof v==='object'?JSON.stringify(v):v===''?'Empty source cell':v));});tbody.append(line);}); 66 table.append(tbody);box.append(table);body.append(box); 67 const nav=node('div');nav.className='gtex-controls'; 68 for(const [label,step] of [['Previous page',-1],['Next page',1]]){ 69 const b=node('button',label);b.type='button';b.disabled=step<0?data.page===1:data.page*data.page_size>=data.n_total; 70 b.onclick=()=>{if(alive(token)){args.page+=step;load();}};nav.append(b); 71 } 72 nav.append(node('span','Page '+data.page+' · '+data.results.length+' displayed / '+data.n_total+' original rows')); 73 for(const extension of ['json','csv']){ 74 const b=node('button','Export all matching '+extension.toUpperCase());b.type='button';b.disabled=!data.n_total; 75 b.onclick=async()=>{b.disabled=true;try{const response=await request({format:extension});const text=await response.text();if(alive(token))download(text,extension);}catch(e){if(alive(token)&&e.name!=='AbortError')status.textContent=e.message;}finally{if(alive(token))b.disabled=false;}};nav.append(b); 76 } 77 body.append(nav); 78 const details=node('details');details.append(node('summary','Original source, definitions and file provenance')); 79 details.addEventListener('toggle',()=>{if(details.open&&details.children.length===1)details.append(node('pre',JSON.stringify({source:data.source,file:data.file,query:data.query,no_refit:data.no_refit},null,2)));});
79body.append(details); 80 } 81 async function load(){ 82 generation++;controllers.forEach(c=>c.abort());const token=generation;status.textContent='Loading original GTEx recordsâ¦';body.replaceChildren(); 83 try{const response=await request();const data=await response.json();if(alive(token))render(data,token);}catch(e){if(alive(token)&&e.name!=='AbortError')status.textContent=e.message;} 84 } 85 load();return instance; 86 } 87 window.Evo2GTExDescriptions={mount}; 88})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.