lvwerra HF Staff commited on
Commit
d2ea2f5
·
verified ·
1 Parent(s): 3869686

Refine icons and locus bins; align strand highlights and clarify comparisons

Browse files
.gitattributes CHANGED
@@ -41,3 +41,4 @@ carbon-a-strand-poc.png filter=lfs diff=lfs merge=lfs -text
41
  loci-overview.png filter=lfs diff=lfs merge=lfs -text
42
  loci-raw.png filter=lfs diff=lfs merge=lfs -text
43
  locus-detail.png filter=lfs diff=lfs merge=lfs -text
 
 
41
  loci-overview.png filter=lfs diff=lfs merge=lfs -text
42
  loci-raw.png filter=lfs diff=lfs merge=lfs -text
43
  locus-detail.png filter=lfs diff=lfs merge=lfs -text
44
+ selection-aligned.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -19,28 +19,30 @@ Two comparison modes are available:
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.
21
 
 
 
22
  Colours classify assemblies, not the novelty of their individual predicted loci. A paired GCA and GCF may have different sequences and coordinates.
23
 
24
  The human assembly detail uses actual published GFF3 coordinates, with 23,767 predicted loci across GCA_000001405.29 and 13 beginning in the first megabase of CM000663.2. CDS rectangles are actual coding segments. These are predictions, not experimental validation.
25
 
26
  ## Locus bars and atlas silhouettes
27
 
28
- 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 **50 consecutive assemblies**; 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.
29
 
30
  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.
31
 
32
- The original **Genome Atlas silhouettes** 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 SVG artwork is unmodified.
33
 
34
  ## Layout and implementation
35
 
36
  The taxonomy and plain-language names reuse the earlier explorer work. Only ancestor nodes on paths to included assemblies are retained. The drawing simplifies intervening taxonomy to standard ranks; full lineage metadata is preserved. Radius is a layout choice, not evolutionary time or branch length.
37
 
38
- Every assembly has its own lane through parent bundles and its own endpoint. A common taper makes individual paths finer towards the root; all assemblies use the same width at a given radius. Density and bundle width encode assembly counts, not coding-locus counts. At overview scale, many paths occupy the same pixel. Zoom, pan, search and clicking reveal the underlying accession.
39
 
40
  The self-contained HTML includes compressed data, JavaScript and two local font families (Manrope and Inter). It makes no network requests to draw the tree. Links to source records open only when selected. WebGL is required. Source and data preparation scripts are included for reuse. The metadata counts include source paths, content hashes and pipeline revisions for auditing. The icon data includes each original source page and licence.
41
 
42
  ## Rebuild
43
 
44
- `python prepare_data.py` reproduces the compact dataset from the copied taxonomy database and cached NCBI summary (paths at the top of the script). `python build.py` builds the self-contained HTML from `template.html`, `app.js`, the data files, and the existing release font assets. `python check.py` runs the base browser checks; `python check_layers.py` checks bin totals, missing-count handling, raw/binned switching, clickable bars, node icons and attribution links. `python fetch_loci.py` refreshes the available metadata counts and `python prepare_icons.py` prepares the atlas icons.
45
 
46
  Data provenance and limitations are recorded in `provenance.json`. The original release video and explorer were not modified.
 
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.
21
 
