lvwerra HF Staff commited on
Commit
cba18a0
·
verified ·
1 Parent(s): 8e683b6

Extend the sweep to four seconds and make the centre dial selectable

Browse files
.gitattributes CHANGED
@@ -47,3 +47,4 @@ light-overview.png filter=lfs diff=lfs merge=lfs -text
47
  bars-closeup.png filter=lfs diff=lfs merge=lfs -text
48
  sweep-complete.png filter=lfs diff=lfs merge=lfs -text
49
  sweep-detail.png filter=lfs diff=lfs merge=lfs -text
 
 
47
  bars-closeup.png filter=lfs diff=lfs merge=lfs -text
48
  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
README.md CHANGED
@@ -35,7 +35,7 @@ The original **Genome Atlas silhouettes**, without circle backgrounds, sit on th
35
 
36
  ## Sweep animation
37
 
38
- **Animate sweep** returns to the whole tree and makes one clockwise turn in 2.5 seconds. The thin centre hand follows the changing highlighted strand. Species, assembly and coding-locus counters accumulate as it moves. Every crossed assembly is counted, including those passed between animation frames; each species is counted once using its NCBI species-rank ancestor. Missing locus counts remain excluded. The completed subset totals are **16,374 species, 33,588 assemblies and 212,905,534 available coding loci** (across 17,828 assemblies with counts). Species-rank ancestors are deduplicated separately from the 16,976 submitted taxon IDs.
39
 
40
  Replay starts the counters from zero. The button stops a running sweep; Escape, zooming or direct chart interaction also stop it. Normal hover resumes afterwards. A separate interaction canvas draws the hand and exact strand highlight, leaving the detailed static tree in place throughout playback.
41
 
@@ -45,12 +45,12 @@ The taxonomy and plain-language names reuse the earlier explorer work. Only ance
45
 
46
  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. Hovering a strand highlights its exact path and opens its assembly details; clicking pins those details while other strands can be previewed on hover. Zoom, pan and search reveal individual accessions. 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.
47
 
48
- Dark mode is the default, with charcoal surfaces, emerald and blue strands, and pale atlas silhouettes. The sun/moon control switches to the light theme and saves the preference locally. The root opening contains a fine watch hand that points to the selected strand. Fine locus bars use shared explicit angular vertices instead of independently approximated Canvas arcs, avoiding gaps in narrow bins without changing their counts.
49
 
50
  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.
51
 
52
  ## Rebuild
53
 
54
- `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`, `layers.js`, `theme.js`, `sweep.js`, the data files, and the existing release font assets. `python check_sweep.py` verifies hand alignment, cumulative counts, replay, cancellation, timing and stationary background rendering. `python check_hover_theme.py` verifies hover previews, pinned fallback, the empty centre, theme persistence and continuous subpixel bars. `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.
55
 
56
  Data provenance and limitations are recorded in `provenance.json`. The original release video and explorer were not modified.
 
35
 
36
  ## Sweep animation
37
 
38
+ **Animate sweep** returns to the whole tree and makes one clockwise turn in 4 seconds. The thin centre hand follows the changing highlighted strand. Species, assembly and coding-locus counters accumulate as it moves. Every crossed assembly is counted, including those passed between animation frames; each species is counted once using its NCBI species-rank ancestor. Missing locus counts remain excluded. The completed subset totals are **16,374 species, 33,588 assemblies and 212,905,534 available coding loci** (across 17,828 assemblies with counts). Species-rank ancestors are deduplicated separately from the 16,976 submitted taxon IDs.
39
 
40
  Replay starts the counters from zero. The button stops a running sweep; Escape, zooming or direct chart interaction also stop it. Normal hover resumes afterwards. A separate interaction canvas draws the hand and exact strand highlight, leaving the detailed static tree in place throughout playback.
41
 
 
45
 
46
  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. Hovering a strand highlights its exact path and opens its assembly details; clicking pins those details while other strands can be previewed on hover. Zoom, pan and search reveal individual accessions. 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.
47
 
48
+ Dark mode is the default, with charcoal surfaces, emerald and blue strands, and pale atlas silhouettes. The sun/moon control switches to the light theme and saves the preference locally. The root opening contains a fine watch hand that points to the selected strand. Hover anywhere inside the opening to select by direction; click to pin, or drag to turn the hand. Dragging outside the dial pans the tree. Fine locus bars use shared explicit angular vertices instead of independently approximated Canvas arcs, avoiding gaps in narrow bins without changing their counts.
49
 
50
  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.
51
 
52
  ## Rebuild
53
 
54
+ `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`, `layers.js`, `theme.js`, `sweep.js`, the data files, and the existing release font assets. `python check_dial.py` verifies centre hover, pinning, dragging without panning, zoomed coordinates, touch, the top seam and takeover from playback. `python check_sweep.py` verifies hand alignment, cumulative counts, replay, cancellation, timing and stationary background rendering. `python check_hover_theme.py` verifies hover previews, pinned fallback, the empty centre, theme persistence and continuous subpixel bars. `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.
55
 
