Spaces:
Running
Running
Extend the sweep to four seconds and make the centre dial selectable
Browse files- .gitattributes +1 -0
- README.md +3 -3
- app.js +15 -3
- bars-closeup.png +2 -2
- carbon-a-strand-source.zip +2 -2
- check_dial.py +48 -0
- check_sweep.py +1 -1
- detail.png +2 -2
- dial-selection.png +3 -0
- genes.png +2 -2
- hover-detail.png +2 -2
- index.html +0 -0
- layers.js +2 -2
- light-overview.png +2 -2
- loci-overview.png +2 -2
- loci-raw.png +2 -2
- locus-detail.png +2 -2
- overview.png +2 -2
- refseq.png +2 -2
- selection-aligned.png +2 -2
- sweep-complete.png +2 -2
- sweep-detail.png +2 -2
- sweep.js +3 -3
- template.html +1 -1
.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
|
| 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
|
| 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()
|
| 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
|
|
Git LFS Details
|
carbon-a-strand-source.zip
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256: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
|
| 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
|
|
Git LFS Details
|
dial-selection.png
ADDED
|
Git LFS Details
|
genes.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
hover-detail.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
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=
|
| 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
|
|
Git LFS Details
|
loci-overview.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
loci-raw.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
locus-detail.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
overview.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
refseq.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
selection-aligned.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
sweep-complete.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
sweep-detail.png
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
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=
|
| 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
|
| 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
|
| 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
|
|
|
|
| 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>
|