22
+ The human branch illustrates why the scope matters: it contains **1,412 assembly accessions**, of which **2** match the RefSeq comparison corpus and **4** have a named GenBank annotation provider in this snapshot. These are assembly-record counts, not human gene counts. RefSeq annotation is maintained separately from GenBank submission metadata ([NCBI explanation](https://www.ncbi.nlm.nih.gov/refseq/about/human/)). The legend, node tooltips and species comparison in assembly details make this explicit.
23
+
24
  Colours classify assemblies, not the novelty of their individual predicted loci. A paired GCA and GCF may have different sequences and coordinates.
25
 
26
  The human assembly detail uses actual published GFF3 coordinates, with 23,767 predicted loci across GCA_000001405.29 and 13 beginning in the first megabase of CM000663.2. CDS rectangles are actual coding segments. These are predictions, not experimental validation.
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 SVG artwork is unmodified.
35
 
36
  ## Layout and implementation
37
 
38
  The taxonomy and plain-language names reuse the earlier explorer work. Only ancestor nodes on paths to included assemblies are retained. The drawing simplifies intervening taxonomy to standard ranks; full lineage metadata is preserved. Radius is a layout choice, not evolutionary time or branch length.
39
 
40
+ Every assembly has its own lane through parent bundles and its own endpoint. A common taper makes individual paths finer towards the root; all assemblies use the same width at a given radius. Density and bundle width encode assembly counts, not coding-locus counts. At overview scale, many paths occupy the same pixel. Zoom, pan, search and clicking reveal the underlying accession. The cursor is neutral over empty space and becomes a pointer over selectable strands; it becomes a drag cursor only during an actual pan. Selection highlights reuse the exact sampled coordinates drawn by WebGL, so they stay aligned with the underlying strand at every zoom level.
41
 
42
  The self-contained HTML includes compressed data, JavaScript and two local font families (Manrope and Inter). It makes no network requests to draw the tree. Links to source records open only when selected. WebGL is required. Source and data preparation scripts are included for reuse. The metadata counts include source paths, content hashes and pipeline revisions for auditing. The icon data includes each original source page and licence.
43
 
44
  ## Rebuild
45
 
46
+ `python prepare_data.py` reproduces the compact dataset from the copied taxonomy database and cached NCBI summary (paths at the top of the script). `python build.py` builds the self-contained HTML from `template.html`, `app.js`, the data files, and the existing release font assets. `python check_selection.py` verifies neutral/selection/drag cursor states, the human comparison counts, and alignment of the Canvas highlight with the rendered WebGL path. `python check.py` runs the base browser checks; `python check_layers.py` checks bin totals, missing-count handling, raw/binned switching, clickable bars, node icons and attribution links. `python fetch_loci.py` refreshes the available metadata counts and `python prepare_icons.py` prepares the atlas icons.
47
 
48
  Data provenance and limitations are recorded in `provenance.json`. The original release video and explorer were not modified.
app.js CHANGED
@@ -15,7 +15,7 @@ function prepare(){
15
  for(const n of nodes.values())if(n.parent)nodes.get(n.parent)?.children.push(n);
16
  D.assemblies.forEach((r,index)=>{nodes.get(r[1]).own.push({row:r,index,accession:r[0],taxid:r[1],name:r[2],loci:D.loci.counts[r[0]]??null,flag:(r[4]===1?1:0)+(r[5]?2:0)});});
17
  const root=nodes.get(2759);
18
- function count(n){n.count=n.own.length;for(let c of n.children)n.count+=count(c);n.children.sort((a,b)=>b.count-a.count||a.id-b.id);return n.count}count(root);
19
  leaves=[];
20
  function order(n){n.lo=leaves.length;for(let c of n.children)order(c);n.own.sort((a,b)=>a.accession.localeCompare(b.accession));leaves.push(...n.own);n.hi=leaves.length;n.mid=(n.lo+n.hi-1)/2;for(let a of n.own)a.i=leaves.indexOf(a,n.lo);}
21
  order(root);
@@ -42,10 +42,10 @@ function setupGL(){
42
  // Every real assembly has its own complete path. No generated placeholder leaves.
43
  let len=leaves.reduce((n,a)=>n+a.points.slice(1).reduce((m,p,j)=>m+stepsFor(a.points[j],p)*2*4,0),0),data=new Float32Array(len),k=0;hitOwner=new Uint32Array(len/8);
44
  const shuffled=leaves.slice().sort((a,b)=>hash(a.i)-hash(b.i));
45
- for(const a of shuffled)for(let j=1;j<a.points.length;j++){
46
  let p=a.points[j-1],q=a.points[j],[c,d]=controls(p,q),prev=p,steps=stepsFor(p,q);
47
  for(let t=1;t<=steps;t++){let next=bez(p,c,d,q,t/steps);hitOwner[k/8]=a.i;for(let v of [prev,next]){data[k++]=v[0];data[k++]=v[1];data[k++]=.008+.054*Math.pow(Math.min(1,Math.hypot(...v)/R),3);data[k++]=a.flag;}prev=next;}
48
- }
49
  hitData=data;vertexCount=k/4;const buffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buffer);gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);const loc=gl.getAttribLocation(program,'a');gl.enableVertexAttribArray(loc);gl.vertexAttribPointer(loc,4,gl.FLOAT,false,0,0);
50
  window.POC={assemblies:leaves.length,vertices:vertexCount,prior:D.meta.classification_counts['1'],refseq:D.meta.refseq_matches,data:D,leaves,nodes,select:selectAssembly,focus:focusAssembly};
51
  }
@@ -60,14 +60,14 @@ function draw(){if(!gl)return;let dpr=canvas.width/W;gl.viewport(0,0,canvas.widt
60
  const labels=zoom<2.5?groups:[...nodes.values()].filter(n=>['family','order','genus'].includes(n.rank)&&n.count>10&&n.count<leaves.length/zoom*.6);
61
  let placed=locusMode!=='off'&&zoom<1.6?[{x:W/2+pan.x-36,y:screen(polar(R+BAR_GAP+BAR_HEIGHT,START))[1]-14,w:72}]:[];for(const n of labels){let angle=START+SPAN*(n.mid+.5)/leaves.length;let [x,y]=screen(polar(R+BAR_HEIGHT+42,angle));if(x<35||x>W-35||y<25||y>H-25)continue;let name=n.common||n.name;if(name.length>27)name=n.name;ctx.font=(zoom<2.5?'500 13px':'500 12px')+' Inter';ctx.save();ctx.font='10px Inter';let cw=ctx.measureText(fmt(n.count)+' assemblies').width;ctx.restore();let tw=Math.max(ctx.measureText(name).width,cw),anchor=Math.cos(angle)>.25?'left':Math.cos(angle)<-.25?'right':'center';let bx=anchor==='left'?x:anchor==='right'?x-tw:x-tw/2;if(bx<8){x+=8-bx;bx=8}if(bx+tw>W-8){x-=bx+tw-W+8;bx=W-8-tw}if(placed.some(b=>bx<b.x+b.w+8&&bx+tw>b.x-8&&Math.abs(y-b.y)<32))continue;placed.push({x:bx,y,w:tw});ctx.textAlign=anchor;ctx.fillStyle='#273b30';ctx.strokeStyle='#fff';ctx.lineWidth=4;ctx.strokeText(name,x,y);ctx.fillText(name,x,y);ctx.font='10px Inter';ctx.fillStyle='#7a857f';ctx.strokeText(fmt(n.count)+' assemblies',x,y+15);ctx.fillText(fmt(n.count)+' assemblies',x,y+15);}
62
  drawAtlasIcons();
63
- if(selected){const a=selected;ctx.strokeStyle='#15392c';ctx.lineWidth=1.8;ctx.beginPath();let p=screen(a.points[0]);ctx.moveTo(...p);for(let j=1;j<a.points.length;j++){let [c,d]=controls(a.points[j-1],a.points[j]);ctx.bezierCurveTo(...screen(c),...screen(d),...screen(a.points[j]));}ctx.stroke();let q=screen(a.tip);ctx.fillStyle='#15392c';ctx.beginPath();ctx.arc(...q,4,0,TAU);ctx.fill();}
64
  $('#zoom-readout').textContent=zoom.toFixed(1)+'×';$('#zoomout').disabled=zoom<=1;$('#zoomin').disabled=zoom>=320;
65
  window.POC&&(window.POC.zoom=zoom);
66
  }
67
  function zoomAt(factor,x=W/2,y=H/2){$('#layer-tooltip').hidden=true;hoverBin=null;let old=zoom,n=Math.max(1,Math.min(320,zoom*factor));let ratio=n/old;pan.x=(pan.x+W/2-x)*ratio+x-W/2;pan.y=(pan.y+H/2-y)*ratio+y-H/2;zoom=n;if(zoom===1)pan={x:0,y:0};schedule()}
68
  function reset(){$('#layer-tooltip').hidden=true;hoverBin=null;zoom=1;pan={x:0,y:0};selected=null;$('#selection').hidden=true;schedule()}
69
- function nearest(x,y){
70
- const [wx,wy]=world(x,y),pad=7/(baseScale*zoom);let best=pad*pad,found=null;
71
  for(let k=0;k<hitData.length;k+=8){let ax=hitData[k],ay=hitData[k+1],bx=hitData[k+4],by=hitData[k+5];
72
  if(wx<Math.min(ax,bx)-pad||wx>Math.max(ax,bx)+pad||wy<Math.min(ay,by)-pad||wy>Math.max(ay,by)+pad)continue;
73
  let dx=bx-ax,dy=by-ay,l=dx*dx+dy*dy,t=l?Math.max(0,Math.min(1,((wx-ax)*dx+(wy-ay)*dy)/l)):0;
@@ -76,12 +76,12 @@ function nearest(x,y){
76
  }return found;
77
  }
78
  function focusAssembly(a,z=70){selected=a;zoom=z;let s=baseScale*zoom;pan={x:-a.tip[0]*s,y:-a.tip[1]*s};selectAssembly(a);schedule()}
79
- function selectAssembly(a){if(!a)return;selected=a;let r=a.row;$('#selection').hidden=false;$('#sel-name').textContent=a.name;$('#sel-id').textContent=a.accession;$('#sel-id').href='https://www.ncbi.nlm.nih.gov/datasets/genome/'+a.accession+'/';$('#sel-lineage').textContent=a.lineage.filter(n=>['kingdom','phylum','class','order','family','genus'].includes(n.rank)).map(n=>n.common||n.name).join(' › ');
80
  const has=mode?r[5]:r[4]===1;
81
- $('#sel-status').innerHTML=mode?`<i class="dot ${has?'blue':'green'}"></i>${has?'In the RefSeq comparison corpus':'Outside the RefSeq comparison corpus'}`:r[4]===1?`<i class="dot blue"></i>Existing annotation listed<br><small>${esc(r[7])} · ${esc(r[9])}</small>`:'<i class="dot green"></i>No annotation listed in this GenBank snapshot';
82
- $('#sel-data').innerHTML=`<span>Predicted coding loci</span><b>${a.loci===null?'Count unavailable':fmt(a.loci)}</b>`+(r[3]?`<span>Assembly length</span><b>${(r[3]/1e6).toLocaleString('en-US',{maximumFractionDigits:2})} Mb</b>`:'')+(r[6]?`<span>RefSeq counterpart</span><b>${esc(r[6])}</b>`:'')+(mode&&r[4]===1?`<span>GenBank annotation provider</span><b>${esc(r[7])}</b>`:'');
83
  $('#sel-loci').hidden=a.accession!=='GCA_000001405.29';schedule()}
84
- 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':'Existing annotation listed';$('#green-label').textContent=mode?'Outside that comparison corpus':'No annotation listed';$('#scope-note').textContent=mode?'Exact paired-accession matches to the project’s RefSeq comparison corpus. This is not all of RefSeq; green does not mean a gene is new.':'Existing annotations: a named annotation provider in the NCBI GenBank assembly summary. A missing record does not establish that an assembly was never annotated.';if(selected)selectAssembly(selected);schedule()}
85
  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.';}
86
  function geneModal(){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="#edf0ed"/><text x="${x(p)}" y="24" text-anchor="middle">${p/1e6} Mb</text>`)}
87
  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="#81d8ae"/>`);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="${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.';
@@ -89,7 +89,19 @@ function geneModal(){const g=D.example;$('#gene-dialog').showModal();$('#gene-su
89
  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();makeLocusBins();resize();updateLegend();$('#loading').remove();$('#shown').textContent=fmt(leaves.length);$('#taxa-shown').textContent=fmt(D.meta.taxa);$('#search').disabled=false;
90
  new ResizeObserver(resize).observe(stage);
91
  canvas.addEventListener('wheel',e=>{e.preventDefault();let r=canvas.getBoundingClientRect();zoomAt(Math.exp(-e.deltaY*.0017),e.clientX-r.left,e.clientY-r.top)},{passive:false});
92
- let down=null,moved=false;canvas.addEventListener('pointerdown',e=>{down={x:e.clientX,y:e.clientY};moved=false;canvas.setPointerCapture(e.pointerId);canvas.style.cursor='grabbing'});canvas.addEventListener('pointermove',e=>{if(!down){hoverLayers(e);return}$('#layer-tooltip').hidden=true;let dx=e.clientX-down.x,dy=e.clientY-down.y;if(Math.abs(dx)+Math.abs(dy)>2)moved=true;pan.x+=dx;pan.y+=dy;down={x:e.clientX,y:e.clientY};schedule()});canvas.addEventListener('pointerup',e=>{if(!moved){let b=canvas.getBoundingClientRect();if(!clickLayers(e.clientX-b.left,e.clientY-b.top))selectAssembly(nearest(e.clientX-b.left,e.clientY-b.top))}down=null;canvas.style.cursor='grab'});canvas.addEventListener('pointercancel',()=>down=null);canvas.addEventListener('pointerleave',()=>{$('#layer-tooltip').hidden=true;hoverBin=null;schedule()});
 
 
 
 
 
 
 
 
 
 
 
 
93
  $('#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();
94
  canvas.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect();zoomAt(3,e.clientX-b.left,e.clientY-b.top)});
95
  $('#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=()=>{selected=null;$('#selection').hidden=true;schedule()};$('#sel-focus').onclick=()=>focusAssembly(selected,100);$('#about').onclick=()=>$('#about-dialog').showModal();$('#about-close').onclick=()=>$('#about-dialog').close();
 
15
  for(const n of nodes.values())if(n.parent)nodes.get(n.parent)?.children.push(n);
16
  D.assemblies.forEach((r,index)=>{nodes.get(r[1]).own.push({row:r,index,accession:r[0],taxid:r[1],name:r[2],loci:D.loci.counts[r[0]]??null,flag:(r[4]===1?1:0)+(r[5]?2:0)});});
17
  const root=nodes.get(2759);
18
+ function count(n){n.count=n.own.length;n.existing=n.own.filter(a=>a.flag&1).length;n.refMatches=n.own.filter(a=>a.flag&2).length;for(let c of n.children){n.count+=count(c);n.existing+=c.existing;n.refMatches+=c.refMatches}n.children.sort((a,b)=>b.count-a.count||a.id-b.id);return n.count}count(root);
19
  leaves=[];
20
  function order(n){n.lo=leaves.length;for(let c of n.children)order(c);n.own.sort((a,b)=>a.accession.localeCompare(b.accession));leaves.push(...n.own);n.hi=leaves.length;n.mid=(n.lo+n.hi-1)/2;for(let a of n.own)a.i=leaves.indexOf(a,n.lo);}
21
  order(root);
 
42
  // Every real assembly has its own complete path. No generated placeholder leaves.
43
  let len=leaves.reduce((n,a)=>n+a.points.slice(1).reduce((m,p,j)=>m+stepsFor(a.points[j],p)*2*4,0),0),data=new Float32Array(len),k=0;hitOwner=new Uint32Array(len/8);
44
  const shuffled=leaves.slice().sort((a,b)=>hash(a.i)-hash(b.i));
45
+ for(const a of shuffled){a.geometryStart=k;for(let j=1;j<a.points.length;j++){
46
  let p=a.points[j-1],q=a.points[j],[c,d]=controls(p,q),prev=p,steps=stepsFor(p,q);
47
  for(let t=1;t<=steps;t++){let next=bez(p,c,d,q,t/steps);hitOwner[k/8]=a.i;for(let v of [prev,next]){data[k++]=v[0];data[k++]=v[1];data[k++]=.008+.054*Math.pow(Math.min(1,Math.hypot(...v)/R),3);data[k++]=a.flag;}prev=next;}
48
+ }a.geometryEnd=k;}
49
  hitData=data;vertexCount=k/4;const buffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buffer);gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);const loc=gl.getAttribLocation(program,'a');gl.enableVertexAttribArray(loc);gl.vertexAttribPointer(loc,4,gl.FLOAT,false,0,0);
50
  window.POC={assemblies:leaves.length,vertices:vertexCount,prior:D.meta.classification_counts['1'],refseq:D.meta.refseq_matches,data:D,leaves,nodes,select:selectAssembly,focus:focusAssembly};
51
  }
 
60
  const labels=zoom<2.5?groups:[...nodes.values()].filter(n=>['family','order','genus'].includes(n.rank)&&n.count>10&&n.count<leaves.length/zoom*.6);
61
  let placed=locusMode!=='off'&&zoom<1.6?[{x:W/2+pan.x-36,y:screen(polar(R+BAR_GAP+BAR_HEIGHT,START))[1]-14,w:72}]:[];for(const n of labels){let angle=START+SPAN*(n.mid+.5)/leaves.length;let [x,y]=screen(polar(R+BAR_HEIGHT+42,angle));if(x<35||x>W-35||y<25||y>H-25)continue;let name=n.common||n.name;if(name.length>27)name=n.name;ctx.font=(zoom<2.5?'500 13px':'500 12px')+' Inter';ctx.save();ctx.font='10px Inter';let cw=ctx.measureText(fmt(n.count)+' assemblies').width;ctx.restore();let tw=Math.max(ctx.measureText(name).width,cw),anchor=Math.cos(angle)>.25?'left':Math.cos(angle)<-.25?'right':'center';let bx=anchor==='left'?x:anchor==='right'?x-tw:x-tw/2;if(bx<8){x+=8-bx;bx=8}if(bx+tw>W-8){x-=bx+tw-W+8;bx=W-8-tw}if(placed.some(b=>bx<b.x+b.w+8&&bx+tw>b.x-8&&Math.abs(y-b.y)<32))continue;placed.push({x:bx,y,w:tw});ctx.textAlign=anchor;ctx.fillStyle='#273b30';ctx.strokeStyle='#fff';ctx.lineWidth=4;ctx.strokeText(name,x,y);ctx.fillText(name,x,y);ctx.font='10px Inter';ctx.fillStyle='#7a857f';ctx.strokeText(fmt(n.count)+' assemblies',x,y+15);ctx.fillText(fmt(n.count)+' assemblies',x,y+15);}
62
  drawAtlasIcons();
63
+ if(selected){const a=selected;ctx.strokeStyle='#15392c';ctx.lineWidth=1.8;ctx.beginPath();ctx.moveTo(...screen([hitData[a.geometryStart],hitData[a.geometryStart+1]]));for(let k=a.geometryStart+4;k<a.geometryEnd;k+=8){ctx.lineTo(...screen([hitData[k],hitData[k+1]]));}ctx.stroke();let q=screen(a.tip);ctx.fillStyle='#15392c';ctx.beginPath();ctx.arc(...q,4,0,TAU);ctx.fill();}
64
  $('#zoom-readout').textContent=zoom.toFixed(1)+'×';$('#zoomout').disabled=zoom<=1;$('#zoomin').disabled=zoom>=320;
65
  window.POC&&(window.POC.zoom=zoom);
66
  }
67
  function zoomAt(factor,x=W/2,y=H/2){$('#layer-tooltip').hidden=true;hoverBin=null;let old=zoom,n=Math.max(1,Math.min(320,zoom*factor));let ratio=n/old;pan.x=(pan.x+W/2-x)*ratio+x-W/2;pan.y=(pan.y+H/2-y)*ratio+y-H/2;zoom=n;if(zoom===1)pan={x:0,y:0};schedule()}
68
  function reset(){$('#layer-tooltip').hidden=true;hoverBin=null;zoom=1;pan={x:0,y:0};selected=null;$('#selection').hidden=true;schedule()}
69
+ function nearest(x,y,radius=7){
70
+ const [wx,wy]=world(x,y),pad=radius/(baseScale*zoom);let best=pad*pad,found=null;
71
  for(let k=0;k<hitData.length;k+=8){let ax=hitData[k],ay=hitData[k+1],bx=hitData[k+4],by=hitData[k+5];
72
  if(wx<Math.min(ax,bx)-pad||wx>Math.max(ax,bx)+pad||wy<Math.min(ay,by)-pad||wy>Math.max(ay,by)+pad)continue;
73
  let dx=bx-ax,dy=by-ay,l=dx*dx+dy*dy,t=l?Math.max(0,Math.min(1,((wx-ax)*dx+(wy-ay)*dy)/l)):0;
 
76
  }return found;
77
  }
78
  function focusAssembly(a,z=70){selected=a;zoom=z;let s=baseScale*zoom;pan={x:-a.tip[0]*s,y:-a.tip[1]*s};selectAssembly(a);schedule()}
79
+ function selectAssembly(a){if(!a)return;selected=a;let r=a.row,species=a.lineage.find(n=>n.rank==='species');$('#selection').hidden=false;$('#sel-name').textContent=a.name;$('#sel-id').textContent=a.accession;$('#sel-id').href='https://www.ncbi.nlm.nih.gov/datasets/genome/'+a.accession+'/';$('#sel-lineage').textContent=a.lineage.filter(n=>['kingdom','phylum','class','order','family','genus'].includes(n.rank)).map(n=>n.common||n.name).join(' › ');
80
  const has=mode?r[5]:r[4]===1;
81
+ $('#sel-status').innerHTML=mode?`<i class="dot ${has?'blue':'green'}"></i>${has?'In the RefSeq comparison corpus':'Outside the RefSeq comparison corpus'}`:r[4]===1?`<i class="dot blue"></i>GenBank annotation record<br><small>${esc(r[7])} · ${esc(r[9])}</small>`:'<i class="dot green"></i>No annotation listed in this GenBank snapshot';
82
+ $('#sel-data').innerHTML=(species?`<span>${esc(species.common||species.name)} · assembly comparison</span><b>${fmt(mode?species.refMatches:species.existing)} / ${fmt(species.count)} ${mode?'in RefSeq corpus':'with GenBank records'}</b>`:'')+`<span>Predicted coding loci</span><b>${a.loci===null?'Count unavailable':fmt(a.loci)}</b>`+(r[3]?`<span>Assembly length</span><b>${(r[3]/1e6).toLocaleString('en-US',{maximumFractionDigits:2})} Mb</b>`:'')+(r[6]?`<span>RefSeq counterpart</span><b>${esc(r[6])}</b>`:'')+(mode&&r[4]===1?`<span>GenBank annotation provider</span><b>${esc(r[7])}</b>`:'');
83
  $('#sel-loci').hidden=a.accession!=='GCA_000001405.29';schedule()}
84
+ 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)selectAssembly(selected);schedule()}
85
  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.';}