56
  Data provenance and limitations are recorded in `provenance.json`. The original release video and explorer were not modified.
app.js CHANGED
@@ -67,6 +67,17 @@ function draw(){if(!gl)return;let dpr=canvas.width/W;gl.viewport(0,0,canvas.widt
67
  }
68
  function zoomAt(factor,x=W/2,y=H/2){stopSweep();hoverAssembly(null);$('#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()}
69
  function reset(){stopSweep(false);$('#layer-tooltip').hidden=true;hoverBin=null;zoom=1;pan={x:0,y:0};clearSelection();schedule()}
 
 
 
 
 
 
 
 
 
 
 
70
  function nearest(x,y,radius=7){
71
  const [wx,wy]=world(x,y),pad=radius/(baseScale*zoom);let best=pad*pad,found=null;
72
  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];
@@ -101,22 +112,23 @@ async function init(){try{const raw=Uint8Array.from(atob($('#dataset').textConte
101
  stage.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});
102
  let down=null,moved=false,hoverPending=false,hoverEvent=null;
103
  const queueHover=e=>{hoverEvent=e;if(hoverPending)return;hoverPending=true;requestAnimationFrame(()=>{hoverPending=false;const event=hoverEvent;hoverEvent=null;if(!down&&event)hoverLayers(event)})};
104
- stage.addEventListener('pointerdown',e=>{stopSweep(false);hoverEvent=null;down={x:e.clientX,y:e.clientY};moved=false;stage.setPointerCapture(e.pointerId)});
105
  stage.addEventListener('pointermove',e=>{
106
  if(!down){queueHover(e);return}
107
  let dx=e.clientX-down.x,dy=e.clientY-down.y;
 
108
  if(!moved&&Math.hypot(dx,dy)<4)return;
109
  if(!moved)hoverAssembly(null);
110
  moved=true;canvas.style.cursor='grabbing';$('#layer-tooltip').hidden=true;
111
  pan.x+=dx;pan.y+=dy;down={x:e.clientX,y:e.clientY};schedule();
112
  });
113
- stage.addEventListener('pointerup',e=>{if(!down)return;const dragged=moved;if(!dragged){let b=canvas.getBoundingClientRect();if(!clickLayers(e.clientX-b.left,e.clientY-b.top)){const a=nearest(e.clientX-b.left,e.clientY-b.top);if(a)selectAssembly(a);else clearSelection()}}down=null;canvas.style.cursor='default';if(!dragged&&e.pointerType!=='touch')hoverLayers(e)});
114
  stage.addEventListener('pointercancel',()=>{down=null;hoverAssembly(null);canvas.style.cursor='default'});
115
  stage.addEventListener('pointerleave',e=>{hoverEvent=null;$('#layer-tooltip').hidden=true;hoverBin=null;if(!down){canvas.style.cursor='default';if(!e.relatedTarget?.closest?.('#selection'))hoverAssembly(null)}schedule()});
116
  $('#selection').addEventListener('pointerleave',e=>{if(e.relatedTarget!==canvas)hoverAssembly(null)});
117
  $('#theme-toggle').onclick=()=>setTheme(theme==='dark'?'light':'dark');
118
  $('#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();
119
- stage.addEventListener('dblclick',e=>{let b=canvas.getBoundingClientRect();zoomAt(3,e.clientX-b.left,e.clientY-b.top)});
120
  $('#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();
121
  }catch(e){$('#loading').textContent='Unable to load the tree: '+e.message;console.error(e)}}
122
  init();
 
67
  }
68
  function zoomAt(factor,x=W/2,y=H/2){stopSweep();hoverAssembly(null);$('#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()}
69
  function reset(){stopSweep(false);$('#layer-tooltip').hidden=true;hoverBin=null;zoom=1;pan={x:0,y:0};clearSelection();schedule()}
70
+ // Select the strand in the pointer's direction anywhere inside the root opening.
71
+ // A small neutral hub avoids unstable angles right at the pivot. Dial drags can
72
+ // continue beyond the opening without turning into a pan gesture.
73
+ function hitDial(x,y,dragging=false){
74
+ const [wx,wy]=world(x,y),r=Math.hypot(wx,wy);
75
+ if((!dragging&&r>92)||r*baseScale*zoom<3)return null;
76
+ const angle=((Math.atan2(wy,wx)-START)%TAU+TAU)%TAU;
77
+ if(angle>SPAN)return angle-SPAN<TAU-angle?leaves.at(-1):leaves[0];
78
+ return leaves[Math.min(leaves.length-1,Math.floor(angle/SPAN*leaves.length))];
79
+ }
80
+ function hitAssembly(x,y,radius=7){return hitDial(x,y)||nearest(x,y,radius)}
81
  function nearest(x,y,radius=7){
82
  const [wx,wy]=world(x,y),pad=radius/(baseScale*zoom);let best=pad*pad,found=null;
83
  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];
 
112
  stage.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});
113
  let down=null,moved=false,hoverPending=false,hoverEvent=null;
114
  const queueHover=e=>{hoverEvent=e;if(hoverPending)return;hoverPending=true;requestAnimationFrame(()=>{hoverPending=false;const event=hoverEvent;hoverEvent=null;if(!down&&event)hoverLayers(event)})};
115
+ stage.addEventListener('pointerdown',e=>{stopSweep(false);hoverEvent=null;const b=stage.getBoundingClientRect(),a=hitDial(e.clientX-b.left,e.clientY-b.top);down={x:e.clientX,y:e.clientY,dial:!!a};moved=false;if(a){selectAssembly(a);canvas.style.cursor='pointer'}stage.setPointerCapture(e.pointerId)});
116
  stage.addEventListener('pointermove',e=>{
117
  if(!down){queueHover(e);return}
118
  let dx=e.clientX-down.x,dy=e.clientY-down.y;
119
+ if(down.dial){const b=stage.getBoundingClientRect(),a=hitDial(e.clientX-b.left,e.clientY-b.top,true);if(a&&a!==selected)selectAssembly(a);moved=moved||Math.hypot(dx,dy)>=4;return}
120
  if(!moved&&Math.hypot(dx,dy)<4)return;
121
  if(!moved)hoverAssembly(null);
122
  moved=true;canvas.style.cursor='grabbing';$('#layer-tooltip').hidden=true;
123
  pan.x+=dx;pan.y+=dy;down={x:e.clientX,y:e.clientY};schedule();
124
  });
125
+ stage.addEventListener('pointerup',e=>{if(!down)return;const dragged=moved,dial=down.dial,b=stage.getBoundingClientRect(),x=e.clientX-b.left,y=e.clientY-b.top;if(dial){const a=hitDial(x,y,true);if(a)selectAssembly(a)}else if(!dragged&&!clickLayers(x,y)){const a=hitAssembly(x,y);if(a)selectAssembly(a);else clearSelection()}down=null;canvas.style.cursor=hitDial(x,y)?'pointer':'default';if(!dial&&!dragged&&e.pointerType!=='touch')hoverLayers(e)});
126
  stage.addEventListener('pointercancel',()=>{down=null;hoverAssembly(null);canvas.style.cursor='default'});
127
  stage.addEventListener('pointerleave',e=>{hoverEvent=null;$('#layer-tooltip').hidden=true;hoverBin=null;if(!down){canvas.style.cursor='default';if(!e.relatedTarget?.closest?.('#selection'))hoverAssembly(null)}schedule()});
128
  $('#selection').addEventListener('pointerleave',e=>{if(e.relatedTarget!==canvas)hoverAssembly(null)});
129
  $('#theme-toggle').onclick=()=>setTheme(theme==='dark'?'light':'dark');
130
  $('#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();
131
+ 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)});
132
  $('#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();
133
  }catch(e){$('#loading').textContent='Unable to load the tree: '+e.message;console.error(e)}}
134
  init();
bars-closeup.png CHANGED

Git LFS Details

  • SHA256: 9f34b6ef4311b2df9a28a1696a07f5cdf99b99fb701a05ecc5057272017f646a
  • Pointer size: 132 Bytes
  • Size of remote file: 2.06 MB

Git LFS Details

  • SHA256: fa0f4ae916759abdb597ddad37995c2c6dcfeff1e08dfb87a2625b9232a129c6
  • Pointer size: 132 Bytes
  • Size of remote file: 2.06 MB
carbon-a-strand-source.zip CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:64a0348410353e921b1074d036ce96bc4f122c0e45ae52de31461bb480888dee
3
- size 15058217
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:307af569cf113c3d89775c1b16c06aba15d1136eb7b23afacf143ed4c60ad015
3
+ size 15060511
check_dial.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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',args=['--no-sandbox','--enable-webgl','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader'])
8
+ page=b.new_page(viewport={'width':1500,'height':1050},has_touch=True);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
+ def point(i,r=62):
11
+ return page.evaluate('''([i,radius])=>{const a=leaves[i],p=screen(polar(radius,a.angle)),box=stage.getBoundingClientRect();return {x:box.x+p[0],y:box.y+p[1],id:a.accession}}''',[i,r])
12
+ # Hit the same exact assembly from different radii inside all four dial sectors.
13
+ targets=[1000,11000,21500,32000]
14
+ for i in targets:
15
+ for radius in [35,78]:
16
+ q=point(i,radius);page.mouse.move(q['x'],q['y']);page.wait_for_function('(i)=>selected?.i===i',arg=i)
17
+ assert page.locator('#selection').is_visible()
18
+ assert page.locator('#sel-id').inner_text()==q['id']
19
+ assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')=='pointer'
20
+ assert page.evaluate('pinnedAssembly===null')
21
+ q=point(11000);page.mouse.click(q['x'],q['y']);page.mouse.move(100,100)
22
+ assert page.evaluate('pinnedAssembly.i')==11000
23
+ assert page.locator('#selection').is_visible()
24
+ # A dial drag changes angle/selection while preserving the camera, even if it
25
+ # leaves the dial. Releasing keeps the chosen assembly pinned.
26
+ a=point(1000);z=point(21500,112);camera=page.evaluate('({zoom,pan:{...pan}})')
27
+ page.mouse.move(a['x'],a['y']);page.mouse.down();page.mouse.move(z['x'],z['y'],steps=8);page.mouse.up()
28
+ assert page.evaluate('pinnedAssembly.i')==21500
29
+ assert page.evaluate('({zoom,pan})')==camera
30
+ page.screenshot(path=str(R/'dial-selection.png'))
31
+ # The mapping stays in tree coordinates under zoom and pan.
32
+ page.evaluate('''()=>{clearSelection();zoom=2.5;pan={x:-80,y:65};draw()}''')
33
+ q=point(32000);page.mouse.move(q['x'],q['y']);page.wait_for_function('selected?.i===32000')
34
+ page.mouse.click(q['x'],q['y']);assert page.evaluate('pinnedAssembly.i')==32000
35
+ # Touch taps can select through the centre as well.
36
+ page.locator('#reset').click();page.locator('#theme-toggle').click();q=point(7000)
37
+ page.touchscreen.tap(q['x'],q['y']);assert page.evaluate('pinnedAssembly.i')==7000
38
+ # The top seam resolves to a real neighbouring strand; the pivot stays stable.
39
+ seam=page.evaluate('''()=>{const right=screen(polar(60,NOON+.01)),left=screen(polar(60,NOON-.01)),centre=screen([0,0]);return [hitDial(...right)?.i,hitDial(...left)?.i,hitDial(...centre)]}''')
40
+ assert seam==[0,33587,None],seam
41
+ # Taking over with the dial stops playback and keeps the partial sweep totals.
42
+ page.locator('#animate-sweep').click();page.wait_for_function('sweep.count>100')
43
+ q=point(17000);page.mouse.click(q['x'],q['y']);assert not page.evaluate('sweep.running');assert page.evaluate('pinnedAssembly.i')==17000
44
+ before=page.evaluate('sweep.count');page.wait_for_timeout(150);assert page.evaluate('sweep.count')==before
45
+ assert page.evaluate('SWEEP_DURATION')==4000
46
+ assert not errors,errors
47
+ print(json.dumps({'dial_hover':'8 positions passed','pin':'passed','dial_drag_without_pan':'passed','zoom_and_pan':'passed','touch':'passed','top_seam':seam,'sweep_takeover':'passed','errors':errors}),flush=True)
48
+ b.close()
check_sweep.py CHANGED
@@ -21,7 +21,7 @@ with sync_playwright() as p:
21
  f=result['frames'];assert all(a['count']<=z['count'] and a['species']<=z['species'] and a['loci']<=z['loci'] for a,z in zip(f,f[1:]))
22
  assert result['totals']=={'species':16374,'count':33588,'loci':212905534,'known':17828}
23
  assert abs(result['angle']-4.71238898038469)<1e-9 and result['zoom']==1
24
- duration=f[-1]['t']-f[1]['t'];assert 2400<=duration<3000,duration
25
  assert len(f)>35,len(f)
26
  assert result['redraws']<6,result['redraws']
27
  assert any(z['count']-a['count']>1 for a,z in zip(f,f[1:]))
 
21
  f=result['frames'];assert all(a['count']<=z['count'] and a['species']<=z['species'] and a['loci']<=z['loci'] for a,z in zip(f,f[1:]))
22
  assert result['totals']=={'species':16374,'count':33588,'loci':212905534,'known':17828}
23
  assert abs(result['angle']-4.71238898038469)<1e-9 and result['zoom']==1
24
+ duration=f[-1]['t']-f[1]['t'];assert 3900<=duration<4500,duration
25
  assert len(f)>35,len(f)
26
  assert result['redraws']<6,result['redraws']
27
  assert any(z['count']-a['count']>1 for a,z in zip(f,f[1:]))
detail.png CHANGED

Git LFS Details

  • SHA256: e96ce731cd47191b0844f29294101e45b3d4483363cf680b1d02f125fa7c37d8
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB

Git LFS Details

  • SHA256: 2f00c8116de646a1b3490e0427d7613981007c7ed303c38e2f7a467d313d4e6a
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
dial-selection.png ADDED

Git LFS Details

  • SHA256: ad7e8c40b0f9061241fb58c19f7694ad32d0d9b0b5e90d606f0fe3be5eaa1ff7
  • Pointer size: 131 Bytes
  • Size of remote file: 587 kB
genes.png CHANGED

Git LFS Details

  • SHA256: 3cb76386c2db85136f00649157848ae6eb4a601e1aea9a567397ad36a8c4155c
  • Pointer size: 131 Bytes
  • Size of remote file: 280 kB

Git LFS Details

  • SHA256: ee2cfb16807549d1509544e2a8c4e26138c5e6c74f5a2cae2f5b91c0f25f0f45
  • Pointer size: 131 Bytes
  • Size of remote file: 281 kB
hover-detail.png CHANGED

Git LFS Details

  • SHA256: 8f7cdb597dcc97239dde26414297fb41901df458d726783f7fcf9b3655e0bc84
  • Pointer size: 131 Bytes
  • Size of remote file: 174 kB

Git LFS Details

  • SHA256: c9e4e46af888f880d481d9ac6dba8bfe67c8a7db005ea4786da040a9d3be119b
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
index.html CHANGED
The diff for this file is too large to render. See raw diff
 
layers.js CHANGED
@@ -79,8 +79,8 @@ function drawAtlasIcons(){
79
  }
80
  function hitIcon(x,y){return iconHits.find(h=>Math.hypot(h.x-x,h.y-y)<h.r)}
81
  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}
