Spaces:
Running
Running
Present the tree with a wide title and resource links; remove sweep counters and playback button
Browse files- .gitattributes +2 -0
- README.md +13 -11
- app.js +7 -3
- carbon-a-strand-poc.png +2 -2
- carbon-a-strand-source.zip +2 -2
- check_presentation.py +84 -0
- export_still.py +9 -7
- index.html +0 -0
- layers.js +1 -1
- presentation-desktop.png +3 -0
- presentation-mobile.png +3 -0
- presentation-verification.json +11 -0
- provenance.json +6 -0
- sweep.js +15 -28
- template.html +41 -38
- verify_live.py +18 -0
.gitattributes
CHANGED
|
@@ -49,3 +49,5 @@ sweep-complete.png filter=lfs diff=lfs merge=lfs -text
|
|
| 49 |
sweep-detail.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
dial-selection.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
download-detail.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 49 |
sweep-detail.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
dial-selection.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
download-detail.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
presentation-desktop.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
presentation-mobile.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: Carbon-A
|
| 3 |
emoji: 🌿
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: blue
|
|
@@ -12,9 +12,9 @@ pinned: false
|
|
| 12 |
|
| 13 |
An interactive proof of concept: a separate, continuous strand for every real assembly in the reusable taxonomy snapshot. Shared ancestors form bundles; zooming separates the strands. Green and blue distinguish assemblies with and without existing annotation evidence under the selected comparison.
|
| 14 |
|
| 15 |
-
The PoC contains **33,588 unique assemblies across 16,976 taxa**, taken from the existing [GenBank annotation explorer](https://huggingface.co/spaces/HuggingFaceBio/genbank-annotation-explorer). The full release totals supplied on 8 October 2026 are **48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp**.
|
| 16 |
|
| 17 |
-
Two comparison modes are available:
|
| 18 |
|
| 19 |
- **NCBI annotation records:** blue means a named annotation provider appears in the cached NCBI GenBank assembly summary. Missing records are not proof that an assembly has never been annotated.
|
| 20 |
- **RefSeq comparison corpus:** blue means an exact versioned match between the listed paired RefSeq accession and the project's 2,055-assembly comparison corpus. This corpus is not the whole of RefSeq.
|
|
@@ -27,17 +27,19 @@ The human assembly detail uses actual published GFF3 coordinates, with 23,767 pr
|
|
| 27 |
|
| 28 |
## Locus bars and atlas silhouettes
|
| 29 |
|
| 30 |
-
The outer annulus aligns to the exact same ordered assembly tips as the tree. Published counts are available for **17,828 of the 33,588 displayed assemblies**, summing to **212,905,534 predicted coding loci**; the other 15,760 assemblies have unavailable counts in this metadata inventory. The default sums available locus counts across **5 consecutive assemblies**; 20-, 50- and 200-assembly bins and individual assembly bars are selectable. Bins partition taxonomic display order, and can straddle named taxon boundaries. Bar length uses a linear scale, recalculated for each bin size and labelled above the tree. Hover reveals the count and number of measured assemblies. Clicking a bin switches to individual bars and zooms into its strands.
|
| 31 |
|
| 32 |
Grey marks at the bar bases indicate missing counts. For bins, the grey mark spans the fraction of assemblies without measurements. A bin sums only available values: missing counts are not imputed, treated as measured zero, or scaled up to the full release. Green locus bars count all predicted loci in the covered assemblies; the blue/green colouring of the tree still describes annotation records at assembly level, not whether individual loci are new.
|
| 33 |
|
| 34 |
-
The original **Genome Atlas silhouettes**, without circle backgrounds, sit on their corresponding taxonomic nodes. Clicking an icon focuses the branch. Icons appear progressively with zoom and are hidden when they would overlap; the checkbox hides them all. All original PhyloPic source links and licence/attribution records are retained in the interface under **About the data → Icon credits and licences** and in the source data. The original SVG artwork is preserved; its displayed fill is adapted to each theme with an alpha mask, without backgrounds.
|
| 35 |
|
| 36 |
-
##
|
| 37 |
|
| 38 |
-
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
|
| 42 |
## Layout and implementation
|
| 43 |
|
|
@@ -59,10 +61,10 @@ Assemblies without a recorded gene-file path offer **Check annotation files** in
|
|
| 59 |
|
| 60 |
## Full-release migration status
|
| 61 |
|
| 62 |
-
|
| 63 |
|
| 64 |
## Rebuild
|
| 65 |
|
| 66 |
-
`python
|
| 67 |
|
| 68 |
-
Data provenance and limitations are recorded in `provenance.json`. The original release video and explorer
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Carbon-A — Across the tree of life
|
| 3 |
emoji: 🌿
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: blue
|
|
|
|
| 12 |
|
| 13 |
An interactive proof of concept: a separate, continuous strand for every real assembly in the reusable taxonomy snapshot. Shared ancestors form bundles; zooming separates the strands. Green and blue distinguish assemblies with and without existing annotation evidence under the selected comparison.
|
| 14 |
|
| 15 |
+
The PoC contains **33,588 unique assemblies across 16,976 taxa**, taken from the existing [GenBank annotation explorer](https://huggingface.co/spaces/HuggingFaceBio/genbank-annotation-explorer). The full release totals supplied on 8 October 2026 are **48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp**. These supplied totals are recorded in About the data; the presentation does not display an aggregate counter. The new radial bars use published per-assembly `gene_count` values wherever available; missing counts are explicitly marked.
|
| 16 |
|
| 17 |
+
Two comparison modes are available under **View options**:
|
| 18 |
|
| 19 |
- **NCBI annotation records:** blue means a named annotation provider appears in the cached NCBI GenBank assembly summary. Missing records are not proof that an assembly has never been annotated.
|
| 20 |
- **RefSeq comparison corpus:** blue means an exact versioned match between the listed paired RefSeq accession and the project's 2,055-assembly comparison corpus. This corpus is not the whole of RefSeq.
|
|
|
|
| 27 |
|
| 28 |
## Locus bars and atlas silhouettes
|
| 29 |
|
| 30 |
+
The outer annulus aligns to the exact same ordered assembly tips as the tree. Published counts are available for **17,828 of the 33,588 displayed assemblies**, summing to **212,905,534 predicted coding loci**; the other 15,760 assemblies have unavailable counts in this metadata inventory. The default sums available locus counts across **5 consecutive assemblies**; 20-, 50- and 200-assembly bins and individual assembly bars are selectable in View options. Bins partition taxonomic display order, and can straddle named taxon boundaries. Bar length uses a linear scale, recalculated for each bin size and labelled above the tree. Hover reveals the count and number of measured assemblies. Clicking a bin switches to individual bars and zooms into its strands.
|
| 31 |
|
| 32 |
Grey marks at the bar bases indicate missing counts. For bins, the grey mark spans the fraction of assemblies without measurements. A bin sums only available values: missing counts are not imputed, treated as measured zero, or scaled up to the full release. Green locus bars count all predicted loci in the covered assemblies; the blue/green colouring of the tree still describes annotation records at assembly level, not whether individual loci are new.
|
| 33 |
|
| 34 |
+
The original **Genome Atlas silhouettes**, without circle backgrounds, sit on their corresponding taxonomic nodes. Clicking an icon focuses the branch. Icons appear progressively with zoom and are hidden when they would overlap; the checkbox in View options hides them all. All original PhyloPic source links and licence/attribution records are retained in the interface under **About the data → Icon credits and licences** and in the source data. The original SVG artwork is preserved; its displayed fill is adapted to each theme with an alpha mask, without backgrounds.
|
| 35 |
|
| 36 |
+
## Presentation and video capture
|
| 37 |
|
| 38 |
+
The Space uses a single wide title, a centred full-width tree and five resource buttons: **Model, Database, Explorer, Paper and Blog**. The sidebar, animation button and all cumulative sweep counters have been removed. Search, bar settings, comparison modes and zoom controls are available under **View options**. **About the data** retains the subset limitations and icon credits.
|
| 39 |
|
| 40 |
+
Click the page background, then press **Space** to start or replay a four-second clockwise sweep; Space again or Escape stops it. A thin centre hand follows the selected strand. Playback has no aggregate counters. Only the interaction canvas redraws each frame; the detailed tree stays cached.
|
| 41 |
+
|
| 42 |
+
For browser automation, `POC.startSweep()` and `POC.stopSweep(false)` provide the same controls. On the app URL, `?record=1` hides the small utility controls and suppresses mouse interactions for a clean recording; adding `&animate=1` starts one sweep a second after loading. Resource links and the title remain visible. These parameters apply to the app itself; the normal private Space page remains the entry point for signed-in users.
|
| 43 |
|
| 44 |
## Layout and implementation
|
| 45 |
|
|
|
|
| 61 |
|
| 62 |
## Full-release migration status
|
| 63 |
|
| 64 |
+
This layout change retains the 33,588 real assembly strands and available per-assembly locus counts; it does not claim that the drawing covers the complete release. The newer October 7 prediction index was found in the same bucket and contains 49,829 assemblies, but its coverage still needs reconciliation with the announced 48,167-assembly release. Published and intermediate gene calls can be reused, and a reconstruction test matched 120 published loci exactly. No full-dataset reconstruction is required or launched for this presentation update.
|
| 65 |
|
| 66 |
## Rebuild
|
| 67 |
|
| 68 |
+
`python build.py` builds the self-contained HTML from the template, JavaScript, compact data and embedded release fonts. `python check_presentation.py` checks desktop/mobile layout, resource buttons, search, frozen selection, annotation access, themes and the counter-free sweep. `python export_still.py` exports the current presentation as a 2160 × 2700 PNG. `python verify_live.py` repeats the current checks against the published private Space.
|
| 69 |
|
| 70 |
+
Earlier exploratory checks are retained for reference; their sidebar and counter expectations describe previous revisions. Data provenance and limitations are recorded in `provenance.json`. The original release video and database explorer are separate projects.
|
app.js
CHANGED
|
@@ -88,7 +88,8 @@ function nearest(x,y,radius=7){
|
|
| 88 |
if(d<best){best=d;found=leaves[hitOwner[k/8]];}
|
| 89 |
}return found;
|
| 90 |
}
|
| 91 |
-
function
|
|
|
|
| 92 |
function clearSelection(){selected=pinnedAssembly=hoveredAssembly=null;$('#selection').hidden=true;scheduleInteraction()}
|
| 93 |
function hoverAssembly(a){
|
| 94 |
if(sweep.running||pinnedAssembly)return;
|
|
@@ -105,7 +106,7 @@ function selectAssembly(a,pin=true){if(!a)return;if(pin)pinnedAssembly=a;selecte
|
|
| 105 |
updateDataAccess(a);$('#sel-loci').hidden=a.accession!=='GCA_000001405.29';scheduleInteraction()}
|
| 106 |
function updateLegend(){let n=mode?D.meta.refseq_matches:D.meta.classification_counts['1'];$('#blue-count').textContent=fmt(n);$('#green-count').textContent=fmt(leaves.length-n);$('#blue-label').textContent=mode?'In RefSeq comparison corpus':'GenBank annotation record';$('#green-label').textContent=mode?'Outside that comparison corpus':'No GenBank record listed';$('#scope-note').textContent=mode?'Counts are assemblies, not genes. Blue marks exact paired-accession matches to the project’s 2,055-assembly RefSeq corpus, not all RefSeq annotations. Green does not imply new genes.':'Counts are assemblies, not genes. This view checks GenBank submission metadata; it does not include annotations stored only on paired RefSeq records. Missing metadata is not proof of no prior annotation.';if(selected&&!sweep.running)selectAssembly(selected,false);schedule()}
|
| 107 |
function search(q){q=q.trim().toLowerCase();let out=[];if(q.length>1){out=leaves.filter(a=>a.accession.toLowerCase().includes(q)||a.name.toLowerCase().includes(q)||(nodes.get(a.taxid).common||'').toLowerCase().includes(q)).slice(0,7);}$('#results').replaceChildren(...out.map(a=>{let b=document.createElement('button');b.innerHTML=`<strong>${esc(a.name)}</strong><span>${esc(a.accession)}</span>`;b.onclick=()=>{focusAssembly(a);$('#results').replaceChildren();$('#search').value=''};return b}));if(q.length>1&&!out.length)$('#results').textContent='No match in this PoC subset.';}
|
| 108 |
-
function geneModal(){stopSweep();const g=D.example;$('#gene-dialog').showModal();$('#gene-summary').textContent=fmt(g.gene_count)+' candidate coding loci in this assembly';const svg=$('#gene-svg');let start=0,end=1000000,width=1100,left=30,right=1080;let x=p=>left+(p-start)/(end-start)*(right-left);let parts=[];for(let p=0;p<=end;p+=100000){parts.push(`<line x1="${x(p)}" y1="38" x2="${x(p)}" y2="198" stroke="${palette.grid}"/><text x="${x(p)}" y="24" text-anchor="middle">${p/1e6} Mb</text>`)}
|
| 109 |
for(let gene of g.genes){if(gene.start>end)continue;let y=gene.strand==='+'?92:158;parts.push(`<g><title>${esc(gene.id)} · ${fmt(gene.start)}–${fmt(gene.end)} bp · ${gene.strand}</title><line x1="${x(gene.start)}" x2="${x(Math.min(end,gene.end))}" y1="${y}" y2="${y}" stroke="${palette.bar}"/>`);for(let [s,e] of gene.cds)if(s<=end)parts.push(`<rect x="${x(s)}" y="${y-7}" width="${Math.max(.6,x(Math.min(end,e))-x(s))}" height="14" fill="${palette.green}"/>`);parts.push('</g>')};svg.innerHTML=parts.join('');$('#gene-count-window').textContent=g.genes.filter(g=>g.start<=end).length+' candidate loci in this 1 Mb window. Bars show predicted coding segments; connecting lines span intervening sequence.';
|
| 110 |
}
|
| 111 |
async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textContent.trim()),c=>c.charCodeAt(0));D=JSON.parse(await new Response(new Blob([raw]).stream().pipeThrough(new DecompressionStream('gzip'))).text());prepare();await Promise.all([document.fonts.ready,loadAtlasIcons()]);setupGL();prepareSweep();setTheme(theme);makeLocusBins();resize();updateLegend();$('#loading').remove();$('#shown').textContent=fmt(leaves.length);$('#taxa-shown').textContent=fmt(D.meta.taxa);$('#search').disabled=false;
|
|
@@ -131,8 +132,11 @@ async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textConte
|
|
| 131 |
$('#selection').addEventListener('pointerleave',e=>{if(e.relatedTarget!==canvas)hoverAssembly(null)});
|
| 132 |
$('#sel-download').onclick=()=>showDataAccess(selected);$('#data-close').onclick=()=>$('#data-dialog').close();$('#data-copy').onclick=copyDataCode;
|
| 133 |
$('#theme-toggle').onclick=()=>setTheme(theme==='dark'?'light':'dark');
|
|
|
|
| 134 |
$('#locus-mode').onchange=e=>{locusMode=e.target.value;makeLocusBins()};$('#show-icons').onchange=e=>{showIcons=e.target.checked;schedule()};$('#credits-open').onclick=()=>$('#credits-dialog').showModal();$('#credits-close').onclick=()=>$('#credits-dialog').close();
|
| 135 |
stage.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect(),x=e.clientX-b.left,y=e.clientY-b.top;if(!hitDial(x,y))zoomAt(3,x,y)});
|
| 136 |
$('#zoomin').onclick=()=>zoomAt(2);$('#zoomout').onclick=()=>zoomAt(.5);$('#reset').onclick=reset;$('#search').addEventListener('input',e=>search(e.target.value));$('#search').addEventListener('keydown',e=>{if(e.key==='Escape')$('#results').replaceChildren();if(e.key==='Enter')$('#results button')?.click()});$('#scope').onchange=e=>{mode=Number(e.target.value);updateLegend()};$('#existing-only').onchange=e=>{view=e.target.checked?'existing':'all';schedule()};$('#human').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'));$('#detail').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'),95);$('#sel-loci').onclick=geneModal;$('#gene-open').onclick=geneModal;$('#gene-close').onclick=()=>$('#gene-dialog').close();$('#sel-close').onclick=()=>{clearSelection();schedule()};$('#sel-focus').onclick=()=>focusAssembly(selected,100);$('#about').onclick=()=>{stopSweep();$('#about-dialog').showModal()};$('#about-close').onclick=()=>$('#about-dialog').close();
|
| 137 |
-
|
|
|
|
|
|
|
| 138 |
init();
|
|
|
|
| 88 |
if(d<best){best=d;found=leaves[hitOwner[k/8]];}
|
| 89 |
}return found;
|
| 90 |
}
|
| 91 |
+
function closeViewOptions(){if($('#view-dialog').open)$('#view-dialog').close()}
|
| 92 |
+
function focusAssembly(a,z=70){closeViewOptions();stopSweep(false);selected=a;zoom=z;let s=baseScale*zoom;pan={x:-a.tip[0]*s,y:-a.tip[1]*s};selectAssembly(a);schedule()}
|
| 93 |
function clearSelection(){selected=pinnedAssembly=hoveredAssembly=null;$('#selection').hidden=true;scheduleInteraction()}
|
| 94 |
function hoverAssembly(a){
|
| 95 |
if(sweep.running||pinnedAssembly)return;
|
|
|
|
| 106 |
updateDataAccess(a);$('#sel-loci').hidden=a.accession!=='GCA_000001405.29';scheduleInteraction()}
|
| 107 |
function updateLegend(){let n=mode?D.meta.refseq_matches:D.meta.classification_counts['1'];$('#blue-count').textContent=fmt(n);$('#green-count').textContent=fmt(leaves.length-n);$('#blue-label').textContent=mode?'In RefSeq comparison corpus':'GenBank annotation record';$('#green-label').textContent=mode?'Outside that comparison corpus':'No GenBank record listed';$('#scope-note').textContent=mode?'Counts are assemblies, not genes. Blue marks exact paired-accession matches to the project’s 2,055-assembly RefSeq corpus, not all RefSeq annotations. Green does not imply new genes.':'Counts are assemblies, not genes. This view checks GenBank submission metadata; it does not include annotations stored only on paired RefSeq records. Missing metadata is not proof of no prior annotation.';if(selected&&!sweep.running)selectAssembly(selected,false);schedule()}
|
| 108 |
function search(q){q=q.trim().toLowerCase();let out=[];if(q.length>1){out=leaves.filter(a=>a.accession.toLowerCase().includes(q)||a.name.toLowerCase().includes(q)||(nodes.get(a.taxid).common||'').toLowerCase().includes(q)).slice(0,7);}$('#results').replaceChildren(...out.map(a=>{let b=document.createElement('button');b.innerHTML=`<strong>${esc(a.name)}</strong><span>${esc(a.accession)}</span>`;b.onclick=()=>{focusAssembly(a);$('#results').replaceChildren();$('#search').value=''};return b}));if(q.length>1&&!out.length)$('#results').textContent='No match in this PoC subset.';}
|
| 109 |
+
function geneModal(){closeViewOptions();stopSweep();const g=D.example;$('#gene-dialog').showModal();$('#gene-summary').textContent=fmt(g.gene_count)+' candidate coding loci in this assembly';const svg=$('#gene-svg');let start=0,end=1000000,width=1100,left=30,right=1080;let x=p=>left+(p-start)/(end-start)*(right-left);let parts=[];for(let p=0;p<=end;p+=100000){parts.push(`<line x1="${x(p)}" y1="38" x2="${x(p)}" y2="198" stroke="${palette.grid}"/><text x="${x(p)}" y="24" text-anchor="middle">${p/1e6} Mb</text>`)}
|
| 110 |
for(let gene of g.genes){if(gene.start>end)continue;let y=gene.strand==='+'?92:158;parts.push(`<g><title>${esc(gene.id)} · ${fmt(gene.start)}–${fmt(gene.end)} bp · ${gene.strand}</title><line x1="${x(gene.start)}" x2="${x(Math.min(end,gene.end))}" y1="${y}" y2="${y}" stroke="${palette.bar}"/>`);for(let [s,e] of gene.cds)if(s<=end)parts.push(`<rect x="${x(s)}" y="${y-7}" width="${Math.max(.6,x(Math.min(end,e))-x(s))}" height="14" fill="${palette.green}"/>`);parts.push('</g>')};svg.innerHTML=parts.join('');$('#gene-count-window').textContent=g.genes.filter(g=>g.start<=end).length+' candidate loci in this 1 Mb window. Bars show predicted coding segments; connecting lines span intervening sequence.';
|
| 111 |
}
|
| 112 |
async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textContent.trim()),c=>c.charCodeAt(0));D=JSON.parse(await new Response(new Blob([raw]).stream().pipeThrough(new DecompressionStream('gzip'))).text());prepare();await Promise.all([document.fonts.ready,loadAtlasIcons()]);setupGL();prepareSweep();setTheme(theme);makeLocusBins();resize();updateLegend();$('#loading').remove();$('#shown').textContent=fmt(leaves.length);$('#taxa-shown').textContent=fmt(D.meta.taxa);$('#search').disabled=false;
|
|
|
|
| 132 |
$('#selection').addEventListener('pointerleave',e=>{if(e.relatedTarget!==canvas)hoverAssembly(null)});
|
| 133 |
$('#sel-download').onclick=()=>showDataAccess(selected);$('#data-close').onclick=()=>$('#data-dialog').close();$('#data-copy').onclick=copyDataCode;
|
| 134 |
$('#theme-toggle').onclick=()=>setTheme(theme==='dark'?'light':'dark');
|
| 135 |
+
$('#view-options').onclick=()=>{stopSweep(false);$('#view-dialog').showModal()};$('#view-close').onclick=closeViewOptions;
|
| 136 |
$('#locus-mode').onchange=e=>{locusMode=e.target.value;makeLocusBins()};$('#show-icons').onchange=e=>{showIcons=e.target.checked;schedule()};$('#credits-open').onclick=()=>$('#credits-dialog').showModal();$('#credits-close').onclick=()=>$('#credits-dialog').close();
|
| 137 |
stage.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect(),x=e.clientX-b.left,y=e.clientY-b.top;if(!hitDial(x,y))zoomAt(3,x,y)});
|
| 138 |
$('#zoomin').onclick=()=>zoomAt(2);$('#zoomout').onclick=()=>zoomAt(.5);$('#reset').onclick=reset;$('#search').addEventListener('input',e=>search(e.target.value));$('#search').addEventListener('keydown',e=>{if(e.key==='Escape')$('#results').replaceChildren();if(e.key==='Enter')$('#results button')?.click()});$('#scope').onchange=e=>{mode=Number(e.target.value);updateLegend()};$('#existing-only').onchange=e=>{view=e.target.checked?'existing':'all';schedule()};$('#human').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'));$('#detail').onclick=()=>focusAssembly(leaves.find(a=>a.accession==='GCA_000001405.29'),95);$('#sel-loci').onclick=geneModal;$('#gene-open').onclick=geneModal;$('#gene-close').onclick=()=>$('#gene-dialog').close();$('#sel-close').onclick=()=>{clearSelection();schedule()};$('#sel-focus').onclick=()=>focusAssembly(selected,100);$('#about').onclick=()=>{stopSweep();$('#about-dialog').showModal()};$('#about-close').onclick=()=>$('#about-dialog').close();
|
| 139 |
+
const params=new URLSearchParams(location.search);
|
| 140 |
+
if(params.get('animate')==='1')setTimeout(startSweep,1000);
|
| 141 |
+
}catch(e){if($('#loading'))$('#loading').textContent='Unable to load the tree: '+e.message;console.error(e)}}
|
| 142 |
init();
|
carbon-a-strand-poc.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
carbon-a-strand-source.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:88535e54b44f8f77a6db8355ad074bd5c4d55864579803461804134000c12aa9
|
| 3 |
+
size 15180542
|
check_presentation.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Browser verification for the presentation layout and counter-free sweep."""
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import sys,json,math
|
| 4 |
+
sys.path.insert(0,'/home/node/local/carbon-a-render/python')
|
| 5 |
+
from playwright.sync_api import sync_playwright
|
| 6 |
+
R=Path(__file__).parent
|
| 7 |
+
BROWSER='/opt/pw-browsers/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell'
|
| 8 |
+
ARGS=['--no-sandbox','--enable-webgl','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader','--disable-renderer-backgrounding','--disable-background-timer-throttling','--disable-backgrounding-occluded-windows']
|
| 9 |
+
|
| 10 |
+
def check(url=None,headers=None):
|
| 11 |
+
with sync_playwright() as p:
|
| 12 |
+
browser=p.chromium.launch(executable_path=BROWSER,args=ARGS)
|
| 13 |
+
page=browser.new_page(viewport={'width':1500,'height':1050},extra_http_headers=headers or {})
|
| 14 |
+
errors=[];page.on('pageerror',lambda e:errors.append(str(e)))
|
| 15 |
+
page.goto(url or (R/'index.html').as_uri(),wait_until='domcontentloaded')
|
| 16 |
+
page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
|
| 17 |
+
assert page.evaluate('POC.assemblies')==33588
|
| 18 |
+
assert page.locator('aside,#animate-sweep,.sweep-meter,#seen-species,#seen-assemblies,#seen-loci').count()==0
|
| 19 |
+
assert page.locator('.resource-links a').all_text_contents()==['Model↗','Database↗','Explorer↗','Paper↗','Blog↗']
|
| 20 |
+
assert page.locator('h1').inner_text()=='Carbon-A · Across the tree of life'
|
| 21 |
+
assert page.locator('.wordmark em').evaluate('e=>getComputedStyle(e).color')=='rgb(41, 233, 154)'
|
| 22 |
+
assert page.locator('#view-dialog').is_hidden()
|
| 23 |
+
assert page.locator('footer').bounding_box()['y']>=page.locator('.stage-wrap').bounding_box()['y']+page.locator('.stage-wrap').bounding_box()['height']
|
| 24 |
+
page.screenshot(path=str(R/'presentation-desktop.png'))
|
| 25 |
+
# Hover, frozen selection, and outside release still use the same geometry.
|
| 26 |
+
def point(i):
|
| 27 |
+
return page.evaluate('''i=>{const q=screen(polar(62,leaves[i].angle)),b=stage.getBoundingClientRect();return [b.x+q[0],b.y+q[1]]}''',i)
|
| 28 |
+
page.mouse.move(*point(12345));page.wait_for_function('selected?.i===12345')
|
| 29 |
+
assert page.locator('#selection').is_visible()
|
| 30 |
+
page.mouse.click(*point(12345));page.mouse.move(*point(25000));page.wait_for_timeout(60)
|
| 31 |
+
assert page.evaluate('pinnedAssembly.i')==12345
|
| 32 |
+
page.mouse.click(*point(25000));assert page.evaluate('selected.i')==12345
|
| 33 |
+
box=page.locator('#stage').bounding_box();page.mouse.click(box['x']+2,box['y']+2)
|
| 34 |
+
assert page.evaluate('pinnedAssembly===null')
|
| 35 |
+
# Search is accessible through the dialog; selecting a result closes it.
|
| 36 |
+
page.locator('#view-options').click();page.locator('#search').fill('GCA_000001405.29')
|
| 37 |
+
page.locator('#results button').first.click()
|
| 38 |
+
assert page.locator('#view-dialog').is_hidden()
|
| 39 |
+
assert page.locator('#sel-id').inner_text()=='GCA_000001405.29'
|
| 40 |
+
assert page.locator('#sel-files').get_attribute('href').endswith('/GCA_000001405.29')
|
| 41 |
+
page.locator('#sel-download').click();compile(page.locator('#data-code').inner_text(),'download_example','exec')
|
| 42 |
+
page.locator('#data-close').click();page.locator('#sel-close').click()
|
| 43 |
+
page.locator('#view-options').click();page.locator('#scope').select_option('1');page.locator('#locus-mode').select_option('20')
|
| 44 |
+
assert page.evaluate('mode')==1 and page.evaluate('locusMode')=='20'
|
| 45 |
+
page.locator('#scope').select_option('0');page.locator('#locus-mode').select_option('5');page.locator('#reset').click();page.locator('#view-close').click()
|
| 46 |
+
# Space inside a search input must never start animation.
|
| 47 |
+
page.locator('#view-options').click();page.locator('#search').focus();page.keyboard.press('Space')
|
| 48 |
+
assert not page.evaluate('sweep.running')
|
| 49 |
+
page.locator('#view-close').click();page.locator('#theme-toggle').click();assert page.evaluate('theme')=='light'
|
| 50 |
+
page.locator('#theme-toggle').click()
|
| 51 |
+
# Test capture from a fresh load, matching the recording entry point.
|
| 52 |
+
# Headless Chromium may stop compositor frames after repeated modal closures.
|
| 53 |
+
page.reload(wait_until='domcontentloaded')
|
| 54 |
+
page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
|
| 55 |
+
# Keyboard playback, smooth full turn, exact highlighted geometry and no UI counters.
|
| 56 |
+
page.evaluate('''()=>{document.activeElement.blur();window.framesSeen=[];window.treeRedraws=0;const oldDraw=gl.drawArrays.bind(gl);gl.drawArrays=(...a)=>{treeRedraws++;oldDraw(...a)};const oldUpdate=updateSweep;updateSweep=t=>{oldUpdate(t);framesSeen.push({t:performance.now(),p:sweep.progress,angle:sweep.angle,selected:selected?.i??null})}}''')
|
| 57 |
+
page.keyboard.press('Space');page.wait_for_function('sweep.running && sweep.progress>.1')
|
| 58 |
+
assert page.locator('#selection').is_hidden()
|
| 59 |
+
try: page.wait_for_function('sweep.status==="complete"',timeout=12000)
|
| 60 |
+
except Exception:
|
| 61 |
+
print('sweep failure',page.evaluate('({sweep,hidden:document.hidden,frames:window.framesSeen?.length,focus:document.activeElement.tagName,now:performance.now(),redraws:treeRedraws,pending,zoom})'),errors,flush=True);page.screenshot(path=str(R/'sweep-failure.png'));print('after screenshot',page.evaluate('sweep'),flush=True);raise
|
| 62 |
+
result=page.evaluate('({frames:framesSeen,redraws:treeRedraws,angle:sweep.angle,selected,zoom})')
|
| 63 |
+
frames=result['frames'];duration=frames[-1]['t']-frames[1]['t']
|
| 64 |
+
assert 3900<=duration<4700,duration
|
| 65 |
+
assert len(frames)>35,len(frames)
|
| 66 |
+
assert result['redraws']<6,result['redraws']
|
| 67 |
+
assert abs(result['angle']-1.5*math.pi)<1e-9
|
| 68 |
+
assert result['selected'] is None and result['zoom']==1
|
| 69 |
+
assert all(a['angle']<=b['angle'] for a,b in zip(frames,frames[1:]))
|
| 70 |
+
page.keyboard.press('Space');page.wait_for_function('sweep.progress>.1 && sweep.running');page.keyboard.press('Escape')
|
| 71 |
+
assert not page.evaluate('sweep.running')
|
| 72 |
+
# A phone layout has no horizontal overflow and retains all resource buttons.
|
| 73 |
+
page.evaluate('reset()');page.mouse.move(0,0);page.set_viewport_size({'width':390,'height':844})
|
| 74 |
+
page.wait_for_timeout(150)
|
| 75 |
+
assert page.evaluate('document.documentElement.scrollWidth')==390
|
| 76 |
+
assert page.locator('footer').bounding_box()['y']+page.locator('footer').bounding_box()['height']<=845
|
| 77 |
+
assert all(x.is_visible() for x in page.locator('.resource-links a').all())
|
| 78 |
+
page.screenshot(path=str(R/'presentation-mobile.png'))
|
| 79 |
+
assert not errors,errors
|
| 80 |
+
browser.close()
|
| 81 |
+
return {'sweep_duration_ms':round(duration),'sweep_frames':len(frames),'background_redraws':result['redraws'],'layout':'desktop and mobile passed','hover_freeze_and_release':'passed','search_and_downloads':'passed','resource_links':5,'counter_and_button':'removed','errors':errors}
|
| 82 |
+
|
| 83 |
+
if __name__=='__main__':
|
| 84 |
+
result=check();(R/'presentation-verification.json').write_text(json.dumps(result,indent=2));print(json.dumps(result))
|
export_still.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
|
|
| 1 |
from pathlib import Path
|
| 2 |
import sys
|
| 3 |
sys.path.insert(0,'/home/node/local/carbon-a-render/python')
|
| 4 |
from playwright.sync_api import sync_playwright
|
|
|
|
| 5 |
R=Path(__file__).parent
|
| 6 |
with sync_playwright() as p:
|
| 7 |
-
b=p.chromium.launch(executable_path=
|
| 8 |
-
page=b.new_page(viewport={'width':
|
| 9 |
-
page.goto((R/'index.html').as_uri()
|
| 10 |
-
page.
|
| 11 |
-
page.
|
| 12 |
-
|
| 13 |
-
|
|
|
|
| 1 |
+
"""Export the current presentation at 2× resolution, without capture controls."""
|
| 2 |
from pathlib import Path
|
| 3 |
import sys
|
| 4 |
sys.path.insert(0,'/home/node/local/carbon-a-render/python')
|
| 5 |
from playwright.sync_api import sync_playwright
|
| 6 |
+
from check_presentation import BROWSER,ARGS
|
| 7 |
R=Path(__file__).parent
|
| 8 |
with sync_playwright() as p:
|
| 9 |
+
b=p.chromium.launch(executable_path=BROWSER,args=ARGS)
|
| 10 |
+
page=b.new_page(viewport={'width':1080,'height':1350},device_scale_factor=2)
|
| 11 |
+
page.goto((R/'index.html').as_uri()+'?record=1')
|
| 12 |
+
page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
|
| 13 |
+
page.screenshot(path=str(R/'carbon-a-strand-poc.png'))
|
| 14 |
+
b.close()
|
| 15 |
+
print(R/'carbon-a-strand-poc.png')
|
index.html
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
layers.js
CHANGED
|
@@ -86,7 +86,7 @@ function hoverLayers(e){if(sweep.running)return;if(pinnedAssembly){$('#layer-too
|
|
| 86 |
if(!ic&&locusMode==='raw'){tip.hidden=true;if(hoverBin!==bin){hoverBin=bin;schedule()}return}
|
| 87 |
if(ic){tip.innerHTML=`<b>${esc(ic.n.common||ic.n.name)}</b><span>${esc(ic.n.name)} · ${fmt(ic.n.count)} assemblies</span><span>${fmt(mode?ic.n.refMatches:ic.n.existing)} ${mode?'match the RefSeq comparison corpus':'have GenBank annotation records'}</span><small>Assembly counts, not gene counts · click to explore</small>`}
|
| 88 |
else{const n=bin.hi-bin.lo,one=n===1,a=leaves[bin.lo];tip.innerHTML=`<b>${one?esc(a.name):shortCount(bin.value)+' predicted coding loci'}</b><span>${one?(bin.known?fmt(bin.value)+' predicted coding loci':'Locus count unavailable'):fmt(bin.known)+' of '+fmt(n)+' assemblies have counts'}</span><small>${one?esc(a.accession):'Sum of available counts · click to inspect individual bars'}</small>`;if(hoverBin!==bin){hoverBin=bin;schedule()}}
|
| 89 |
-
tip.hidden=false;tip.style.left=Math.max(8,Math.min(W-
|
| 90 |
}
|
| 91 |
function focusNode(n){stopSweep(false);let pts=[point(n,n.mid)];for(let j=0;j<=16;j++)pts.push(polar(R,START+SPAN*(n.lo+(n.hi-n.lo)*j/16)/leaves.length));let xs=pts.map(p=>p[0]),ys=pts.map(p=>p[1]),minx=Math.min(...xs),maxx=Math.max(...xs),miny=Math.min(...ys),maxy=Math.max(...ys);zoom=Math.max(1,Math.min(35,Math.min(W/(maxx-minx+140),H/(maxy-miny+140))/baseScale));pan={x:-(minx+maxx)/2*baseScale*zoom,y:-(miny+maxy)/2*baseScale*zoom};clearSelection();$('#layer-tooltip').hidden=true;schedule()}
|
| 92 |
function clickLayers(x,y){const ic=hitIcon(x,y);if(ic){focusNode(ic.n);return true}const b=hitBar(x,y);if(!b)return false;
|
|
|
|
| 86 |
if(!ic&&locusMode==='raw'){tip.hidden=true;if(hoverBin!==bin){hoverBin=bin;schedule()}return}
|
| 87 |
if(ic){tip.innerHTML=`<b>${esc(ic.n.common||ic.n.name)}</b><span>${esc(ic.n.name)} · ${fmt(ic.n.count)} assemblies</span><span>${fmt(mode?ic.n.refMatches:ic.n.existing)} ${mode?'match the RefSeq comparison corpus':'have GenBank annotation records'}</span><small>Assembly counts, not gene counts · click to explore</small>`}
|
| 88 |
else{const n=bin.hi-bin.lo,one=n===1,a=leaves[bin.lo];tip.innerHTML=`<b>${one?esc(a.name):shortCount(bin.value)+' predicted coding loci'}</b><span>${one?(bin.known?fmt(bin.value)+' predicted coding loci':'Locus count unavailable'):fmt(bin.known)+' of '+fmt(n)+' assemblies have counts'}</span><small>${one?esc(a.accession):'Sum of available counts · click to inspect individual bars'}</small>`;if(hoverBin!==bin){hoverBin=bin;schedule()}}
|
| 89 |
+
tip.hidden=false;const wrap=$('.stage-wrap').getBoundingClientRect();tip.style.left=box.left-wrap.left+Math.max(8,Math.min(W-tip.offsetWidth-8,x+16))+'px';tip.style.top=box.top-wrap.top+Math.max(8,Math.min(H-tip.offsetHeight-8,y+15))+'px';
|
| 90 |
}
|
| 91 |
function focusNode(n){stopSweep(false);let pts=[point(n,n.mid)];for(let j=0;j<=16;j++)pts.push(polar(R,START+SPAN*(n.lo+(n.hi-n.lo)*j/16)/leaves.length));let xs=pts.map(p=>p[0]),ys=pts.map(p=>p[1]),minx=Math.min(...xs),maxx=Math.max(...xs),miny=Math.min(...ys),maxy=Math.max(...ys);zoom=Math.max(1,Math.min(35,Math.min(W/(maxx-minx+140),H/(maxy-miny+140))/baseScale));pan={x:-(minx+maxx)/2*baseScale*zoom,y:-(miny+maxy)/2*baseScale*zoom};clearSelection();$('#layer-tooltip').hidden=true;schedule()}
|
| 92 |
function clickLayers(x,y){const ic=hitIcon(x,y);if(ic){focusNode(ic.n);return true}const b=hitBar(x,y);if(!b)return false;
|
presentation-desktop.png
ADDED
|
Git LFS Details
|
presentation-mobile.png
ADDED
|
Git LFS Details
|
presentation-verification.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"sweep_duration_ms": 4008,
|
| 3 |
+
"sweep_frames": 73,
|
| 4 |
+
"background_redraws": 0,
|
| 5 |
+
"layout": "desktop and mobile passed",
|
| 6 |
+
"hover_freeze_and_release": "passed",
|
| 7 |
+
"search_and_downloads": "passed",
|
| 8 |
+
"resource_links": 5,
|
| 9 |
+
"counter_and_button": "removed",
|
| 10 |
+
"errors": []
|
| 11 |
+
}
|
provenance.json
CHANGED
|
@@ -67,5 +67,11 @@
|
|
| 67 |
"bucket": "HuggingFaceBio/genbank-annotations",
|
| 68 |
"paths": "Embedded from per-assembly metadata source paths",
|
| 69 |
"generated_example": "GCA_000001405.29; all 4 files downloaded, annotation/sequence hashes match metadata"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
}
|
|
|
|
| 67 |
"bucket": "HuggingFaceBio/genbank-annotations",
|
| 68 |
"paths": "Embedded from per-assembly metadata source paths",
|
| 69 |
"generated_example": "GCA_000001405.29; all 4 files downloaded, annotation/sequence hashes match metadata"
|
| 70 |
+
},
|
| 71 |
+
"presentation": {
|
| 72 |
+
"updated_at": "2026-10-12",
|
| 73 |
+
"layout": "Wide title, centered tree and five resource buttons; no sidebar",
|
| 74 |
+
"animation": "4-second sweep; no counters or visible playback button; keyboard Space or POC.startSweep()",
|
| 75 |
+
"data_changed": false
|
| 76 |
}
|
| 77 |
}
|
sweep.js
CHANGED
|
@@ -1,22 +1,16 @@
|
|
| 1 |
-
//
|
| 2 |
const SWEEP_DURATION=4000,NOON=-Math.PI/2;
|
| 3 |
-
const sweep={running:false,status:'ready',progress:0,angle:NOON,
|
| 4 |
-
let
|
| 5 |
|
| 6 |
function prepareSweep(){
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
if(
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
sweepKnown[i+1]=sweepKnown[i]+(a.loci!==null?1:0);
|
| 15 |
-
}
|
| 16 |
-
POC.sweep=sweep;POC.sweepTotals={species:seen.size,assemblies:n,loci:sweepLoci[n],known:sweepKnown[n]};
|
| 17 |
-
$('#animate-sweep').disabled=false;
|
| 18 |
-
$('#animate-sweep').onclick=()=>sweep.running?stopSweep():startSweep();
|
| 19 |
-
document.addEventListener('keydown',e=>{if(e.key==='Escape'&&sweep.running)stopSweep()});
|
| 20 |
document.addEventListener('visibilitychange',()=>{if(document.hidden&&sweep.running)stopSweep()});
|
| 21 |
}
|
| 22 |
|
|
@@ -47,17 +41,10 @@ function updateSweep(progress){
|
|
| 47 |
const t=sweep.progress,eased=t*t*(3-2*t);
|
| 48 |
sweep.angle=NOON+TAU*eased;
|
| 49 |
const n=leaves.length,count=Math.max(0,Math.min(n,Math.floor((sweep.angle-START)/SPAN*n+.5)));
|
| 50 |
-
sweep.count=count;sweep.species=sweepSpecies[count];sweep.loci=sweepLoci[count];sweep.known=sweepKnown[count];
|
| 51 |
selected=count&&sweep.angle<=START+SPAN?leaves[count-1]:null;
|
| 52 |
-
$('#seen-species').textContent=fmt(sweep.species);
|
| 53 |
-
$('#seen-assemblies').textContent=fmt(count);
|
| 54 |
-
$('#seen-loci').textContent=fmt(sweep.loci);
|
| 55 |
-
$('#seen-loci').title=fmt(sweep.known)+' assemblies with published counts; missing counts are excluded.';
|
| 56 |
}
|
| 57 |
|
| 58 |
-
function
|
| 59 |
-
$('#sweep-button-label').textContent=label;
|
| 60 |
-
$('#animate-sweep').setAttribute('aria-label',sweep.running?'Stop the sweep':'Animate a 4-second sweep of the whole tree');
|
| 61 |
$('.stage-wrap').dataset.sweeping=String(sweep.running);
|
| 62 |
}
|
| 63 |
|
|
@@ -73,7 +60,7 @@ function startSweep(){
|
|
| 73 |
zoom=1;pan={x:0,y:0};clearSelection();hoverBin=null;$('#layer-tooltip').hidden=true;
|
| 74 |
$('#results').replaceChildren();$('#selection').hidden=true;
|
| 75 |
sweep.running=true;sweep.status='running';sweep.startedAt=0;
|
| 76 |
-
updateSweep(0);
|
| 77 |
// Render the stationary tree once. Only the thin interaction canvas moves.
|
| 78 |
if(redraw)draw();else{cacheSweepTree();drawInteraction()}
|
| 79 |
sweep.frame=requestAnimationFrame(function step(){
|
|
@@ -84,8 +71,8 @@ function startSweep(){
|
|
| 84 |
if(sweep.progress<1){sweep.frame=requestAnimationFrame(step);return}
|
| 85 |
sweep.running=false;sweep.status='complete';sweep.frame=0;
|
| 86 |
selected=pinnedAssembly=hoveredAssembly=null;
|
| 87 |
-
|
| 88 |
-
$('#sweep-announcement').textContent=
|
| 89 |
drawInteraction();
|
| 90 |
});
|
| 91 |
}
|
|
@@ -93,7 +80,7 @@ function startSweep(){
|
|
| 93 |
function stopSweep(pin=true){
|
| 94 |
if(!sweep.running)return;
|
| 95 |
cancelAnimationFrame(sweep.frame);sweep.frame=0;sweep.running=false;sweep.status='stopped';
|
| 96 |
-
|
| 97 |
if(pin&&selected)selectAssembly(selected);else selected=pinnedAssembly=hoveredAssembly=null;
|
| 98 |
scheduleInteraction();
|
| 99 |
}
|
|
|
|
| 1 |
+
// A four-second sweep for video capture, with no counters or on-page controls.
|
| 2 |
const SWEEP_DURATION=4000,NOON=-Math.PI/2;
|
| 3 |
+
const sweep={running:false,status:'ready',progress:0,angle:NOON,frame:0,startedAt:0};
|
| 4 |
+
let interactionPending=false;
|
| 5 |
|
| 6 |
function prepareSweep(){
|
| 7 |
+
Object.assign(POC,{sweep,startSweep,stopSweep});
|
| 8 |
+
document.addEventListener('keydown',e=>{
|
| 9 |
+
if(e.key==='Escape'&&sweep.running)stopSweep();
|
| 10 |
+
if(e.code!=='Space'||e.repeat||e.altKey||e.ctrlKey||e.metaKey)return;
|
| 11 |
+
if(e.target.closest('input,textarea,select,button,a,[contenteditable]')||document.querySelector('dialog[open]'))return;
|
| 12 |
+
e.preventDefault();sweep.running?stopSweep(false):startSweep();
|
| 13 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
document.addEventListener('visibilitychange',()=>{if(document.hidden&&sweep.running)stopSweep()});
|
| 15 |
}
|
| 16 |
|
|
|
|
| 41 |
const t=sweep.progress,eased=t*t*(3-2*t);
|
| 42 |
sweep.angle=NOON+TAU*eased;
|
| 43 |
const n=leaves.length,count=Math.max(0,Math.min(n,Math.floor((sweep.angle-START)/SPAN*n+.5)));
|
|
|
|
| 44 |
selected=count&&sweep.angle<=START+SPAN?leaves[count-1]:null;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
+
function updateSweepState(){
|
|
|
|
|
|
|
| 48 |
$('.stage-wrap').dataset.sweeping=String(sweep.running);
|
| 49 |
}
|
| 50 |
|
|
|
|
| 60 |
zoom=1;pan={x:0,y:0};clearSelection();hoverBin=null;$('#layer-tooltip').hidden=true;
|
| 61 |
$('#results').replaceChildren();$('#selection').hidden=true;
|
| 62 |
sweep.running=true;sweep.status='running';sweep.startedAt=0;
|
| 63 |
+
updateSweep(0);updateSweepState();
|
| 64 |
// Render the stationary tree once. Only the thin interaction canvas moves.
|
| 65 |
if(redraw)draw();else{cacheSweepTree();drawInteraction()}
|
| 66 |
sweep.frame=requestAnimationFrame(function step(){
|
|
|
|
| 71 |
if(sweep.progress<1){sweep.frame=requestAnimationFrame(step);return}
|
| 72 |
sweep.running=false;sweep.status='complete';sweep.frame=0;
|
| 73 |
selected=pinnedAssembly=hoveredAssembly=null;
|
| 74 |
+
updateSweepState();
|
| 75 |
+
$('#sweep-announcement').textContent='Sweep complete.';
|
| 76 |
drawInteraction();
|
| 77 |
});
|
| 78 |
}
|
|
|
|
| 80 |
function stopSweep(pin=true){
|
| 81 |
if(!sweep.running)return;
|
| 82 |
cancelAnimationFrame(sweep.frame);sweep.frame=0;sweep.running=false;sweep.status='stopped';
|
| 83 |
+
updateSweepState();$('#sweep-announcement').textContent='Sweep stopped.';
|
| 84 |
if(pin&&selected)selectAssembly(selected);else selected=pinnedAssembly=hoveredAssembly=null;
|
| 85 |
scheduleInteraction();
|
| 86 |
}
|
template.html
CHANGED
|
@@ -1,41 +1,44 @@
|
|
| 1 |
-
<!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Carbon-A —
|
| 2 |
__FONTS__
|
| 3 |
:root{color-scheme:dark;--green:#29e99a;--blue:#789dff;--ink:#e6f0ed;--muted:#91a5a4;--rule:#233038;--bg:#080e13;--panel:#0d151c;--hover:#16252c;--secondary:#bccdc7;--shadow:#00000050}
|
| 4 |
-
:root[data-theme="light"]{color-scheme:light;--green:#00b86b;--blue:#4167ec;--ink:#14221c;--muted:#6e7b73;--rule:#e6ece8;--bg:#fff;--panel:#fff;--hover:#f6faf7;--secondary:#435a4c;--shadow:#2035280c}
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
.
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
#
|
| 22 |
-
#
|
| 23 |
-
#
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
#
|
| 27 |
-
#
|
| 28 |
-
.
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
.sweep-meter{position:absolute;left:25px;right:25px;top:70px;display:grid;grid-template-columns:1fr 1fr 1.3fr;gap:0 24px;max-width:650px;z-index:2}
|
| 32 |
-
.sweep-meter strong{display:block;font-family:Manrope,Arial,sans-serif;font-size:26px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.7px;line-height:1.15}
|
| 33 |
-
.sweep-meter>div:last-of-type strong{color:var(--green)}
|
| 34 |
-
.sweep-meter>div>span{display:block;font-size:10px;color:var(--muted);margin-top:5px}
|
| 35 |
-
.sweep-meter p{grid-column:1/-1;display:flex;gap:18px;font-size:9px;color:var(--muted);margin:12px 0 0;line-height:1.4}
|
| 36 |
-
#sweep-status{color:var(--secondary)}
|
| 37 |
-
#stage{top:159px}#selection{top:159px}
|
| 38 |
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
| 39 |
-
@media(max-width:
|
| 40 |
-
|
| 41 |
-
</style></head><body><header><div class="brand">Carbon-<em>A</em> Database</div><div class="header-right"><span class="tag">Assembly strands · Proof of concept</span><button id="about">About the data</button><button id="theme-toggle" aria-label="Switch to light mode" title="Switch to light mode"><svg class="theme-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 1v3m0 16v3M1 12h3m16 0h3M4.2 4.2l2.1 2.1m11.4 11.4 2.1 2.1M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg><svg class="theme-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 14.5A9 9 0 0 1 9.5 3.5a9 9 0 1 0 11 11Z"/></svg></button></div></header><main><aside><h1>The scale is<br>in the tree.</h1><p class="intro">One strand for every assembly.<br>Shared ancestry brings the strands together. Zoom in to follow them apart.</p><p class="subset"><b>PoC subset · <span id="shown">33,588</span> assemblies · <span id="taxa-shown">16,976</span> taxa.</b><br>The full release contains 48,167 assemblies.</p><section class="locus-controls"><label class="tag" for="locus-mode">Coding loci at the strand tips</label><select id="locus-mode"><option value="5">Binned · 5 assemblies per bar</option><option value="20">Binned · 20 assemblies per bar</option><option value="50">Binned · 50 assemblies per bar</option><option value="200">Binned · 200 assemblies per bar</option><option value="raw">Individual assemblies</option><option value="off">Hide locus bars</option></select><strong id="locus-readout"></strong><p id="locus-explain"></p><label class="checkbox icon-toggle"><input type="checkbox" id="show-icons" checked>Genome Atlas silhouettes</label></section><div class="release"><span class="tag">Full release</span><div class="big">566.34<em>M</em></div><div class="sub">candidate coding loci</div><div class="stats"><div><b>48,167</b><span>annotated assemblies</span></div><div><b>22,617</b><span>annotated taxa</span></div><div class="stat-wide"><b>27.18 Tbp</b><span>across included assemblies</span></div></div></div><section class="legend"><label class="tag" for="scope">Compare assembly records</label><select id="scope"><option value="0">GenBank annotation records</option><option value="1">RefSeq comparison corpus</option></select><div class="key"><i class="sample-line blue"></i><span id="blue-label"></span><strong id="blue-count"></strong></div><div class="key"><i class="sample-line"></i><span id="green-label"></span><strong id="green-count"></strong></div><label class="checkbox"><input id="existing-only" type="checkbox">Emphasise matching assemblies</label><p id="scope-note" class="scope-note"></p></section><div class="quick"><button id="human">Find a human assembly ↗</button></div><button id="gene-open" style="font-size:11px;border:0;padding:6px 0;color:var(--green)">See its coding regions →</button></aside><section class="stage-wrap"><div class="topbar"><div class="searchbox"><input id="search" type="search" aria-label="Search assemblies or species" placeholder="Search a species or assembly…" disabled autocomplete="off"><div id="results"></div></div><button id="animate-sweep" disabled aria-label="Animate a 4-second sweep of the whole tree"><svg class="sweep-play" viewBox="0 0 16 16" aria-hidden="true"><path d="M4 2.5 13 8 4 13.5Z"/></svg><svg class="sweep-stop" viewBox="0 0 16 16" aria-hidden="true"><path d="M4 4h8v8H4Z"/></svg><span id="sweep-button-label">Animate sweep</span></button></div><div class="sweep-meter" aria-label="Cumulative counts in the current sweep"><div><strong id="seen-species">0</strong><span>Species seen</span></div><div><strong id="seen-assemblies">0</strong><span>Assemblies seen</span></div><div><strong id="seen-loci">0</strong><span>Coding loci seen</span></div><p><span id="sweep-status">Ready · 4 seconds</span><span>Shown subset · unique species · available locus counts</span></p></div><span id="sweep-announcement" class="visually-hidden" role="status"></span><div id="stage"><canvas id="tree" aria-label="Interactive taxonomic tree of 33,588 assembly strands. Use search or the human assembly button to inspect individual records."></canvas><canvas id="tree-still" aria-hidden="true"></canvas><canvas id="overlay" aria-hidden="true"></canvas><canvas id="interaction" aria-hidden="true"></canvas><div id="loading">Preparing 33,588 assembly strands…</div></div><div id="layer-tooltip" hidden></div><section id="selection" hidden><button id="sel-close" aria-label="Close assembly details">×</button><div id="sel-state">Hover preview</div><h2 id="sel-name"></h2><a id="sel-id" target="_blank" rel="noopener"></a><div id="sel-lineage"></div><div id="sel-status"></div><div id="sel-data"></div><div class="data-access"><a id="sel-files" target="_blank" rel="noopener">Annotation files ↗</a><br><button id="sel-download">Get annotation data ↗</button><p id="sel-availability"></p></div><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section><div class="help"><b>Hover centre or strands · Click to freeze · Click outside to release · Scroll to zoom · Drag to pan</b><br>Topology follows taxonomy. Branch length does not represent evolutionary time.</div><div class="controls"><button id="detail">Strand detail</button><button id="zoomout" aria-label="Zoom out">−</button><span id="zoom-readout">1.0×</span><button id="zoomin" aria-label="Zoom in">+</button><button id="reset">Whole tree</button></div></section></main><dialog id="gene-dialog"><button class="dialog-close" id="gene-close" aria-label="Close coding-region view">×</button><span class="tag">From an assembly to its annotations</span><h2 style="margin-top:16px">Homo sapiens</h2><p id="gene-summary"></p><div class="gene-label">GCA_000001405.29 · chromosome 1, CM000663.2 · first 1,000,000 bases<br>Upper track: forward strand · lower track: reverse strand</div><svg id="gene-svg" viewBox="0 0 1110 210" role="img" aria-label="Actual predicted coding segments in the first one million bases of human chromosome 1"></svg><p id="gene-count-window"></p><p class="gene-notes">Real coordinates from the published Carbon-A GFF3. These are model predictions, not experimentally confirmed genes. The blue/green tree colours describe annotation records for an assembly; they do not classify individual loci as known or new.</p></dialog><dialog id="about-dialog"><button class="dialog-close" id="about-close" aria-label="Close data information">×</button><h2>What is being drawn</h2><p>Each of the 33,588 strands is a distinct accession from the explorer’s covered-assembly table, mapped through the NCBI taxonomy. No assemblies or taxonomic branches have been invented to fill out the release total.</p><ul><li>Tree and common names: the existing HuggingFaceBio GenBank annotation explorer, 7 October 2026.</li><li>Existing annotation evidence: the cached NCBI GenBank assembly summary, 7 October 2026; a named annotation provider is required.</li><li>RefSeq mode counts matching assemblies, not genes: exact versioned matches between an assembly’s paired GCF accession and the project’s 2,055-assembly comparison corpus. It does not cover all RefSeq annotations.</li><li>Gene detail: actual published GFF3 coordinates and metadata for GCA_000001405.29.</li><li>Release totals supplied on 8 October 2026: 48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp.</li></ul><p>The centre hand points to the selected strand. Hover or drag inside the centre to select by direction; clicking pins the selection. Animate sweep makes a full clockwise turn in 4 seconds and counts every assembly passed, including strands crossed between animation frames. Species are deduplicated by their NCBI species-rank ancestor; coding loci sum only published counts. A complete sweep of this subset reaches 16,374 species, 33,588 assemblies and 212,905,534 loci. The counters reset on replay.</p><p>Equal-width assembly strands preserve count through shared ancestors. At overview scale many strands fall within a pixel; zoom reveals them separately. Radius and branch length are layout choices, not dates or evolutionary distances.</p><p>For example, the human branch contains 1,412 assemblies: 2 match the project’s RefSeq comparison corpus, while 4 have annotation-provider records in this GenBank snapshot. These small counts describe assembly records, not human genes. RefSeq annotations are maintained separately from GenBank submission annotations. <a href="https://www.ncbi.nlm.nih.gov/refseq/about/human/" target="_blank" rel="noopener">NCBI’s explanation of human reference annotation</a>.</p><p>The outer bars sum measured gene_count values from published per-assembly metadata. Bins contain consecutive assembly tips in taxonomy order. Grey base marks indicate the fraction of assemblies without counts; these are not treated as measured zeros. The radial scale is linear and recalculated for each bin size. Colours within the tree classify assemblies; the green outer bars count all predicted loci, regardless of prior annotation status.</p><p>Silhouettes are the original PhyloPic assets and taxon assignments used by the Genome Atlas. Click an icon to explore its branch. <button id="credits-open">Icon credits and licences</button></p><p><a href="https://huggingface.co/spaces/HuggingFaceBio/genbank-annotation-explorer" target="_blank" rel="noopener">Source explorer ↗</a> · <a href="https://www.ncbi.nlm.nih.gov/datasets/docs/v2/reference-docs/ftp-browsing/" target="_blank" rel="noopener">NCBI metadata ↗</a></p></dialog><dialog id="credits-dialog"><button id="credits-close" class="dialog-close" aria-label="Close icon credits">×</button><h2>Genome Atlas silhouettes</h2><p>Original PhyloPic silhouettes, reused with the atlas’s taxonomic assignments. Their fill colour is adapted to the selected theme; original SVG files are preserved in the source data. Each image’s source page and licence are linked below.</p><div id="icon-credits"></div></dialog><dialog id="data-dialog"><button id="data-close" class="dialog-close" aria-label="Close download instructions">×</button><h2>Get annotation data</h2><p id="data-assembly"></p><p id="data-description"></p><p><a id="data-browse" target="_blank" rel="noopener">Browse annotation files ↗</a></p><p class="data-setup">Install <code>huggingface_hub</code> and run <code>hf auth login</code> with an account that can access the bucket.</p><pre><code id="data-code"></code></pre><button id="data-copy">Copy Python code</button><span id="copy-status" role="status"></span><p><a href="https://huggingface.co/docs/huggingface_hub/guides/buckets#download-files" target="_blank" rel="noopener">Hugging Face download documentation ↗</a></p></dialog><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Carbon-A — Across the tree of life</title><script>try{const t=localStorage.getItem('carbon-a-theme');if(t==='light'||t==='dark')document.documentElement.dataset.theme=t}catch{}if(new URLSearchParams(location.search).get('record')==='1')document.documentElement.dataset.recording='true';</script><style>
|
| 2 |
__FONTS__
|
| 3 |
:root{color-scheme:dark;--green:#29e99a;--blue:#789dff;--ink:#e6f0ed;--muted:#91a5a4;--rule:#233038;--bg:#080e13;--panel:#0d151c;--hover:#16252c;--secondary:#bccdc7;--shadow:#00000050}
|
| 4 |
+
:root[data-theme="light"]{color-scheme:light;--green:#00b86b;--blue:#4167ec;--ink:#14221c;--muted:#6e7b73;--rule:#e6ece8;--bg:#fff;--panel:#fff;--hover:#f6faf7;--secondary:#435a4c;--shadow:#2035280c}
|
| 5 |
+
*{box-sizing:border-box}body{margin:0;color:var(--ink);background:var(--bg);font:13px Inter,Arial,sans-serif}button,input,select{font:inherit;color:inherit}button,a,input,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer;background:var(--bg);border:1px solid var(--rule);padding:9px 13px}button:hover{border-color:var(--muted);background:var(--hover)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--green);outline-offset:3px}button:disabled{opacity:.35;cursor:default}a{color:inherit}input::placeholder{color:var(--muted);opacity:1}[hidden]{display:none!important}
|
| 6 |
+
.presentation{height:100svh;min-height:650px;display:grid;grid-template-rows:auto minmax(360px,1fr) auto}
|
| 7 |
+
.hero{padding:30px 28px 8px;text-align:center}
|
| 8 |
+
h1{font-family:Manrope,Arial,sans-serif;font-size:clamp(29px,3.4vw,55px);font-weight:500;line-height:1.2;letter-spacing:-1.9px;margin:0}
|
| 9 |
+
.wordmark{font-weight:800;white-space:nowrap}.wordmark em{font-style:normal;color:var(--green)}.title-separator{color:var(--muted);font-weight:400;margin:0 .24em}
|
| 10 |
+
.stage-wrap{position:relative;min-width:0;min-height:0;width:100%;max-width:1600px;justify-self:center;overflow:hidden}
|
| 11 |
+
#stage{position:absolute;inset:6px 24px 0;touch-action:none}canvas{position:absolute;width:100%;height:100%;inset:0}#tree{cursor:default;touch-action:none}#overlay,#interaction,#tree-still{pointer-events:none}#tree-still{visibility:hidden}[data-sweeping="true"] #tree,[data-sweeping="true"] #overlay{visibility:hidden}[data-sweeping="true"] #tree-still{visibility:visible}
|
| 12 |
+
.resources{padding:12px 24px 18px;text-align:center}
|
| 13 |
+
.resource-links{display:flex;justify-content:center;gap:10px;flex-wrap:wrap}
|
| 14 |
+
.resource-links a{display:flex;align-items:center;justify-content:space-between;gap:28px;padding:12px 16px;min-width:120px;border:1px solid var(--rule);border-radius:4px;text-decoration:none;font-size:13px;font-weight:500;background:transparent}
|
| 15 |
+
.resource-links a span{color:var(--green);font-size:15px}.resource-links a:hover{border-color:var(--green);background:var(--hover)}
|
| 16 |
+
.utilities{display:flex;justify-content:center;align-items:center;gap:14px;margin-top:9px}
|
| 17 |
+
.utilities button{border:0;background:transparent;color:var(--muted);padding:6px 2px;font-size:10px}.utilities button:hover{color:var(--ink)}
|
| 18 |
+
#theme-toggle{display:grid;place-items:center;width:26px;height:26px;padding:4px;flex-shrink:0}#theme-toggle svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.5;fill:none}[data-theme="dark"] .theme-moon,[data-theme="light"] .theme-sun{display:none}
|
| 19 |
+
[data-recording="true"] .utilities{visibility:hidden}[data-recording="true"] #stage{pointer-events:none}[data-recording="true"] #selection,[data-recording="true"] #layer-tooltip{display:none!important}
|
| 20 |
+
.tag{font-size:10px;letter-spacing:1.3px;color:var(--muted);text-transform:uppercase}
|
| 21 |
+
#loading{position:absolute;inset:0;display:grid;place-content:center;background:var(--bg);color:var(--muted);z-index:10}
|
| 22 |
+
#selection{position:absolute;right:24px;top:18px;width:295px;max-height:calc(100% - 36px);overflow:auto;background:var(--panel);border:1px solid var(--rule);box-shadow:0 8px 40px var(--shadow);z-index:5;padding:23px}
|
| 23 |
+
#sel-close{position:absolute;top:6px;right:8px;border:0;background:transparent;font-size:20px;padding:0 4px;color:var(--muted)}#sel-name{font:600 18px/1.25 Manrope,Arial,sans-serif;letter-spacing:-.5px;margin:0 0 6px}#sel-id{font-size:11px;color:var(--green);font-weight:500;text-decoration:none}#sel-lineage{font-size:10px;line-height:1.7;color:var(--muted);margin-top:15px}#sel-status{font-size:11px;line-height:1.6;border-top:1px solid var(--rule);padding-top:13px;margin-top:14px}#sel-status small{font-size:10px;color:var(--muted);margin-left:13px}.dot{display:inline-block;width:6px;height:6px;background:var(--green);border-radius:50%;margin-right:6px}.dot.blue{background:var(--blue)}#sel-data{display:grid;gap:3px;margin:16px 0;font-size:10px}#sel-data span{color:var(--muted);margin-top:7px}#sel-data b{font-weight:500;font-size:12px}#selection button:not(#sel-close){font-size:10px;margin:4px 0;padding:8px 10px}#sel-state{font-size:9px;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin:0 0 12px}#selection[data-pinned="true"] #sel-state{color:var(--green)}
|
| 24 |
+
#layer-tooltip{position:absolute;z-index:8;width:250px;pointer-events:none;padding:13px 15px;background:var(--panel);border:1px solid var(--rule);box-shadow:0 5px 25px var(--shadow);font-size:11px}#layer-tooltip b,#layer-tooltip span,#layer-tooltip small{display:block}#layer-tooltip b{font-weight:600;margin-bottom:6px}#layer-tooltip span{line-height:1.5}#layer-tooltip small{font-size:10px;color:var(--muted);margin-top:6px}
|
| 25 |
+
.data-access{border-top:1px solid var(--rule);padding-top:10px;margin-top:12px}.data-access button,.data-access a{color:var(--green)}.data-access a{font-size:11px;line-height:1.8;text-decoration:none}#sel-availability{font-size:10px;line-height:1.5;color:var(--muted);margin:3px 0 8px}
|
| 26 |
+
dialog{background:var(--panel);border:1px solid var(--rule);padding:32px;max-width:calc(100vw - 32px);max-height:85svh;width:1000px;color:var(--ink)}dialog::backdrop{background:#00000070;backdrop-filter:blur(3px)}dialog h2{font:600 28px/1.2 Manrope,Arial,sans-serif;letter-spacing:-1px;margin:0 0 15px}dialog p{font-size:12px;line-height:1.7;color:var(--muted)}dialog ul{padding-left:18px;font-size:12px;line-height:1.9;color:var(--muted)}.dialog-close{float:right;border:0;font-size:22px;padding:0 4px}
|
| 27 |
+
#view-dialog{width:520px}#about-dialog{width:680px}#credits-dialog{width:740px}#data-dialog{width:780px}
|
| 28 |
+
.searchbox{position:relative;margin:22px 0 18px}#search{width:100%;padding:12px;border:1px solid var(--rule);background:var(--bg);font-size:12px}#results{position:absolute;top:40px;width:100%;background:var(--panel);box-shadow:0 10px 30px var(--shadow);font-size:11px;z-index:4}#results:empty{display:none}#results button{display:block;width:100%;text-align:left;padding:11px;border:0;border-bottom:1px solid var(--rule)}#results strong,#results span{display:block;font-size:11px;font-weight:500}#results span{font-size:10px;color:var(--muted);margin-top:3px}
|
| 29 |
+
.legend,.locus-controls{border-top:1px solid var(--rule);padding-top:18px;margin-top:18px}.legend select,.locus-controls select{width:100%;border:0;border-bottom:1px solid var(--rule);padding:9px 0;margin:6px 0 12px;background:var(--panel);font-size:12px}.key{display:flex;align-items:center;gap:8px;font-size:11px;margin-bottom:10px}.key strong{margin-left:auto;font-weight:500;font-variant-numeric:tabular-nums}.sample-line{height:2px;width:21px;display:inline-block;background:var(--green);flex-shrink:0}.sample-line.blue{background:var(--blue)}.scope-note,.locus-controls p{font-size:10px;color:var(--muted);line-height:1.5;margin:10px 0 13px}.checkbox{font-size:11px;display:flex;gap:7px;align-items:center}.checkbox input{accent-color:var(--green)}.locus-controls strong{display:block;font-size:11px;font-weight:500;margin-top:10px}.icon-toggle{margin-top:12px}.quick{display:flex;gap:6px;margin:20px 0 6px}.quick button{font-size:11px;padding:8px 10px}.controls{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:18px}.controls button{font-size:11px;padding:7px 10px}.controls span{font-size:10px;padding:0 5px;min-width:42px;text-align:center;color:var(--muted)}
|
| 30 |
+
#icon-credits{max-height:60vh;overflow:auto}#icon-credits table{border-collapse:collapse;width:100%;font-size:11px;text-align:left}#icon-credits th,#icon-credits td{padding:10px 8px;border-bottom:1px solid var(--rule)}#icon-credits a,#data-dialog a{color:var(--green)}.gene-label{font-size:11px;margin-top:28px;color:var(--muted)}#gene-svg{width:100%;height:auto;font:11px Inter;fill:var(--muted);margin:16px 0}.gene-notes{border-top:1px solid var(--rule);padding-top:15px;font-size:11px}#data-dialog pre{max-height:45vh;overflow:auto;border:1px solid var(--rule);background:var(--bg);padding:18px;font-size:11px;line-height:1.7;white-space:pre}#copy-status{font-size:11px;margin-left:12px;color:var(--green)}#data-dialog .data-setup{font-size:11px}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
| 32 |
+
@media(max-width:700px){.presentation{min-height:650px}.hero{padding:25px 22px 4px}h1{font-size:clamp(26px,5.8vw,38px);letter-spacing:-1px}.title-separator{display:none}.title-topic{display:block;margin-top:6px;font-size:.8em}#stage{inset:0 4px}.resource-links{gap:7px}.resource-links a{min-width:0;padding:10px 11px;gap:12px;font-size:11px}.resources{padding:8px 12px 14px}.utilities{margin-top:8px}#selection{width:275px;right:12px;top:10px;max-height:calc(100% - 20px)}dialog{padding:24px}}
|
| 33 |
+
@media(prefers-reduced-motion:no-preference){button,.resource-links a{transition:background .15s,border-color .15s}}
|
| 34 |
+
</style></head><body>
|
| 35 |
+
<main class="presentation">
|
| 36 |
+
<header class="hero"><h1><span class="wordmark">Carbon-<em>A</em></span><span class="title-separator" aria-hidden="true"> · </span><span class="title-topic">Across the tree of life</span></h1></header>
|
| 37 |
+
<section class="stage-wrap" aria-label="Carbon-A assembly tree">
|
| 38 |
+
<span id="sweep-announcement" class="visually-hidden" role="status"></span>
|
| 39 |
+
<div id="stage"><canvas id="tree" aria-label="Interactive taxonomic tree of assembly strands. Hover to inspect, click to freeze, and scroll to zoom. Search is in View options."></canvas><canvas id="tree-still" aria-hidden="true"></canvas><canvas id="overlay" aria-hidden="true"></canvas><canvas id="interaction" aria-hidden="true"></canvas><div id="loading">Preparing the assembly tree…</div></div>
|
| 40 |
+
<div id="layer-tooltip" hidden></div><section id="selection" hidden><button id="sel-close" aria-label="Close assembly details">×</button><div id="sel-state">Hover preview</div><h2 id="sel-name"></h2><a id="sel-id" target="_blank" rel="noopener"></a><div id="sel-lineage"></div><div id="sel-status"></div><div id="sel-data"></div><div class="data-access"><a id="sel-files" target="_blank" rel="noopener">Annotation files ↗</a><br><button id="sel-download">Get annotation data ↗</button><p id="sel-availability"></p></div><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section></section>
|
| 41 |
+
<footer class="resources"><nav class="resource-links" aria-label="Carbon-A resources"><a href="https://huggingface.co/HuggingFaceBio/Carbon-A-1.2B" target="_blank" rel="noopener">Model<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations" target="_blank" rel="noopener">Database<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/spaces/HuggingFaceBio/carbon-a-database-explorer" target="_blank" rel="noopener">Explorer<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/buckets/HuggingFaceBio/Carbon-A-Technical-Report" target="_blank" rel="noopener">Paper<span aria-hidden="true">↗</span></a><a href="https://huggingface.co/blog/HuggingFaceBio/carbon-annotator-genbank-genome-annotation" target="_blank" rel="noopener">Blog<span aria-hidden="true">↗</span></a></nav><div class="utilities"><button id="view-options">View options</button><button id="about">About the data</button><button id="theme-toggle" aria-label="Switch to light mode" title="Switch to light mode"><svg class="theme-sun" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 1v3m0 16v3M1 12h3m16 0h3M4.2 4.2l2.1 2.1m11.4 11.4 2.1 2.1M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg><svg class="theme-moon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 14.5A9 9 0 0 1 9.5 3.5a9 9 0 1 0 11 11Z"/></svg></button></div></footer>
|
| 42 |
+
</main>
|
| 43 |
+
<dialog id="view-dialog"><button id="view-close" class="dialog-close" aria-label="Close view options">×</button><h2>Explore the tree</h2><div class="searchbox"><input id="search" type="search" aria-label="Search assemblies or species" placeholder="Search a species or assembly…" disabled autocomplete="off"><div id="results"></div></div><section class="locus-controls"><label class="tag" for="locus-mode">Coding loci at the strand tips</label><select id="locus-mode"><option value="5">Binned · 5 assemblies per bar</option><option value="20">Binned · 20 assemblies per bar</option><option value="50">Binned · 50 assemblies per bar</option><option value="200">Binned · 200 assemblies per bar</option><option value="raw">Individual assemblies</option><option value="off">Hide locus bars</option></select><strong id="locus-readout"></strong><p id="locus-explain"></p><label class="checkbox icon-toggle"><input type="checkbox" id="show-icons" checked>Genome Atlas silhouettes</label></section><section class="legend"><label class="tag" for="scope">Compare assembly records</label><select id="scope"><option value="0">GenBank annotation records</option><option value="1">RefSeq comparison corpus</option></select><div class="key"><i class="sample-line blue"></i><span id="blue-label"></span><strong id="blue-count"></strong></div><div class="key"><i class="sample-line"></i><span id="green-label"></span><strong id="green-count"></strong></div><label class="checkbox"><input id="existing-only" type="checkbox">Emphasise matching assemblies</label><p id="scope-note" class="scope-note"></p></section><div class="controls"><button id="detail">Strand detail</button><button id="zoomout" aria-label="Zoom out">−</button><span id="zoom-readout">1.0×</span><button id="zoomin" aria-label="Zoom in">+</button><button id="reset">Whole tree</button></div><div class="quick"><button id="human">Find a human assembly ↗</button><button id="gene-open">Its coding regions →</button></div><p>Hover to inspect · Click to freeze · Click outside to release.<br>Scroll to zoom · Drag to pan · Space to animate.</p></dialog>
|
| 44 |
+
<dialog id="gene-dialog"><button class="dialog-close" id="gene-close" aria-label="Close coding-region view">×</button><span class="tag">From an assembly to its annotations</span><h2 style="margin-top:16px">Homo sapiens</h2><p id="gene-summary"></p><div class="gene-label">GCA_000001405.29 · chromosome 1, CM000663.2 · first 1,000,000 bases<br>Upper track: forward strand · lower track: reverse strand</div><svg id="gene-svg" viewBox="0 0 1110 210" role="img" aria-label="Actual predicted coding segments in the first one million bases of human chromosome 1"></svg><p id="gene-count-window"></p><p class="gene-notes">Real coordinates from the published Carbon-A GFF3. These are model predictions, not experimentally confirmed genes. The blue/green tree colours describe annotation records for an assembly; they do not classify individual loci as known or new.</p></dialog><dialog id="about-dialog"><button class="dialog-close" id="about-close" aria-label="Close data information">×</button><h2>What is being drawn</h2><p>Each of the <span id="shown">33,588</span> strands across <span id="taxa-shown">16,976</span> taxa is a distinct accession from the explorer’s covered-assembly table, mapped through the NCBI taxonomy. This is a subset of the release. No assemblies or taxonomic branches have been invented to fill out the release total.</p><ul><li>Tree and common names: the existing HuggingFaceBio GenBank annotation explorer, 7 October 2026.</li><li>Existing annotation evidence: the cached NCBI GenBank assembly summary, 7 October 2026; a named annotation provider is required.</li><li>RefSeq mode counts matching assemblies, not genes: exact versioned matches between an assembly’s paired GCF accession and the project’s 2,055-assembly comparison corpus. It does not cover all RefSeq annotations.</li><li>Gene detail: actual published GFF3 coordinates and metadata for GCA_000001405.29.</li><li>Release totals supplied on 8 October 2026: 48,167 assemblies, 22,617 taxa, 566.34M coding loci and 27.18 Tbp.</li></ul><p>The centre hand points to the selected strand. Hover or drag inside the centre to select by direction; click to freeze and click outside the tree to release. For video capture, press Space to start or replay a four-second clockwise sweep. Escape stops it. The sweep has no counters.</p><p>Equal-width assembly strands preserve count through shared ancestors. At overview scale many strands fall within a pixel; zoom reveals them separately. Radius and branch length are layout choices, not dates or evolutionary distances.</p><p>For example, the human branch contains 1,412 assemblies: 2 match the project’s RefSeq comparison corpus, while 4 have annotation-provider records in this GenBank snapshot. These small counts describe assembly records, not human genes. RefSeq annotations are maintained separately from GenBank submission annotations. <a href="https://www.ncbi.nlm.nih.gov/refseq/about/human/" target="_blank" rel="noopener">NCBI’s explanation of human reference annotation</a>.</p><p>The outer bars sum measured gene_count values from published per-assembly metadata. Bins contain consecutive assembly tips in taxonomy order. Grey base marks indicate the fraction of assemblies without counts; these are not treated as measured zeros. The radial scale is linear and recalculated for each bin size. Colours within the tree classify assemblies; the green outer bars count all predicted loci, regardless of prior annotation status.</p><p>Silhouettes are the original PhyloPic assets and taxon assignments used by the Genome Atlas. Click an icon to explore its branch. <button id="credits-open">Icon credits and licences</button></p><p><a href="https://huggingface.co/spaces/HuggingFaceBio/carbon-a-database-explorer" target="_blank" rel="noopener">Source explorer ↗</a> · <a href="https://www.ncbi.nlm.nih.gov/datasets/docs/v2/reference-docs/ftp-browsing/" target="_blank" rel="noopener">NCBI metadata ↗</a></p></dialog><dialog id="credits-dialog"><button id="credits-close" class="dialog-close" aria-label="Close icon credits">×</button><h2>Genome Atlas silhouettes</h2><p>Original PhyloPic silhouettes, reused with the atlas’s taxonomic assignments. Their fill colour is adapted to the selected theme; original SVG files are preserved in the source data. Each image’s source page and licence are linked below.</p><div id="icon-credits"></div></dialog><dialog id="data-dialog"><button id="data-close" class="dialog-close" aria-label="Close download instructions">×</button><h2>Get annotation data</h2><p id="data-assembly"></p><p id="data-description"></p><p><a id="data-browse" target="_blank" rel="noopener">Browse annotation files ↗</a></p><p class="data-setup">Install <code>huggingface_hub</code> and run <code>hf auth login</code> with an account that can access the bucket.</p><pre><code id="data-code"></code></pre><button id="data-copy">Copy Python code</button><span id="copy-status" role="status"></span><p><a href="https://huggingface.co/docs/huggingface_hub/guides/buckets#download-files" target="_blank" rel="noopener">Hugging Face download documentation ↗</a></p></dialog><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>
|
verify_live.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import json,hashlib,requests
|
| 3 |
+
from huggingface_hub import HfApi,hf_hub_download,get_token
|
| 4 |
+
from check_presentation import check
|
| 5 |
+
R=Path(__file__).parent;repo='HuggingFaceBio/carbon-a-tree-preview';api=HfApi()
|
| 6 |
+
p=json.loads((R/'publication.json').read_text());info=api.repo_info(repo,repo_type='space')
|
| 7 |
+
assert info.private and info.sha==p['commit']
|
| 8 |
+
for name in ['index.html','carbon-a-strand-poc.png','provenance.json']:
|
| 9 |
+
downloaded=Path(hf_hub_download(repo,name,repo_type='space',revision=p['commit']))
|
| 10 |
+
assert hashlib.sha256(downloaded.read_bytes()).digest()==hashlib.sha256((R/name).read_bytes()).digest(),name
|
| 11 |
+
print('verified',name,flush=True)
|
| 12 |
+
url='https://huggingfacebio-carbon-a-tree-preview.static.hf.space/'
|
| 13 |
+
headers={'Authorization':'Bearer '+get_token()}
|
| 14 |
+
r=requests.get(url,headers=headers,timeout=30);r.raise_for_status()
|
| 15 |
+
assert r.content==(R/'index.html').read_bytes(),'Static app is still serving a different revision.'
|
| 16 |
+
result=check(url,headers)
|
| 17 |
+
p['verified']={'private':True,'runtime':api.get_space_runtime(repo).stage,'live_http_status':r.status_code,'files_match':True,'browser':result}
|
| 18 |
+
(R/'publication.json').write_text(json.dumps(p,indent=2));print(json.dumps(p['verified']))
|