86
  function geneModal(){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="#edf0ed"/><text x="${x(p)}" y="24" text-anchor="middle">${p/1e6} Mb</text>`)}
87
  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="#81d8ae"/>`);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="${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.';
 
89
  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();makeLocusBins();resize();updateLegend();$('#loading').remove();$('#shown').textContent=fmt(leaves.length);$('#taxa-shown').textContent=fmt(D.meta.taxa);$('#search').disabled=false;
90
  new ResizeObserver(resize).observe(stage);
91
  canvas.addEventListener('wheel',e=>{e.preventDefault();let r=canvas.getBoundingClientRect();zoomAt(Math.exp(-e.deltaY*.0017),e.clientX-r.left,e.clientY-r.top)},{passive:false});
92
+ let down=null,moved=false,hoverPending=false,hoverEvent=null;
93
+ const queueHover=e=>{hoverEvent=e;if(hoverPending)return;hoverPending=true;requestAnimationFrame(()=>{hoverPending=false;if(!down)hoverLayers(hoverEvent)})};
94
+ canvas.addEventListener('pointerdown',e=>{down={x:e.clientX,y:e.clientY};moved=false;canvas.setPointerCapture(e.pointerId)});
95
+ canvas.addEventListener('pointermove',e=>{
96
+ if(!down){queueHover(e);return}
97
+ let dx=e.clientX-down.x,dy=e.clientY-down.y;
98
+ if(!moved&&Math.hypot(dx,dy)<4)return;
99
+ moved=true;canvas.style.cursor='grabbing';$('#layer-tooltip').hidden=true;
100
+ pan.x+=dx;pan.y+=dy;down={x:e.clientX,y:e.clientY};schedule();
101
+ });
102
+ canvas.addEventListener('pointerup',e=>{if(!down)return;if(!moved){let b=canvas.getBoundingClientRect();if(!clickLayers(e.clientX-b.left,e.clientY-b.top))selectAssembly(nearest(e.clientX-b.left,e.clientY-b.top))}down=null;hoverLayers(e)});
103
+ canvas.addEventListener('pointercancel',()=>{down=null;canvas.style.cursor='default'});
104
+ canvas.addEventListener('pointerleave',()=>{$('#layer-tooltip').hidden=true;hoverBin=null;if(!down)canvas.style.cursor='default';schedule()});
105
  $('#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();
106
  canvas.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect();zoomAt(3,e.clientX-b.left,e.clientY-b.top)});