82
- function hoverLayers(e){if(sweep.running)return;if(document.querySelector('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');
83
- if(!ic&&!bin){tip.hidden=true;if(hoverBin){hoverBin=null;schedule()}const a=nearest(x,y,3);canvas.style.cursor=a?'pointer':'default';hoverAssembly(a);return}
84
  canvas.style.cursor='pointer';
85
  hoverAssembly(!ic&&locusMode==='raw'?leaves[bin.lo]:null);
86
  if(!ic&&locusMode==='raw'){tip.hidden=true;if(hoverBin!==bin){hoverBin=bin;schedule()}return}
 
79
  }
80
  function hitIcon(x,y){return iconHits.find(h=>Math.hypot(h.x-x,h.y-y)<h.r)}
81
  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}
82
+ function hoverLayers(e){if(sweep.running)return;if(document.querySelector('dialog[open]'))return;let box=stage.getBoundingClientRect(),x=e.clientX-box.left,y=e.clientY-box.top,dial=hitDial(x,y),ic=dial?null:hitIcon(x,y),bin=dial?null:hitBar(x,y),tip=$('#layer-tooltip');
83
+ if(!ic&&!bin){tip.hidden=true;if(hoverBin){hoverBin=null;schedule()}const a=hitAssembly(x,y,3);canvas.style.cursor=a?'pointer':'default';hoverAssembly(a);return}
84
  canvas.style.cursor='pointer';
85
  hoverAssembly(!ic&&locusMode==='raw'?leaves[bin.lo]:null);
86
  if(!ic&&locusMode==='raw'){tip.hidden=true;if(hoverBin!==bin){hoverBin=bin;schedule()}return}
light-overview.png CHANGED

Git LFS Details

  • SHA256: 281e654177c769b0317a1cd71f36fa388f722c88b821c2acc180520c7cce6319
  • Pointer size: 131 Bytes
  • Size of remote file: 534 kB

Git LFS Details

  • SHA256: 822c545cc230341f250ea4d2692d7158659b83a7b926cf1adba300487fec9844
  • Pointer size: 131 Bytes
  • Size of remote file: 534 kB
loci-overview.png CHANGED

Git LFS Details

  • SHA256: f945c7a56888131242eafaa6475e25c62af5392c5ffef730de0724fc2d671626
  • Pointer size: 131 Bytes
  • Size of remote file: 625 kB

Git LFS Details

  • SHA256: 3a29fc04e7b235987003a669cda579bad72acd1fc9e57a63af8c92ec775d65c9
  • Pointer size: 131 Bytes
  • Size of remote file: 625 kB
loci-raw.png CHANGED

Git LFS Details

  • SHA256: 1c0b540f33835eefe9da6773124614382c75b931a3b8f4f5933a8f6af991a7ab
  • Pointer size: 131 Bytes
  • Size of remote file: 623 kB

Git LFS Details

  • SHA256: 36574faeb8efe6201b2b1863379d3cc5b4f05a6b946778eab60327c48e128eed
  • Pointer size: 131 Bytes
  • Size of remote file: 623 kB
locus-detail.png CHANGED

Git LFS Details

  • SHA256: 5fdac6dd2d409dac39d93457de9f6ccd8f317a8ade6c9e24947821378656f11f
  • Pointer size: 131 Bytes
  • Size of remote file: 194 kB

Git LFS Details

  • SHA256: f53b8c98ba9c55b4950a6cf268520f5c00f4cf7d4a85286505dfca5a795948fa
  • Pointer size: 131 Bytes
  • Size of remote file: 194 kB
overview.png CHANGED

Git LFS Details

  • SHA256: 934bd3402d7435bd2fb8a6bae3be74e11575d7271a5a8308fd36606920024007
  • Pointer size: 131 Bytes
  • Size of remote file: 567 kB

Git LFS Details

  • SHA256: 940883dd85f6cea4fc4d76c1fde04b0177add5e44b73c35f1eded779fc5e81ee
  • Pointer size: 131 Bytes
  • Size of remote file: 567 kB
refseq.png CHANGED

Git LFS Details

  • SHA256: 671dac949435ef3fd593b051fc9daa7cc452530d22fa43a8addaad4a0676b395
  • Pointer size: 131 Bytes
  • Size of remote file: 429 kB

Git LFS Details

  • SHA256: e522857b6b781c970e592ba49aa5a36e49181bb503e6bd71c2b0658d93960802
  • Pointer size: 131 Bytes
  • Size of remote file: 429 kB
selection-aligned.png CHANGED

Git LFS Details

  • SHA256: c3339f808c1b5342024af1d75ec6540d380dbd087a85e6c9738abe28cc33bed0
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB

Git LFS Details

  • SHA256: bb4b03eeff04c629909617a5c5138dea961641a1bc05f242d0992725588bc732
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
sweep-complete.png CHANGED

Git LFS Details

  • SHA256: 3a847abf43b55ec2172a34ab68434b86c8d4e4187ff848b16128e62a6c6de6b9
  • Pointer size: 131 Bytes
  • Size of remote file: 571 kB

Git LFS Details

  • SHA256: 31ef3a7c89d279bb27acf4083776006f5b512d2bd8cc1111a8892f27b23ae116
  • Pointer size: 131 Bytes
  • Size of remote file: 571 kB
sweep-detail.png CHANGED

Git LFS Details

  • SHA256: 4f91936deef7e6b13cc5f6d6675222572cd7f23d63a303b7eb05d299a9d63bd1
  • Pointer size: 131 Bytes
  • Size of remote file: 592 kB

Git LFS Details

  • SHA256: f7aaf3e2bf9eb8b2f41a0ca1f62440b06a0115a218f6d53b4a68fe0188a21d88
  • Pointer size: 131 Bytes
  • Size of remote file: 591 kB
sweep.js CHANGED
@@ -1,5 +1,5 @@
1
  // The sweep counts every crossed strand, not just the ones displayed in frames.
2
- const SWEEP_DURATION=2500,NOON=-Math.PI/2;
3
  const sweep={running:false,status:'ready',progress:0,angle:NOON,count:0,species:0,loci:0,known:0,frame:0,startedAt:0};
4
  let sweepSpecies,sweepLoci,sweepKnown,interactionPending=false;
5
 
@@ -43,7 +43,7 @@ function drawInteraction(){
43
 
44
  function updateSweep(progress){
45
  sweep.progress=Math.max(0,Math.min(1,progress));
46
- // Gentle acceleration and deceleration; a complete turn still takes 2.5 s.
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)));
@@ -57,7 +57,7 @@ function updateSweep(progress){
57
 
58
  function sweepButton(label){
59
  $('#sweep-button-label').textContent=label;
60
- $('#animate-sweep').setAttribute('aria-label',sweep.running?'Stop the sweep':'Animate a 2.5-second sweep of the whole tree');
61
  $('.stage-wrap').dataset.sweeping=String(sweep.running);
62
  }
63
 
 
1
  // The sweep counts every crossed strand, not just the ones displayed in frames.
2
+ const SWEEP_DURATION=4000,NOON=-Math.PI/2;
3
  const sweep={running:false,status:'ready',progress:0,angle:NOON,count:0,species:0,loci:0,known:0,frame:0,startedAt:0};
4
  let sweepSpecies,sweepLoci,sweepKnown,interactionPending=false;
5
 
 
43
 
44
  function updateSweep(progress){
45
  sweep.progress=Math.max(0,Math.min(1,progress));
46
+ // Gentle acceleration and deceleration; a complete turn still takes 4 s.
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)));
 
