Skip to content

Commit e2bec69

Browse files
cmdcolinclaude
andcommitted
Improve accession page layout, fix BUSCO rendering, and deduplicate types
- Refactor accession page stats into a proper dl/dt/dd grid layout - Fix BUSCO completeness breakdown: parentheses were split across independent conditionals causing mismatched parens when fields were absent - Add L50 toLocaleString() formatting for consistency with N50 values - Extract AnnotationInfo as a named export from hubtools, removing the duplicate definition in accessionData.ts - Fix pairedAccession resolution when report is matched via paired_accession - Fix GitHub/Mastodon/Bluesky icon FOUC by adding inline width/height attrs - Remove stale boilerplate comments Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent a06c702 commit e2bec69

6 files changed

Lines changed: 208 additions & 102 deletions

File tree

hubtools/src/parseAssemblyEntry.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,13 @@ export function parseAssemblyEntry({
9797
const ncbiRefSeqCategory = assembly_info.refseq_category
9898
const suppressed = assembly_info.assembly_status === 'suppressed'
9999
const assemblyType = assembly_info.assembly_type
100+
// When the report was matched via r.paired_accession === accession, the
101+
// report describes the other assembly (e.g. GCA report for a GCF hub), so
102+
// the paired accession is report.accession, not report.paired_accession.
100103
const pairedAccession =
101-
report.paired_accession ?? assembly_info.paired_assembly?.accession
104+
report.paired_accession === accession
105+
? report.accession
106+
: (report.paired_accession ?? assembly_info.paired_assembly?.accession)
102107
const pairedAssemblyStatus = assembly_info.paired_assembly?.status
103108
const pairedAssemblyDifferences = assembly_info.paired_assembly?.differences
104109
const genomeNotes = assembly_info.genome_notes

hubtools/src/types.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,30 @@ export interface UCSCGenArkAssemblyEntry {
99
ucscBrowser: string
1010
}
1111

12+
export interface AnnotationInfo {
13+
name?: string
14+
provider?: string
15+
release_date?: string
16+
busco?: {
17+
busco_lineage?: string
18+
busco_ver?: string
19+
complete?: number
20+
single_copy?: number
21+
duplicated?: number
22+
fragmented?: number
23+
missing?: number
24+
total_count?: string
25+
}
26+
stats?: {
27+
gene_counts?: {
28+
protein_coding?: number
29+
non_coding?: number
30+
pseudogene?: number
31+
total?: number
32+
}
33+
}
34+
}
35+
1236
// NCBI Datasets API format (from `datasets summary genome accession`)
1337
export interface NCBIDatasetsReport {
1438
accession: string
@@ -54,19 +78,7 @@ export interface NCBIDatasetsReport {
5478
genome_coverage?: string
5579
number_of_component_sequences?: number
5680
}
57-
annotation_info?: {
58-
name?: string
59-
provider?: string
60-
release_date?: string
61-
stats?: {
62-
gene_counts?: {
63-
protein_coding?: number
64-
non_coding?: number
65-
pseudogene?: number
66-
total?: number
67-
}
68-
}
69-
}
81+
annotation_info?: AnnotationInfo
7082
}
7183

7284
export interface NCBIDatasetsResponse {

website/src/components/Header.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,21 @@ const navItems = [
4646
</svg></a
4747
>
4848
<a href="https://github.com/GMOD/jbrowse-components" class="socialLink">
49-
<svg class="icon" fill="currentColor" viewBox="0 0 24 24">
49+
<svg class="icon" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
5050
<path
5151
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
5252
></path>
5353
</svg>
5454
</a>
5555
<a href="https://genomic.social/@usejbrowse" class="socialLink">
56-
<svg class="icon" fill="currentColor" viewBox="0 0 24 24">
56+
<svg class="icon" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
5757
<path
5858
d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"
5959
></path>
6060
</svg>
6161
</a>
6262
<a href="https://bsky.app/profile/jbrowse.org" class="socialLink">
63-
<svg class="icon" fill="currentColor" viewBox="0 0 600 530">
63+
<svg class="icon" width="24" height="24" fill="currentColor" viewBox="0 0 600 530">
6464
<path
6565
d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"
6666
></path>

website/src/pages/accession/[id].astro

Lines changed: 166 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ const { id } = Astro.params
2323
const ret = loadAccessionMap().get(id as string)
2424
2525
if (!ret) {
26-
// Astro handles 404s for paths not returned by getStaticPaths
27-
// For direct access, you might want a custom 404 page or redirect
28-
// For now, we'll just show a message.
2926
throw new Error(`Accession not found: ${id}`)
3027
}
3128
@@ -86,18 +83,31 @@ const imageData = tryAndReadJSON<{ imageUrl?: string; pageUrl?: string }>(
8683
const imageUrl = imageData?.imageUrl
8784
const pageUrl = imageData?.pageUrl
8885
89-
const statsParts = [
90-
stats?.total_length
91-
? `${Number(stats.total_length).toLocaleString()} bp total`
92-
: null,
93-
stats?.chromosome_count ? `${stats.chromosome_count} chromosomes` : null,
94-
stats?.scaffold_n50
95-
? `scaffold N50: ${Number(stats.scaffold_n50).toLocaleString()}`
96-
: null,
97-
stats?.contig_n50
98-
? `contig N50: ${Number(stats.contig_n50).toLocaleString()}`
99-
: null,
100-
].filter(Boolean)
86+
const buscoBreakdown = annotationInfo?.busco
87+
? [
88+
annotationInfo.busco.single_copy !== undefined
89+
? `${(annotationInfo.busco.single_copy * 100).toFixed(1)}% single-copy`
90+
: null,
91+
annotationInfo.busco.duplicated !== undefined
92+
? `${(annotationInfo.busco.duplicated * 100).toFixed(1)}% duplicated`
93+
: null,
94+
annotationInfo.busco.fragmented !== undefined
95+
? `${(annotationInfo.busco.fragmented * 100).toFixed(1)}% fragmented`
96+
: null,
97+
annotationInfo.busco.missing !== undefined
98+
? `${(annotationInfo.busco.missing * 100).toFixed(1)}% missing`
99+
: null,
100+
].filter(Boolean)
101+
: []
102+
103+
const hasAssemblyStats =
104+
stats?.total_length ||
105+
stats?.chromosome_count ||
106+
stats?.scaffold_count ||
107+
stats?.contig_count ||
108+
gcPercent !== undefined ||
109+
genomeCoverage ||
110+
sequencingTech
101111
---
102112

103113
<Layout frontmatter={{ title: scientificName }}>
@@ -177,7 +187,7 @@ const statsParts = [
177187
) : null
178188
}
179189
{
180-
pairedAccession && pairedAssemblyData ? (
190+
pairedAccession ? (
181191
<div>
182192
<b>
183193
{pairedLabel}
@@ -191,7 +201,15 @@ const statsParts = [
191201
</span>
192202
:
193203
</b>{' '}
194-
<a href={`/accession/${pairedAccession}`}>{pairedAccession}</a>
204+
{pairedAssemblyData ? (
205+
<a href={`/accession/${pairedAccession}`}>{pairedAccession}</a>
206+
) : (
207+
<a
208+
href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${pairedAccession}/`}
209+
>
210+
{pairedAccession}
211+
</a>
212+
)}
195213
{pairedAssemblyStatus === 'suppressed' ? (
196214
<>
197215
{' '}
@@ -212,39 +230,6 @@ const statsParts = [
212230
</div>
213231
) : null
214232
}
215-
{
216-
statsParts.length ? (
217-
<div>
218-
<b>Assembly stats:</b>
219-
<ul>
220-
{statsParts.map(p => (
221-
<li>{p}</li>
222-
))}
223-
</ul>
224-
</div>
225-
) : null
226-
}
227-
{
228-
gcPercent !== undefined ? (
229-
<div>
230-
<b>GC content:</b> {gcPercent}%
231-
</div>
232-
) : null
233-
}
234-
{
235-
genomeCoverage ? (
236-
<div>
237-
<b>Coverage:</b> {genomeCoverage}x
238-
</div>
239-
) : null
240-
}
241-
{
242-
sequencingTech ? (
243-
<div>
244-
<b>Sequencing:</b> {sequencingTech}
245-
</div>
246-
) : null
247-
}
248233
{
249234
bioprojectAccession ? (
250235
<div>
@@ -257,29 +242,139 @@ const statsParts = [
257242
</div>
258243
) : null
259244
}
260-
{
261-
annotationInfo ? (
262-
<div>
263-
<b>Annotation:</b> {annotationInfo.name}
264-
{annotationInfo.provider ? ` (${annotationInfo.provider})` : null}
265-
{annotationInfo.release_date
266-
? `, ${annotationInfo.release_date}`
267-
: null}
245+
</div>
246+
{
247+
hasAssemblyStats ? (
248+
<div>
249+
<h2>Assembly statistics</h2>
250+
<dl class={styles.statsTable}>
251+
{stats?.total_length ? (
252+
<>
253+
<dt>Total length</dt>
254+
<dd>{Number(stats.total_length).toLocaleString()} bp</dd>
255+
</>
256+
) : null}
257+
{stats?.ungapped_length ? (
258+
<>
259+
<dt>Ungapped length</dt>
260+
<dd>{Number(stats.ungapped_length).toLocaleString()} bp</dd>
261+
</>
262+
) : null}
263+
{stats?.chromosome_count ? (
264+
<>
265+
<dt>Chromosomes</dt>
266+
<dd>{stats.chromosome_count}</dd>
267+
</>
268+
) : null}
269+
{stats?.scaffold_count ? (
270+
<>
271+
<dt>Scaffolds</dt>
272+
<dd>{stats.scaffold_count}</dd>
273+
</>
274+
) : null}
275+
{stats?.scaffold_n50 ? (
276+
<>
277+
<dt>Scaffold N50</dt>
278+
<dd>{Number(stats.scaffold_n50).toLocaleString()}</dd>
279+
</>
280+
) : null}
281+
{stats?.scaffold_l50 ? (
282+
<>
283+
<dt>Scaffold L50</dt>
284+
<dd>{Number(stats.scaffold_l50).toLocaleString()}</dd>
285+
</>
286+
) : null}
287+
{stats?.contig_count ? (
288+
<>
289+
<dt>Contigs</dt>
290+
<dd>{stats.contig_count}</dd>
291+
</>
292+
) : null}
293+
{stats?.contig_n50 ? (
294+
<>
295+
<dt>Contig N50</dt>
296+
<dd>{Number(stats.contig_n50).toLocaleString()}</dd>
297+
</>
298+
) : null}
299+
{stats?.contig_l50 ? (
300+
<>
301+
<dt>Contig L50</dt>
302+
<dd>{Number(stats.contig_l50).toLocaleString()}</dd>
303+
</>
304+
) : null}
305+
{gcPercent !== undefined ? (
306+
<>
307+
<dt>GC content</dt>
308+
<dd>{gcPercent}%</dd>
309+
</>
310+
) : null}
311+
{genomeCoverage ? (
312+
<>
313+
<dt>Coverage</dt>
314+
<dd>{genomeCoverage}x</dd>
315+
</>
316+
) : null}
317+
{sequencingTech ? (
318+
<>
319+
<dt>Sequencing</dt>
320+
<dd>{sequencingTech}</dd>
321+
</>
322+
) : null}
323+
</dl>
324+
</div>
325+
) : null
326+
}
327+
{
328+
annotationInfo ? (
329+
<div>
330+
<h2>Annotation</h2>
331+
<dl class={styles.statsTable}>
332+
{annotationInfo.name ? (
333+
<>
334+
<dt>Name</dt>
335+
<dd>
336+
{annotationInfo.name}
337+
{annotationInfo.provider
338+
? ` (${annotationInfo.provider})`
339+
: null}
340+
{annotationInfo.release_date
341+
? `, ${annotationInfo.release_date}`
342+
: null}
343+
</dd>
344+
</>
345+
) : null}
346+
{annotationInfo.busco?.complete !== undefined ? (
347+
<>
348+
<dt>BUSCO completeness</dt>
349+
<dd>
350+
{`${(annotationInfo.busco.complete * 100).toFixed(1)}% complete`}
351+
{buscoBreakdown.length ? ` (${buscoBreakdown.join(', ')})` : null}
352+
{annotationInfo.busco.busco_lineage
353+
? ` — ${annotationInfo.busco.busco_lineage}`
354+
: null}
355+
</dd>
356+
</>
357+
) : null}
268358
{annotationInfo.stats?.gene_counts ? (
269-
<div>
270-
{annotationInfo.stats.gene_counts.total?.toLocaleString()} genes
271-
{' '}
272-
{annotationInfo.stats.gene_counts.protein_coding?.toLocaleString()}{' '}
273-
protein-coding,{' '}
274-
{annotationInfo.stats.gene_counts.non_coding?.toLocaleString()}{' '}
275-
non-coding,{' '}
276-
{annotationInfo.stats.gene_counts.pseudogene?.toLocaleString()}{' '}
277-
pseudogenes
278-
</div>
359+
<>
360+
<dt>Gene counts</dt>
361+
<dd>
362+
{annotationInfo.stats.gene_counts.total?.toLocaleString()}{' '}
363+
total —{' '}
364+
{annotationInfo.stats.gene_counts.protein_coding?.toLocaleString()}{' '}
365+
protein-coding,{' '}
366+
{annotationInfo.stats.gene_counts.non_coding?.toLocaleString()}{' '}
367+
non-coding,{' '}
368+
{annotationInfo.stats.gene_counts.pseudogene?.toLocaleString()}{' '}
369+
pseudogenes
370+
</dd>
371+
</>
279372
) : null}
280-
</div>
281-
) : null
282-
}
373+
</dl>
374+
</div>
375+
) : null
376+
}
377+
<div>
283378
{
284379
comments ? (
285380
<div>
@@ -288,7 +383,6 @@ const statsParts = [
288383
</div>
289384
) : null
290385
}
291-
292386
{
293387
imageUrl ? (
294388
<div class={styles.imageContainer}>

website/src/pages/accession/accession.module.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
.statsTable {
2+
display: grid;
3+
grid-template-columns: max-content 1fr;
4+
column-gap: 1.5rem;
5+
}
6+
17
.imageContainer {
28
float: right;
39
margin-left: 1.5rem;

0 commit comments

Comments
 (0)