107
  $('#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=()=>{selected=null;$('#selection').hidden=true;schedule()};$('#sel-focus').onclick=()=>focusAssembly(selected,100);$('#about').onclick=()=>$('#about-dialog').showModal();$('#about-close').onclick=()=>$('#about-dialog').close();
carbon-a-strand-poc.png CHANGED

Git LFS Details

  • SHA256: 29e293a77927226c3f523e7a552f16cd7f868adca757003ba3c76a29a3ff8d59
  • Pointer size: 132 Bytes
  • Size of remote file: 2.88 MB

Git LFS Details

  • SHA256: a0eb7585f4ad577578bbc4a008e61c3d6e39ee5b0361c5259a64507c6ac357ce
  • Pointer size: 132 Bytes
  • Size of remote file: 2.99 MB
carbon-a-strand-source.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e48117ae3d5d95dd78e08ac29ad37d431d0429095b423bf4d6c10d56e9afadf0
3
- size 15045655
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6367121933f23b07415c4327b9c45330c73141452bf45e76107232c8c46e2736
3
+ size 15048145
check_layers.py CHANGED
@@ -10,15 +10,20 @@ with sync_playwright() as p:
10
  page.wait_for_timeout(300)
11
  stats=page.evaluate('({available:POC.locusAvailable,total:POC.locusTotal,sum:locusBins.reduce((n,b)=>n+b.value,0),known:locusBins.reduce((n,b)=>n+b.known,0),bins:locusBins.length,icons:iconImages.size,drawn:POC.iconsDrawn.length})')
12
  assert stats['available']==stats['known'];assert stats['total']==stats['sum'];assert stats['icons']==len(json.loads((R/'data/atlas-icons.json').read_text())['images']);assert stats['drawn']>=5
 
 
 
 
 
13
  print('initial',stats,flush=True)
14
  # The annulus is clickable and switches to individual bars without losing counts.
15
- pos=page.evaluate('''()=>{let b=locusBins.find(b=>b.known===50),p=screen(polar(R+BAR_GAP+barHeight(b.value)/2,START+SPAN*(b.mid+.5)/leaves.length)),r=stage.getBoundingClientRect();return {x:r.x+p[0],y:r.y+p[1]}}''')
16
  page.mouse.click(pos['x'],pos['y']);page.wait_for_timeout(150);assert page.locator('#locus-mode').input_value()=='raw';assert page.evaluate('locusBins.length')==33588
17
  assert page.evaluate('locusBins.reduce((n,b)=>n+b.value,0)')==stats['total'];assert page.evaluate('locusBins.filter(b=>!b.known).length')==33588-stats['available']
18
  page.screenshot(path=str(R/'locus-detail.png'))
19
- page.locator('#reset').click();page.locator('#locus-mode').select_option('200');assert page.evaluate('locusBins.reduce((n,b)=>n+b.value,0)')==stats['total'];page.locator('#locus-mode').select_option('50');page.wait_for_timeout(100)
20
  hit=page.evaluate('''()=>{let h=iconHits.find(h=>h.n.count<6000),r=stage.getBoundingClientRect();return {x:r.x+h.x,y:r.y+h.y}}''');page.mouse.move(hit['x'],hit['y']);assert page.locator('#layer-tooltip').is_visible();page.mouse.click(hit['x'],hit['y']);page.wait_for_timeout(100);assert page.evaluate('POC.zoom')>1
21
- page.locator('#reset').click();page.locator('#show-icons').uncheck();page.wait_for_timeout(100);assert page.evaluate('iconHits.length')==0;page.locator('#show-icons').check();page.locator('#locus-mode').select_option('off');page.wait_for_timeout(100);assert page.evaluate('locusMode')=='off';page.locator('#locus-mode').select_option('50')
22
  page.locator('#about').click();page.locator('#credits-open').click();assert page.locator('#icon-credits tbody tr').count()==stats['icons'];page.locator('#credits-close').click();page.locator('#about-close').click();assert not errors,errors
23
  page.screenshot(path=str(R/'loci-overview.png'));page.locator('#locus-mode').select_option('raw');page.screenshot(path=str(R/'loci-raw.png'))
24
  print('all layer checks passed',flush=True);b.close()
 
10
  page.wait_for_timeout(300)
11
  stats=page.evaluate('({available:POC.locusAvailable,total:POC.locusTotal,sum:locusBins.reduce((n,b)=>n+b.value,0),known:locusBins.reduce((n,b)=>n+b.known,0),bins:locusBins.length,icons:iconImages.size,drawn:POC.iconsDrawn.length})')
12
  assert stats['available']==stats['known'];assert stats['total']==stats['sum'];assert stats['icons']==len(json.loads((R/'data/atlas-icons.json').read_text())['images']);assert stats['drawn']>=5
13
+ assert stats['bins']==6718
14
+ assert page.locator('#locus-mode').input_value()=='5'
15
+ assert page.evaluate('POC.nodes.get(9606).count')==1412
16
+ assert page.evaluate('POC.nodes.get(9606).refMatches')==2
17
+ assert page.evaluate('POC.nodes.get(9606).existing')==4
18
  print('initial',stats,flush=True)
19
  # The annulus is clickable and switches to individual bars without losing counts.
20
+ pos=page.evaluate('''()=>{let b=locusBins.find(b=>b.known===5),p=screen(polar(R+BAR_GAP+barHeight(b.value)/2,START+SPAN*(b.mid+.5)/leaves.length)),r=stage.getBoundingClientRect();return {x:r.x+p[0],y:r.y+p[1]}}''')
21
  page.mouse.click(pos['x'],pos['y']);page.wait_for_timeout(150);assert page.locator('#locus-mode').input_value()=='raw';assert page.evaluate('locusBins.length')==33588
22
  assert page.evaluate('locusBins.reduce((n,b)=>n+b.value,0)')==stats['total'];assert page.evaluate('locusBins.filter(b=>!b.known).length')==33588-stats['available']
23
  page.screenshot(path=str(R/'locus-detail.png'))
24
+ page.locator('#reset').click();page.locator('#locus-mode').select_option('200');assert page.evaluate('locusBins.reduce((n,b)=>n+b.value,0)')==stats['total'];page.locator('#locus-mode').select_option('5');page.wait_for_timeout(100)
25
  hit=page.evaluate('''()=>{let h=iconHits.find(h=>h.n.count<6000),r=stage.getBoundingClientRect();return {x:r.x+h.x,y:r.y+h.y}}''');page.mouse.move(hit['x'],hit['y']);assert page.locator('#layer-tooltip').is_visible();page.mouse.click(hit['x'],hit['y']);page.wait_for_timeout(100);assert page.evaluate('POC.zoom')>1
26
+ page.locator('#reset').click();page.locator('#show-icons').uncheck();page.wait_for_timeout(100);assert page.evaluate('iconHits.length')==0;page.locator('#show-icons').check();page.locator('#locus-mode').select_option('off');page.wait_for_timeout(100);assert page.evaluate('locusMode')=='off';page.locator('#locus-mode').select_option('5')
27
  page.locator('#about').click();page.locator('#credits-open').click();assert page.locator('#icon-credits tbody tr').count()==stats['icons'];page.locator('#credits-close').click();page.locator('#about-close').click();assert not errors,errors
28
  page.screenshot(path=str(R/'loci-overview.png'));page.locator('#locus-mode').select_option('raw');page.screenshot(path=str(R/'loci-raw.png'))
29
  print('all layer checks passed',flush=True);b.close()
check_selection.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import sys,json
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='/opt/pw-browsers/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell',headless=True,args=['--no-sandbox','--enable-webgl','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader'])
8
+ page=b.new_page(viewport={'width':1500,'height':1050},device_scale_factor=1);errors=[];page.on('pageerror',lambda e:errors.append(str(e)))
9
+ page.goto((R/'index.html').as_uri());page.wait_for_function('window.POC && !document.querySelector("#loading")')
10
+ box=page.locator('#stage').bounding_box();cx=box['x']+box['width']/2;cy=box['y']+box['height']/2
11
+ page.mouse.move(cx,cy);page.wait_for_timeout(100);assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')=='default'
12
+ page.locator('#detail').click();page.locator('#sel-close').click();page.mouse.move(cx,cy);page.wait_for_timeout(150);assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')=='pointer'
13
+ page.mouse.down();assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')!='grabbing';page.mouse.up();assert page.locator('#sel-id').inner_text()=='GCA_000001405.29'
14
+ assert '4 / 1,412' in page.locator('#sel-data').inner_text();page.locator('#scope').select_option('1');assert '2 / 1,412' in page.locator('#sel-data').inner_text()
15
+ # Inspect the path actually sent to Canvas, compared with the GPU's sampled line.
16
+ result=page.evaluate('''()=>{let path=[],observed=null;const oldBegin=ctx.beginPath,oldMove=ctx.moveTo,oldLine=ctx.lineTo,oldStroke=ctx.stroke;ctx.beginPath=function(){path=[];return oldBegin.call(this)};ctx.moveTo=function(x,y){path.push([x,y]);return oldMove.call(this,x,y)};ctx.lineTo=function(x,y){path.push([x,y]);return oldLine.call(this,x,y)};ctx.stroke=function(){if(this.strokeStyle==='#15392c')observed=path.slice();return oldStroke.call(this)};draw();ctx.beginPath=oldBegin;ctx.moveTo=oldMove;ctx.lineTo=oldLine;ctx.stroke=oldStroke;let a=selected,expected=[screen([hitData[a.geometryStart],hitData[a.geometryStart+1]])];for(let k=a.geometryStart+4;k<a.geometryEnd;k+=8)expected.push(screen([hitData[k],hitData[k+1]]));return {count:observed.length,expected:expected.length,error:Math.max(...observed.map((v,i)=>Math.hypot(v[0]-expected[i][0],v[1]-expected[i][1])))}}''')
17
+ assert result['count']==result['expected'] and result['error']<.001,result
18
+ print('highlight agreement',result,flush=True)
19
+ page.screenshot(path=str(R/'selection-aligned.png'))
20
+ page.mouse.move(cx-100,cy+100);page.mouse.down();page.mouse.move(cx-98,cy+100);assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')!='grabbing';page.mouse.move(cx-60,cy+120);assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')=='grabbing';page.mouse.up();assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')!='grabbing'
21
+ assert page.locator('#sel-id').inner_text()=='GCA_000001405.29'
22
+ timing=page.evaluate('''()=>{let t=performance.now();for(let i=0;i<5;i++)nearest(W/2+i,H/2,3);return (performance.now()-t)/5}''');print('mean hit-test ms',round(timing,1),'errors',errors,flush=True);assert not errors;b.close()
detail.png CHANGED