57
 
58
  function sweepButton(label){
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
 
template.html CHANGED
@@ -37,4 +37,4 @@ button:hover{border-color:var(--muted)}
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:850px){.searchbox{width:auto;min-width:0;flex:1}#animate-sweep{min-width:129px;padding:10px 9px;font-size:10px}.sweep-meter{left:20px;right:20px;gap:0 12px;top:74px}.sweep-meter strong{font-size:21px}.sweep-meter p{display:block;font-size:8px}.sweep-meter p span{display:block}.sweep-meter p span+span{margin-top:3px}#stage{top:171px}#selection{top:171px}}
40
- </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 2.5-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 · 2.5 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><button id="sel-focus">Follow this strand ↗</button><button id="sel-loci" hidden>See coding regions →</button></section><div class="help"><b>Hover to inspect · Click to pin · 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. Animate sweep makes a full clockwise turn in 2.5 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><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>
 
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:850px){.searchbox{width:auto;min-width:0;flex:1}#animate-sweep{min-width:129px;padding:10px 9px;font-size:10px}.sweep-meter{left:20px;right:20px;gap:0 12px;top:74px}.sweep-meter strong{font-size:21px}.sweep-meter p{display:block;font-size:8px}.sweep-meter p span{display:block}.sweep-meter p span+span{margin-top:3px}#stage{top:171px}#selection{top:171px}}
40
+ </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><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 pin · 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><script id="dataset" type="application/octet-stream">__DATA__</script><script>__SCRIPT__</script></body></html>