Fix static explorer rendering: assets/app.js
Browse files- assets/app.js +21 -2
assets/app.js
CHANGED
|
@@ -50,6 +50,23 @@ function csvDownload(filename, rows, columns){
|
|
| 50 |
}
|
| 51 |
function fmt(n){return Number(n).toLocaleString('en-US')}
|
| 52 |
function pFmt(n){n=Number(n);return Number.isFinite(n)?(n<.001?n.toExponential(2):n.toPrecision(3)):'NA'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
function renderOverview(){
|
| 55 |
table('assembly-table', state.data.assemblySummary, Object.keys(state.data.assemblySummary[0]));
|
|
@@ -97,8 +114,10 @@ async function renderDE(){
|
|
| 97 |
const method=$('de-method').value, loaded=await loadDe(), rows=deFiltered(loaded.rows,method), entry=loaded.entry;
|
| 98 |
if(!entry){$('de-summary').textContent='No differential-expression result is available for this selection.';return;}
|
| 99 |
const foldCol=method==='DESeq2'?'log2FoldChange':'logFC', pCol=$('de-y').value==='pvalue'?(method==='DESeq2'?'pvalue':'PValue'):(method==='DESeq2'?'padj':'FDR');
|
| 100 |
-
const
|
| 101 |
-
|
|
|
|
|
|
|
| 102 |
$('de-summary').innerHTML=`<strong>${method}</strong> compared <code>${esc(entry.group_a)}</code> (<strong>n = ${entry.n_group_a}</strong>) with <code>${esc(entry.group_b)}</code> (<strong>n = ${entry.n_group_b}</strong>). Of the <strong>${fmt(entry.n_genes_input)} genes</strong> in the expression matrix, <strong>${fmt(entry.n_genes_tested)}</strong> were retained for statistical testing. Using p-value ≤ ${$('de-pvalue').value}, adjusted p-value / FDR ≤ ${$('de-adjusted').value}, and absolute fold change ≥ ${$('de-fold').value}, <strong>${fmt(rows.length)} DEGs</strong> remain.`;
|
| 103 |
const cols=method==='DESeq2'?['gene_id','baseMean','log2FoldChange','lfcSE','stat','pvalue','padj','direction']:['gene_id','logFC','logCPM','F','PValue','FDR','direction'];
|
| 104 |
table('de-table',rows.slice(0,500),cols);$('de-download').onclick=()=>csvDownload(`${slugSafe($('de-species').value)}_de_filtered.csv`,rows,cols);
|
|
|
|
| 50 |
}
|
| 51 |
function fmt(n){return Number(n).toLocaleString('en-US')}
|
| 52 |
function pFmt(n){n=Number(n);return Number.isFinite(n)?(n<.001?n.toExponential(2):n.toPrecision(3)):'NA'}
|
| 53 |
+
function plotLogP(rows, column){
|
| 54 |
+
const positive=rows.map(r=>Number(r[column])).filter(x=>Number.isFinite(x)&&x>0);
|
| 55 |
+
const observedMax=positive.length?Math.max(...positive.map(x=>-Math.log10(x))):0;
|
| 56 |
+
// Keep underflowed values visible without allowing them to create a 300-high axis.
|
| 57 |
+
const cap=Math.min(Math.max(5, observedMax+1),50);
|
| 58 |
+
return {
|
| 59 |
+
value:n=>{n=Number(n);return Number.isFinite(n)&&n>0?Math.min(-Math.log10(n),cap):cap},
|
| 60 |
+
cap,
|
| 61 |
+
capped:n=>{n=Number(n);return !Number.isFinite(n)||n<=0||-Math.log10(n)>=cap}
|
| 62 |
+
};
|
| 63 |
+
}
|
| 64 |
+
function plotPText(value, label, capped){
|
| 65 |
+
const n=Number(value);
|
| 66 |
+
if(!Number.isFinite(n))return `${label}: unavailable`;
|
| 67 |
+
if(n<=0)return `${label}: 0 (reported as 0; plotted at finite cap)`;
|
| 68 |
+
return `${label}: ${pFmt(n)}${capped?' (finite display cap)':''}`;
|
| 69 |
+
}
|
| 70 |
|
| 71 |
function renderOverview(){
|
| 72 |
table('assembly-table', state.data.assemblySummary, Object.keys(state.data.assemblySummary[0]));
|
|
|
|
| 114 |
const method=$('de-method').value, loaded=await loadDe(), rows=deFiltered(loaded.rows,method), entry=loaded.entry;
|
| 115 |
if(!entry){$('de-summary').textContent='No differential-expression result is available for this selection.';return;}
|
| 116 |
const foldCol=method==='DESeq2'?'log2FoldChange':'logFC', pCol=$('de-y').value==='pvalue'?(method==='DESeq2'?'pvalue':'PValue'):(method==='DESeq2'?'padj':'FDR');
|
| 117 |
+
const yLabel=$('de-y').value==='pvalue'?'p-value':(method==='DESeq2'?'padj':'FDR');
|
| 118 |
+
const yTitle=`-log10(${yLabel})`, logP=plotLogP(rows,pCol);
|
| 119 |
+
const traces=['down','up'].map(direction=>{const x=rows.filter(r=>r.direction===direction);return {type:'scatter',mode:'markers',name:direction,x:x.map(r=>r[foldCol]),y:x.map(r=>logP.value(r[pCol])),customdata:x.map(r=>[r.gene_id,plotPText(r[pCol],yLabel,logP.capped(r[pCol]))]),hovertemplate:'%{customdata[0]}<br>Fold change: %{x:.3f}<br>'+yTitle+': %{y:.2f}<br>%{customdata[1]}<extra></extra>',marker:{size:6}}});
|
| 120 |
+
Plotly.newPlot('de-plot',traces,{template:'plotly_white',paper_bgcolor:'#fff',plot_bgcolor:'#f7fbfd',font:{color:'#123b5d'},height:620,title:`Volcano plot: ${method}`,xaxis:{title:foldCol},yaxis:{title:yTitle,range:[0,Math.max(logP.cap*1.08,1)]},shapes:[{type:'line',x0:0,x1:0,y0:0,y1:1,yref:'paper',line:{dash:'dot',color:'#7890a0'}}]},{responsive:true});
|
| 121 |
$('de-summary').innerHTML=`<strong>${method}</strong> compared <code>${esc(entry.group_a)}</code> (<strong>n = ${entry.n_group_a}</strong>) with <code>${esc(entry.group_b)}</code> (<strong>n = ${entry.n_group_b}</strong>). Of the <strong>${fmt(entry.n_genes_input)} genes</strong> in the expression matrix, <strong>${fmt(entry.n_genes_tested)}</strong> were retained for statistical testing. Using p-value ≤ ${$('de-pvalue').value}, adjusted p-value / FDR ≤ ${$('de-adjusted').value}, and absolute fold change ≥ ${$('de-fold').value}, <strong>${fmt(rows.length)} DEGs</strong> remain.`;
|
| 122 |
const cols=method==='DESeq2'?['gene_id','baseMean','log2FoldChange','lfcSE','stat','pvalue','padj','direction']:['gene_id','logFC','logCPM','F','PValue','FDR','direction'];
|
| 123 |
table('de-table',rows.slice(0,500),cols);$('de-download').onclick=()=>csvDownload(`${slugSafe($('de-species').value)}_de_filtered.csv`,rows,cols);
|