Git LFS Details

  • SHA256: 684ebc563d6b054102a48be2f6c510d40a7771ac4812030cc3baa7bf8113f381
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB

Git LFS Details

  • SHA256: 0622a8eff0ad752ac72272b3143fdb440606b1aaf22f9d620a677b9f8e81fe70
  • Pointer size: 131 Bytes
  • Size of remote file: 160 kB
export_still.py CHANGED
@@ -8,6 +8,6 @@ with sync_playwright() as p:
8
  page=b.new_page(viewport={'width':1200,'height':1420},device_scale_factor=2)
9
  page.goto((R/'index.html').as_uri());page.wait_for_function('window.POC && !document.querySelector("#loading")')
10
  page.add_style_tag(content='''header{height:90px;padding:0 50px}.brand{font-size:29px}.header-right button{display:none}main{display:block;height:1330px;min-height:1330px}aside,.topbar,.controls,.help,#selection{display:none!important}.stage-wrap{height:100%}#stage{inset:132px 22px 120px}.poster-heading{position:absolute;left:50px;top:35px}.poster-heading h1{font-size:39px;letter-spacing:-1.5px;margin-bottom:11px}.poster-heading p{font-size:15px;color:#6e7b73;margin:0}.poster-footer{position:absolute;bottom:20px;left:50px;right:50px;border-top:1px solid #e6ece8;padding-top:22px}.poster-key{display:flex;justify-content:space-between;font-size:16px;font-weight:500}.poster-key span{display:flex;align-items:center;gap:12px}.poster-key i{width:34px;height:2px;background:#00b86b;display:inline-block}.poster-key i.blue{background:#4167ec}.poster-footer p{font-size:11px;line-height:1.7;color:#6e7b73;margin-top:18px}''')
11
- page.evaluate('''() => {document.querySelector('.stage-wrap').insertAdjacentHTML('beforeend',`<div class="poster-heading"><h1>One assembly. One strand.</h1><p>33,588 assemblies · outer bars sum predicted coding loci across 50 adjacent strands.</p></div><div class="poster-footer"><div class="poster-key"><span><i></i>29,350 with no annotation record listed</span><span><i class="blue"></i>4,238 with existing annotation records</span></div><p>Proof of concept: 33,588 of the release’s 48,167 assemblies. Colours reflect GenBank annotation records in the 7 October 2026 snapshot.<br>Outer bars sum available loci across up to 50 assembly strands; grey base marks show missing counts. Branch length is not evolutionary time.<br>Genome Atlas silhouettes: PhyloPic. Original image credits and licences are included in the interactive preview.</p></div>`)}''')
12
  page.evaluate("document.querySelector('.poster-heading p').textContent='33,588 assemblies · '+shortCount(POC.locusTotal)+' predicted loci across '+fmt(POC.locusAvailable)+' assemblies with counts.'");page.wait_for_timeout(800);page.screenshot(path=str(R/'carbon-a-strand-poc.png'));b.close()
13
  print(R/'carbon-a-strand-poc.png')
 
8
  page=b.new_page(viewport={'width':1200,'height':1420},device_scale_factor=2)
9
  page.goto((R/'index.html').as_uri());page.wait_for_function('window.POC && !document.querySelector("#loading")')
10
  page.add_style_tag(content='''header{height:90px;padding:0 50px}.brand{font-size:29px}.header-right button{display:none}main{display:block;height:1330px;min-height:1330px}aside,.topbar,.controls,.help,#selection{display:none!important}.stage-wrap{height:100%}#stage{inset:132px 22px 120px}.poster-heading{position:absolute;left:50px;top:35px}.poster-heading h1{font-size:39px;letter-spacing:-1.5px;margin-bottom:11px}.poster-heading p{font-size:15px;color:#6e7b73;margin:0}.poster-footer{position:absolute;bottom:20px;left:50px;right:50px;border-top:1px solid #e6ece8;padding-top:22px}.poster-key{display:flex;justify-content:space-between;font-size:16px;font-weight:500}.poster-key span{display:flex;align-items:center;gap:12px}.poster-key i{width:34px;height:2px;background:#00b86b;display:inline-block}.poster-key i.blue{background:#4167ec}.poster-footer p{font-size:11px;line-height:1.7;color:#6e7b73;margin-top:18px}''')
11
+ page.evaluate('''() => {document.querySelector('.stage-wrap').insertAdjacentHTML('beforeend',`<div class="poster-heading"><h1>One assembly. One strand.</h1><p>33,588 assemblies · outer bars sum predicted coding loci across 5 adjacent strands.</p></div><div class="poster-footer"><div class="poster-key"><span><i></i>29,350 with no GenBank annotation record</span><span><i class="blue"></i>4,238 with GenBank annotation records</span></div><p>Proof of concept: 33,588 of the release’s 48,167 assemblies. Colours reflect GenBank annotation records in the 7 October 2026 snapshot.<br>Outer bars sum available loci across up to 5 assembly strands; grey base marks show missing counts. Branch length is not evolutionary time.<br>Genome Atlas silhouettes: PhyloPic. Original image credits and licences are included in the interactive preview.</p></div>`)}''')
12
  page.evaluate("document.querySelector('.poster-heading p').textContent='33,588 assemblies · '+shortCount(POC.locusTotal)+' predicted loci across '+fmt(POC.locusAvailable)+' assemblies with counts.'");page.wait_for_timeout(800);page.screenshot(path=str(R/'carbon-a-strand-poc.png'));b.close()
13
  print(R/'carbon-a-strand-poc.png')
genes.png CHANGED

Git LFS Details

  • SHA256: 0504d030fbc01c53c528bd9a052b48d4fae54e1e6735df9a97162e3f11092d1f
  • Pointer size: 131 Bytes
  • Size of remote file: 284 kB

Git LFS Details

  • SHA256: 7e28a78c1da986fc923edf602647685358098127a918e1db1d95740ca79dad92
  • Pointer size: 131 Bytes
  • Size of remote file: 309 kB
index.html CHANGED
The diff for this file is too large to render. See raw diff
 
layers.js CHANGED
@@ -1,10 +1,10 @@
1
  // Measured annotation counts aligned to the same assembly ordering as the tree.
2
- let locusMode='50',locusBins=[],locusMax=1,iconImages=new Map(),iconHits=[],hoverBin=null,showIcons=true;
3
  const BAR_GAP=14,BAR_HEIGHT=174;
4
  function shortCount(n){return n>=1e6?(n/1e6).toLocaleString('en-US',{maximumFractionDigits:2})+'M':n>=1000?(n/1000).toLocaleString('en-US',{maximumFractionDigits:1})+'k':fmt(n)}
5
  function niceCeiling(n){if(!n)return 1;let p=10**Math.floor(Math.log10(n)),f=n/p;return (f<=1?1:f<=2?2:f<=5?5:10)*p}
6
  function makeLocusBins(){
7
- const size=locusMode==='raw'?1:Number(locusMode)||50;locusBins=[];
8
  for(let lo=0;lo<leaves.length;lo+=size){let hi=Math.min(leaves.length,lo+size),known=0,value=0;
9
  for(let i=lo;i<hi;i++){let v=leaves[i].loci;if(v!==null){known++;value+=v}}
10
  locusBins.push({lo,hi,known,value,mid:(lo+hi-1)/2});
@@ -54,7 +54,6 @@ function drawAtlasIcons(){
54
  if(x<25||x>W-25||y<30||y>H-30)continue;
55
  if(iconHits.some(h=>h.uid===uid||Math.hypot(h.x-x,h.y-y)<57))continue;
56
  const ratio=im.naturalWidth/im.naturalHeight||D.icons.images[uid].ratio||1,iw=ratio>1?size:size*ratio,ih=ratio>1?size/ratio:size;
57
- ctx.fillStyle='#ffffff';ctx.beginPath();ctx.arc(x,y,size*.67,0,TAU);ctx.fill();
58
  ctx.drawImage(im,x-iw/2,y-ih/2,iw,ih);iconHits.push({x,y,r:size*.8,n,uid});
59
  if(iconHits.length>=max)break;
60
  }
@@ -63,9 +62,9 @@ function drawAtlasIcons(){
63
  function hitIcon(x,y){return iconHits.find(h=>Math.hypot(h.x-x,h.y-y)<h.r)}
64
  function hitBar(x,y){if(locusMode==='off')return null;let p=world(x,y),rad=Math.hypot(...p);if(rad<R+2||rad>R+BAR_GAP+BAR_HEIGHT+8)return null;let angle=Math.atan2(p[1],p[0]);while(angle<START)angle+=TAU;if(angle>START+SPAN)return null;let i=Math.floor((angle-START)/SPAN*leaves.length),size=locusMode==='raw'?1:Number(locusMode);return locusBins[Math.floor(i/size)]||null}
65
  function hoverLayers(e){if($('#gene-dialog').open||$('#about-dialog').open)return;let box=stage.getBoundingClientRect(),x=e.clientX-box.left,y=e.clientY-box.top,ic=hitIcon(x,y),bin=hitBar(x,y),tip=$('#layer-tooltip');
66
- if(!ic&&!bin){tip.hidden=true;if(hoverBin){hoverBin=null;schedule()}canvas.style.cursor='grab';return}
67
  canvas.style.cursor='pointer';
68
- if(ic){tip.innerHTML=`<b>${esc(ic.n.common||ic.n.name)}</b><span>${esc(ic.n.name)} · ${fmt(ic.n.count)} assemblies</span><small>Click to explore this branch</small>`}
69
  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()}}
70
  tip.hidden=false;tip.style.left=Math.max(8,Math.min(W-270,x+16))+14+'px';tip.style.top=Math.max(65,Math.min(H-75,y+15))+64+'px';
71
  }
 
1
  // Measured annotation counts aligned to the same assembly ordering as the tree.
2
+ let locusMode='5',locusBins=[],locusMax=1,iconImages=new Map(),iconHits=[],hoverBin=null,showIcons=true;
3
  const BAR_GAP=14,BAR_HEIGHT=174;
4
  function shortCount(n){return n>=1e6?(n/1e6).toLocaleString('en-US',{maximumFractionDigits:2})+'M':n>=1000?(n/1000).toLocaleString('en-US',{maximumFractionDigits:1})+'k':fmt(n)}
5
  function niceCeiling(n){if(!n)return 1;let p=10**Math.floor(Math.log10(n)),f=n/p;return (f<=1?1:f<=2?2:f<=5?5:10)*p}
6
  function makeLocusBins(){
7
+ const size=locusMode==='raw'?1:Number(locusMode)||5;locusBins=[];
8
  for(let lo=0;lo<leaves.length;lo+=size){let hi=Math.min(leaves.length,lo+size),known=0,value=0;
9
  for(let i=lo;i<hi;i++){let v=leaves[i].loci;if(v!==null){known++;value+=v}}
10
  locusBins.push({lo,hi,known,value,mid:(lo+hi-1)/2});
 
54
  if(x<25||x>W-25||y<30||y>H-30)continue;
55
  if(iconHits.some(h=>h.uid===uid||Math.hypot(h.x-x,h.y-y)<57))continue;
56
  const ratio=im.naturalWidth/im.naturalHeight||D.icons.images[uid].ratio||1,iw=ratio>1?size:size*ratio,ih=ratio>1?size/ratio:size;
 
57
  ctx.drawImage(im,x-iw/2,y-ih/2,iw,ih);iconHits.push({x,y,r:size*.8,n,uid});
58
  if(iconHits.length>=max)break;
59
  }
 
62
  function hitIcon(x,y){return iconHits.find(h=>Math.hypot(h.x-x,h.y-y)<h.r)}
63
  function hitBar(x,y){if(locusMode==='off')return null;let p=world(x,y),rad=Math.hypot(...p);if(rad<R+2||rad>R+BAR_GAP+BAR_HEIGHT+8)return null;let angle=Math.atan2(p[1],p[0]);while(angle<START)angle+=TAU;if(angle>START+SPAN)return null;let i=Math.floor((angle-START)/SPAN*leaves.length),size=locusMode==='raw'?1:Number(locusMode);return locusBins[Math.floor(i/size)]||null}
64
  function hoverLayers(e){if($('#gene-dialog').open||$('#about-dialog').open)return;let box=stage.getBoundingClientRect(),x=e.clientX-box.left,y=e.clientY-box.top,ic=hitIcon(x,y),bin=hitBar(x,y),tip=$('#layer-tooltip');
65
+ if(!ic&&!bin){tip.hidden=true;if(hoverBin){hoverBin=null;schedule()}canvas.style.cursor=nearest(x,y,3)?'pointer':'default';return}
66
  canvas.style.cursor='pointer';
67
+ 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>`}
68
  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()}}
69
  tip.hidden=false;tip.style.left=Math.max(8,Math.min(W-270,x+16))+14+'px';tip.style.top=Math.max(65,Math.min(H-75,y+15))+64+'px';
70
  }
loci-overview.png CHANGED

Git LFS Details

  • SHA256: f100b2a09da2a43db9843cffe4ee42909359220da7d692e461e9d0f6fd8575fe
  • Pointer size: 131 Bytes
  • Size of remote file: 692 kB

Git LFS Details

  • SHA256: 50e2c8d6ed44bb906c69abaaa6cfffe7ded57253ed831d68d32e3b6e99122e4b
  • Pointer size: 131 Bytes
  • Size of remote file: 710 kB
loci-raw.png CHANGED

Git LFS Details

  • SHA256: c8f7576e767a6e5b5b41b8efeb32cc113e85946e705bf4a63ba55d9ff4a1c131
  • Pointer size: 131 Bytes
  • Size of remote file: 695 kB

Git LFS Details

  • SHA256: 2445d3301d33c5c90b7dac26464a2cbebe162336c4cd2861cac04d20ef6cfff5
  • Pointer size: 131 Bytes
  • Size of remote file: 710 kB
locus-detail.png CHANGED

Git LFS Details

  • SHA256: 6049887579f914806b80a4b3e5f100f543fb22a654bfeef8462bd16ae696185c
  • Pointer size: 131 Bytes
  • Size of remote file: 357 kB

Git LFS Details

  • SHA256: 587502126112eb6fd7674eb240e6ec09bdf4fbe2c3b6af2e79c8136ec2a13759
  • Pointer size: 131 Bytes
  • Size of remote file: 183 kB
overview.png CHANGED

Git LFS Details

  • SHA256: d2ef01db280db99ef0dfeba0e288166f7dcf4b429c38aee9483fab4c5292a91e
  • Pointer size: 131 Bytes
  • Size of remote file: 631 kB

Git LFS Details

  • SHA256: c43528da9f3287c7ff9c2325a9eab02498177fff9eb476f97dfd404ae132403c
  • Pointer size: 131 Bytes
  • Size of remote file: 647 kB
refseq.png CHANGED

Git LFS Details

  • SHA256: e7f3822bf88c57eef6d4b77e336838f5c73f4ba27e6cc51690aa0ce5cbc7035e
  • Pointer size: 131 Bytes
  • Size of remote file: 407 kB

Git LFS Details

  • SHA256: dc4634dda4cf8da38d80ac911fd68911f942c4c064fbf0f7adb1c1c434ab55e8
  • Pointer size: 131 Bytes
  • Size of remote file: 421 kB
selection-aligned.png ADDED

Git LFS Details

  • SHA256: 18d7899fd94146a9e2027253183def5a930b9d5a020cd2b86e65de4a484bfc81
  • Pointer size: 131 Bytes
  • Size of remote file: 160 kB
template.html CHANGED
@@ -1,7 +1,7 @@
1
  <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Carbon-A — Assembly strands</title><style>
2
  __FONTS__
3
- :root{--green:#00b86b;--blue:#4167ec;--ink:#14221c;--muted:#6e7b73;--rule:#e6ece8}*{box-sizing:border-box}body{margin:0;color:var(--ink);background:#fff;font-family:Inter,Arial,sans-serif;font-size:13px}button,input,select{font:inherit}button,a,input,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer;color:inherit;background:#fff;border:1px solid var(--rule);padding:9px 13px}button:hover{border-color:#80978a;background:#f6faf7}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}header{height:87px;padding:0 40px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--rule)}.brand{font-family:Manrope,Arial,sans-serif;font-weight:800;font-size:26px;letter-spacing:-1px}.brand em{font-style:normal;color:var(--green)}.tag{font-size:10px;letter-spacing:1.3px;color:var(--muted);text-transform:uppercase}.header-right{display:flex;gap:24px;align-items:center}main{display:grid;grid-template-columns:320px 1fr;height:calc(100vh - 87px);min-height:720px}aside{padding:34px 28px 20px 40px;position:relative;z-index:3;border-right:1px solid var(--rule);overflow-y:auto}h1{font-family:Manrope,Arial,sans-serif;font-size:32px;letter-spacing:-1.5px;line-height:1.12;font-weight:700;margin:0 0 16px}.intro{font-size:13px;line-height:1.6;color:var(--muted);margin:0 0 24px}.release{border-top:1px solid var(--rule);padding-top:19px;margin-bottom:24px}.release .big{font-family:Manrope;font-size:38px;letter-spacing:-1.7px;font-weight:700;margin-top:10px;line-height:1.1}.release .big em{font-style:normal;color:var(--green)}.release .sub{font-size:12px;color:var(--muted);margin-top:5px}.stats{display:grid;grid-template-columns:1fr 1fr;gap:14px 18px;margin-top:20px}.stats b{font-family:Manrope;font-size:21px;display:block;letter-spacing:-.6px}.stats span{font-size:10px;color:var(--muted)}.stat-wide{grid-column:1/-1}.legend{border-top:1px solid var(--rule);padding-top:20px}.legend select{width:100%;border:0;border-bottom:1px solid var(--rule);padding:7px 0 9px;margin:6px 0 12px;background:#fff;font-size:12px;font-weight:500}.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{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(--blue)}.subset{font-size:10px;line-height:1.6;color:var(--muted);margin-top:20px;padding-top:16px;border-top:1px solid var(--rule)}.subset b{color:#3a4d42;font-weight:500}.quick{display:flex;gap:6px;margin:20px 0 6px}.quick button{font-size:11px;padding:8px 10px}.stage-wrap{position:relative;min-width:0;overflow:hidden}#stage{position:absolute;inset:64px 14px 44px}canvas{position:absolute;width:100%;height:100%;inset:0}#tree{cursor:grab;touch-action:none}#overlay{pointer-events:none}.topbar{position:absolute;top:18px;left:25px;right:25px;display:flex;align-items:center;justify-content:space-between;gap:10px;z-index:4}.searchbox{position:relative;width:280px}#search{width:100%;padding:10px 12px;border:1px solid var(--rule);background:#fff;font-size:11px}#results{position:absolute;top:39px;width:100%;background:#fff;box-shadow:0 10px 30px #10291a0d;font-size:11px}#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}.one-line{font-size:11px;color:var(--muted)}.controls{display:flex;align-items:center;gap:4px;position:absolute;right:26px;bottom:21px;z-index:3}.controls button{padding:7px 12px}.controls span{font-size:10px;padding:0 7px;min-width:42px;text-align:center;color:var(--muted)}.help{position:absolute;bottom:25px;left:25px;font-size:10px;line-height:1.7;color:var(--muted);pointer-events:none}.help b{color:#435a4c;font-weight:500}#selection{position:absolute;right:22px;top:69px;width:295px;background:#fff;border:1px solid #dfe8e2;box-shadow:0 8px 40px #2035280c;z-index:5;padding:23px}#sel-close{position:absolute;top:6px;right:8px;border:0;background:transparent;font-size:20px;padding:0 4px;color:var(--muted)}#sel-name{font-size:18px;letter-spacing:-.5px;font-family:Manrope;margin:0 0 6px;line-height:1.25}#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;grid-template-columns:1fr;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}#loading{position:absolute;inset:0;display:grid;place-content:center;background:white;color:var(--muted);z-index:10}dialog{border:1px solid var(--rule);padding:36px;max-width:calc(100vw - 40px);width:1000px;color:var(--ink)}dialog::backdrop{background:#10271930;backdrop-filter:blur(3px)}dialog h2{font-family:Manrope;font-size:28px;letter-spacing:-1px;margin:0 0 7px}dialog p{font-size:13px;line-height:1.7;color:var(--muted)}.dialog-close{float:right;border:0;font-size:22px;padding:0 4px}.gene-label{font-size:11px;margin-top:28px;color:var(--muted)}#gene-svg{width:100%;height:auto;font:11px Inter;fill:#718279;margin:16px 0}.gene-notes{border-top:1px solid var(--rule);padding-top:15px;font-size:11px}#about-dialog{width:680px}#about-dialog p{font-size:12px}#about-dialog ul{padding-left:18px;font-size:12px;line-height:1.9;color:var(--muted)}
4
  @media(min-width:1600px){main{grid-template-columns:365px 1fr}aside{padding:40px}.release .big{font-size:44px}h1{font-size:38px}}
5
  @media(max-width:850px){header{padding:0 20px;height:72px}.brand{font-size:22px}.header-right .tag{display:none}main{height:auto;min-height:0;display:flex;flex-direction:column}aside{padding:25px 22px;border-right:0;border-bottom:1px solid var(--rule);overflow:visible}h1{font-size:30px;max-width:420px}.intro{max-width:520px;margin-bottom:18px}.release{display:none}.legend{padding-top:10px;border-top:0;max-width:520px}.legend .tag{display:none}.scope-note{max-width:500px}.subset{margin-top:15px;padding-top:10px}.quick{margin-top:15px}.stage-wrap{height:740px}.topbar{left:20px;right:20px}.one-line{display:none}#stage{inset:68px 0 85px}.help{left:20px;bottom:62px}.controls{left:20px;right:auto}#selection{right:15px;top:68px;width:275px}dialog{padding:25px}.header-right{gap:10px}}
6
  .subset{margin:0 0 14px;padding:0;border-top:0}.locus-controls{border-top:1px solid var(--rule);padding-top:15px;margin:16px 0 20px}.locus-controls select{width:100%;padding:8px 0;border:0;border-bottom:1px solid var(--rule);background:white;font-size:12px}.locus-controls p{font-size:10px;line-height:1.5;color:var(--muted);margin:8px 0}.locus-controls strong{display:block;font-size:11px;font-weight:500;margin-top:10px}.icon-toggle{margin-top:12px}.icon-toggle input{accent-color:var(--green)}#layer-tooltip{position:absolute;z-index:8;width:250px;pointer-events:none;padding:13px 15px;background:#fff;border:1px solid #dbe5de;box-shadow:0 5px 25px #142a1b0c;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}#credits-dialog{width:740px}#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{color:#406651}.release{padding-top:13px;margin-bottom:17px}.release .big{font-size:31px;margin-top:7px}.stats{gap:10px;margin-top:12px}.stats .stat-wide{grid-column:auto}.stats b{font-size:18px}.intro{margin-bottom:17px}h1{font-size:30px}
7
- </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></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="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">Highlight existing annotations</label><select id="scope"><option value="0">NCBI 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 existing annotations</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><span class="one-line">Real assemblies · NCBI taxonomy</span></div><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="overlay" 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><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><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section><div class="help"><b>Scroll to zoom · Drag to pan · Click an icon or bar</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: 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>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>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, unmodified images from PhyloPic, reused with the atlas’s taxonomic assignments. Each image’s source page and licence are linked below.</p><div id="icon-credits"></div></dialog><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>
 
1
  <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Carbon-A — Assembly strands</title><style>
2
  __FONTS__
3
+ :root{--green:#00b86b;--blue:#4167ec;--ink:#14221c;--muted:#6e7b73;--rule:#e6ece8}*{box-sizing:border-box}body{margin:0;color:var(--ink);background:#fff;font-family:Inter,Arial,sans-serif;font-size:13px}button,input,select{font:inherit}button,a,input,select{-webkit-tap-highlight-color:transparent}button{cursor:pointer;color:inherit;background:#fff;border:1px solid var(--rule);padding:9px 13px}button:hover{border-color:#80978a;background:#f6faf7}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}header{height:87px;padding:0 40px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--rule)}.brand{font-family:Manrope,Arial,sans-serif;font-weight:800;font-size:26px;letter-spacing:-1px}.brand em{font-style:normal;color:var(--green)}.tag{font-size:10px;letter-spacing:1.3px;color:var(--muted);text-transform:uppercase}.header-right{display:flex;gap:24px;align-items:center}main{display:grid;grid-template-columns:320px 1fr;height:calc(100vh - 87px);min-height:720px}aside{padding:34px 28px 20px 40px;position:relative;z-index:3;border-right:1px solid var(--rule);overflow-y:auto}h1{font-family:Manrope,Arial,sans-serif;font-size:32px;letter-spacing:-1.5px;line-height:1.12;font-weight:700;margin:0 0 16px}.intro{font-size:13px;line-height:1.6;color:var(--muted);margin:0 0 24px}.release{border-top:1px solid var(--rule);padding-top:19px;margin-bottom:24px}.release .big{font-family:Manrope;font-size:38px;letter-spacing:-1.7px;font-weight:700;margin-top:10px;line-height:1.1}.release .big em{font-style:normal;color:var(--green)}.release .sub{font-size:12px;color:var(--muted);margin-top:5px}.stats{display:grid;grid-template-columns:1fr 1fr;gap:14px 18px;margin-top:20px}.stats b{font-family:Manrope;font-size:21px;display:block;letter-spacing:-.6px}.stats span{font-size:10px;color:var(--muted)}.stat-wide{grid-column:1/-1}.legend{border-top:1px solid var(--rule);padding-top:20px}.legend select{width:100%;border:0;border-bottom:1px solid var(--rule);padding:7px 0 9px;margin:6px 0 12px;background:#fff;font-size:12px;font-weight:500}.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{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(--blue)}.subset{font-size:10px;line-height:1.6;color:var(--muted);margin-top:20px;padding-top:16px;border-top:1px solid var(--rule)}.subset b{color:#3a4d42;font-weight:500}.quick{display:flex;gap:6px;margin:20px 0 6px}.quick button{font-size:11px;padding:8px 10px}.stage-wrap{position:relative;min-width:0;overflow:hidden}#stage{position:absolute;inset:64px 14px 44px}canvas{position:absolute;width:100%;height:100%;inset:0}#tree{cursor:default;touch-action:none}#overlay{pointer-events:none}.topbar{position:absolute;top:18px;left:25px;right:25px;display:flex;align-items:center;justify-content:space-between;gap:10px;z-index:4}.searchbox{position:relative;width:280px}#search{width:100%;padding:10px 12px;border:1px solid var(--rule);background:#fff;font-size:11px}#results{position:absolute;top:39px;width:100%;background:#fff;box-shadow:0 10px 30px #10291a0d;font-size:11px}#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}.one-line{font-size:11px;color:var(--muted)}.controls{display:flex;align-items:center;gap:4px;position:absolute;right:26px;bottom:21px;z-index:3}.controls button{padding:7px 12px}.controls span{font-size:10px;padding:0 7px;min-width:42px;text-align:center;color:var(--muted)}.help{position:absolute;bottom:25px;left:25px;font-size:10px;line-height:1.7;color:var(--muted);pointer-events:none}.help b{color:#435a4c;font-weight:500}#selection{position:absolute;right:22px;top:69px;width:295px;background:#fff;border:1px solid #dfe8e2;box-shadow:0 8px 40px #2035280c;z-index:5;padding:23px}#sel-close{position:absolute;top:6px;right:8px;border:0;background:transparent;font-size:20px;padding:0 4px;color:var(--muted)}#sel-name{font-size:18px;letter-spacing:-.5px;font-family:Manrope;margin:0 0 6px;line-height:1.25}#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;grid-template-columns:1fr;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}#loading{position:absolute;inset:0;display:grid;place-content:center;background:white;color:var(--muted);z-index:10}dialog{border:1px solid var(--rule);padding:36px;max-width:calc(100vw - 40px);width:1000px;color:var(--ink)}dialog::backdrop{background:#10271930;backdrop-filter:blur(3px)}dialog h2{font-family:Manrope;font-size:28px;letter-spacing:-1px;margin:0 0 7px}dialog p{font-size:13px;line-height:1.7;color:var(--muted)}.dialog-close{float:right;border:0;font-size:22px;padding:0 4px}.gene-label{font-size:11px;margin-top:28px;color:var(--muted)}#gene-svg{width:100%;height:auto;font:11px Inter;fill:#718279;margin:16px 0}.gene-notes{border-top:1px solid var(--rule);padding-top:15px;font-size:11px}#about-dialog{width:680px}#about-dialog p{font-size:12px}#about-dialog ul{padding-left:18px;font-size:12px;line-height:1.9;color:var(--muted)}
4
  @media(min-width:1600px){main{grid-template-columns:365px 1fr}aside{padding:40px}.release .big{font-size:44px}h1{font-size:38px}}
5
  @media(max-width:850px){header{padding:0 20px;height:72px}.brand{font-size:22px}.header-right .tag{display:none}main{height:auto;min-height:0;display:flex;flex-direction:column}aside{padding:25px 22px;border-right:0;border-bottom:1px solid var(--rule);overflow:visible}h1{font-size:30px;max-width:420px}.intro{max-width:520px;margin-bottom:18px}.release{display:none}.legend{padding-top:10px;border-top:0;max-width:520px}.legend .tag{display:none}.scope-note{max-width:500px}.subset{margin-top:15px;padding-top:10px}.quick{margin-top:15px}.stage-wrap{height:740px}.topbar{left:20px;right:20px}.one-line{display:none}#stage{inset:68px 0 85px}.help{left:20px;bottom:62px}.controls{left:20px;right:auto}#selection{right:15px;top:68px;width:275px}dialog{padding:25px}.header-right{gap:10px}}
6
  .subset{margin:0 0 14px;padding:0;border-top:0}.locus-controls{border-top:1px solid var(--rule);padding-top:15px;margin:16px 0 20px}.locus-controls select{width:100%;padding:8px 0;border:0;border-bottom:1px solid var(--rule);background:white;font-size:12px}.locus-controls p{font-size:10px;line-height:1.5;color:var(--muted);margin:8px 0}.locus-controls strong{display:block;font-size:11px;font-weight:500;margin-top:10px}.icon-toggle{margin-top:12px}.icon-toggle input{accent-color:var(--green)}#layer-tooltip{position:absolute;z-index:8;width:250px;pointer-events:none;padding:13px 15px;background:#fff;border:1px solid #dbe5de;box-shadow:0 5px 25px #142a1b0c;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}#credits-dialog{width:740px}#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{color:#406651}.release{padding-top:13px;margin-bottom:17px}.release .big{font-size:31px;margin-top:7px}.stats{gap:10px;margin-top:12px}.stats .stat-wide{grid-column:auto}.stats b{font-size:18px}.intro{margin-bottom:17px}h1{font-size:30px}
7
+ </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></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><span class="one-line">Real assemblies · NCBI taxonomy</span></div><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="overlay" 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><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><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section><div class="help"><b>Click a strand, icon or bar · 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>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, unmodified images from PhyloPic, reused with the atlas’s taxonomic assignments. Each image’s source page and licence are linked below.</p><div id="icon-credits"></div></dialog><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>