cgeorgiaw HF Staff commited on
Commit
648cd8a
·
verified ·
1 Parent(s): d0d6e7b

Carry the genome atlas style through the annotation workspace

Browse files
Files changed (5) hide show
  1. README.md +2 -0
  2. app.css +37 -0
  3. app.py +63 -44
  4. style.py +62 -0
  5. taxonomy.py +3 -3
README.md CHANGED
@@ -27,6 +27,8 @@ Click a group to unfold its direct lineages, and use breadcrumbs, parent navigat
27
 
28
  The graphic uses server-rendered SVG/HTML with scoped styles in `atlas.css` and delegated click/keyboard handlers in `atlas.js`; no external graphics library or image assets are required. Include these files when deploying.
29
 
 
 
30
  ### Refresh the taxonomy snapshot
31
 
32
  ```bash
 
27
 
28
  The graphic uses server-rendered SVG/HTML with scoped styles in `atlas.css` and delegated click/keyboard handlers in `atlas.js`; no external graphics library or image assets are required. Include these files when deploying.
29
 
30
+ The annotation workspace shares the atlas palette and typography through `style.py` and `app.css`: warm panels, serif section headings, green controls, and matching tables, downloads, and disclosures. CDS plots use a solid green positive strand and a dashed warm-brown negative strand; binary labels use green. The fixed light palette stays consistent with the landing graphic under either OS color preference. Include both style files when deploying; Gradio 6 receives the theme and stylesheet in `launch()`.
31
+
32
  ### Refresh the taxonomy snapshot
33
 
34
  ```bash
app.css ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global component styling, paired with the landing graphic's scoped atlas.css. */
2
+ .gradio-container { max-width:1280px!important; margin:auto!important; padding:24px 20px 40px!important; color-scheme:light; }
3
+ #eukaryotic-atlas { padding:0!important; border:0!important; background:transparent!important; }
4
+ .gradio-container .atlas-panel { background:#f7f8f0; border:1px solid #e0e6d8; border-radius:24px; padding:26px 30px; box-shadow:0 8px 32px #234b2908; gap:18px; margin-top:6px; }
5
+ .workspace-heading { display:flex; align-items:flex-start; gap:14px; padding:0 0 4px; }
6
+ .workspace-step { width:32px; height:32px; border:1px solid #cdddc8; background:#eaf1e2; border-radius:50%; display:flex; align-items:center; justify-content:center; flex-shrink:0; font:500 11px Arial,sans-serif; color:#527261; margin-top:4px; }
7
+ .workspace-heading h2 { margin:0 0 8px; font:400 32px/1.15 Georgia,'Times New Roman',serif; letter-spacing:-.7px; color:#173c30; }
8
+ .workspace-heading p { margin:0; color:#647467; font:13px/1.6 Arial,Helvetica,sans-serif; }
9
+ .gradio-container .quiet-note { color:#647467; font-size:12px; line-height:1.7; }
10
+ .gradio-container .quiet-note p { color:#647467; font-size:12px; line-height:1.7; }
11
+ .gradio-container .scope-note { padding:11px 15px; border:1px solid #e0e6d8; border-radius:12px; background:#edf3e7; }
12
+ .gradio-container .scope-note p { margin:0; }
13
+ .gradio-container .action-button { align-self:flex-end; min-height:46px; }
14
+ .gradio-container .atlas-panel > .form { border:0; background:transparent; box-shadow:none; }
15
+ .gradio-container .atlas-panel .form { border-radius:14px; }
16
+ .gradio-container .atlas-panel button { transition:background-color .15s, border-color .15s; }
17
+ .gradio-container button:focus-visible,.gradio-container a:focus-visible { outline:2px solid #327555; outline-offset:3px; }
18
+ .gradio-container .prose a { text-underline-offset:3px; }
19
+ .gradio-container .prose code { color:#315641; }
20
+ .gradio-container .atlas-disclosure { border:1px solid #e0e6d8; border-radius:14px; box-shadow:none; }
21
+ .gradio-container .atlas-disclosure > button { padding:15px 18px; }
22
+ #annotation-plot { border:1px solid #e0e6d8; background:#fffefa; border-radius:16px; overflow:hidden; }
23
+ #annotation-plot .plot-container { background:#fffefa; }
24
+ #annotation-examples { font-size:12px; }
25
+ #annotation-examples .label { color:#647467; }
26
+ #annotation-results { border-radius:14px; }
27
+ .workspace-footer { border-top:1px solid #e0e6d8; padding-top:18px; margin-top:6px; display:flex; justify-content:space-between; flex-wrap:wrap; gap:8px; font:11px/1.6 Arial,sans-serif; color:#75836f; }
28
+ .workspace-footer a { color:#315641; text-decoration:none; }
29
+ .workspace-footer a:hover { text-decoration:underline; }
30
+ @media(max-width:700px) {
31
+ .gradio-container { padding:12px 10px 24px!important; }
32
+ .gradio-container .atlas-panel { border-radius:18px; padding:20px 15px; }
33
+ .workspace-heading h2 { font-size:28px; }
34
+ .workspace-heading { gap:10px; }
35
+ .gradio-container .action-button { width:100%; }
36
+ }
37
+ @media(prefers-reduced-motion:reduce) { .gradio-container .atlas-panel button { transition:none; } }
app.py CHANGED
@@ -14,6 +14,7 @@ import pyarrow.parquet as pq
14
  import plotly.graph_objects as go
15
 
16
  from taxonomy import build_taxonomy_tab
 
17
  from catalog import Catalog
18
  from remote_catalog import RemoteCatalog, RemoteReadError
19
 
@@ -44,19 +45,26 @@ def build_app(catalog=None):
44
  binary = mode == "Binary labels"
45
  column = "Predicted CDS" if binary else "P(CDS)"
46
  figure = go.Figure()
47
- tracks = [("CDS (either strand)", "#2563eb")] if binary else [("+ strand", "#2563eb"), ("− strand", "#ea580c")]
48
  for strand, color in tracks:
49
  rows = frame[frame["Strand"] == strand]
50
  figure.add_trace(go.Scatter(x=rows["Position (bp)"].tolist(), y=rows[column].tolist(),
51
  name=strand, mode="lines",
52
- line=dict(color=color, shape="hv" if binary else "linear")))
 
53
  if not binary:
54
- figure.add_hline(y=threshold, line_dash="dot", line_color="#64748b",
55
  annotation_text=f"Threshold {threshold:g}")
56
  figure.update_layout(title="Predicted CDS — either strand" if binary else "CDS probability by strand",
57
  xaxis_title="Position (bp; 0-based)", yaxis_title=column,
58
- height=360, margin=dict(l=55, r=20, t=50, b=45), template="plotly_white",
59
- hovermode="x unified", legend=dict(orientation="h"))
 
 
 
 
 
 
60
  figure.update_yaxes(range=[-0.05, 1.05], tickvals=[0, 1] if binary else None)
61
  return figure
62
 
@@ -120,44 +128,55 @@ def build_app(catalog=None):
120
 
121
  with gr.Blocks(title="GenBank Annotation Explorer", delete_cache=(3600, 3600)) as demo:
122
  build_taxonomy_tab()
123
- gr.Markdown("## Find genome annotations\nLook up an assembly or contig accession to explore predicted coding regions.")
124
- gr.Markdown(f"**Prototype {scope} · {len(catalog.records):,} segments · "
125
- f"{len(catalog.manifest['assemblies']):,} assemblies · {catalog.manifest['bases']:,} bases**\n\n"
126
- "Source: [HuggingFaceBio/genbank-annotations](https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations). "
127
- f"These are model-predicted CDS probabilities, not curated gene features. Search covers the {scope} only. "
128
- + (f"Annotations load on demand from **{len(catalog.manifest['sources'])} bucket files**. "
129
- f"Index updated {catalog.manifest['created_at'][:10]}." if remote_mode else ""))
130
- with gr.Row():
131
- accession = gr.Textbox(label="Accession ID", placeholder="Assembly (GCA_…) or contig accession", scale=5)
132
- search_button = gr.Button("Find annotations", variant="primary", scale=1)
133
- examples = [first["assembly_accession"], first["record_name"]]
134
- if remote_mode:
135
- with closing(catalog.connect()) as conn:
136
- examples += [r[0] for r in conn.execute("SELECT record_name FROM segments WHERE id IN (SELECT min(id) FROM segments GROUP BY object_path)")]
137
- examples += ["JBPJTW010000350.1"]
138
- gr.Examples(examples=[[e] for e in dict.fromkeys(examples)], inputs=accession)
139
- status = gr.Markdown("Enter an accession or select an example. IDs are case-insensitive; version suffixes are optional.")
140
- results = gr.Dataframe(value=catalog.table([]), interactive=False, label="Matching segments")
141
- segment = gr.Dropdown(choices=[], label="Segment to explore", interactive=True)
142
- with gr.Row():
143
- start = gr.Number(label="Start (0-based, inclusive)", precision=0)
144
- end = gr.Number(label="End (exclusive)", precision=0)
145
- view = gr.Button("Update region")
146
- with gr.Row():
147
- mode = gr.Radio(["Probabilities", "Binary labels"], value="Probabilities", label="Viewer mode")
148
- threshold = gr.Slider(0, 1, value=0.5, step=0.01, label="CDS threshold (max strand P > threshold)")
149
- plot = gr.Plot(label="CDS tracks")
150
- note = gr.Markdown("Choose a segment to see its probability tracks.")
151
- with gr.Accordion("Segment metadata and provenance", open=False):
152
- metadata = gr.JSON(label="Source metadata")
153
- with gr.Row():
154
- download = gr.Button("Prepare segment download")
155
- file = gr.File(label="Original segment annotations (Parquet)", interactive=False)
156
- with gr.Accordion("Browse indexed accessions and coverage", open=False):
157
- gr.Markdown(f"Showing the first {len(all_ids):,} indexed segments. Search an accession to find other indexed records. "
158
- "An indexed file does not imply complete coverage of its assembly.")
159
- gr.Dataframe(value=catalog.table(all_ids), interactive=False)
160
- gr.JSON(value=catalog.manifest, label="Index provenance" if remote_mode else "Sample provenance")
 
 
 
 
 
 
 
 
 
 
 
161
  outputs = [status, results, segment, file]
162
  for event in (search_button.click, accession.submit):
163
  event(search, accession, outputs).then(select_segment, [segment, mode, threshold], [metadata, start, end, plot, note, file])
@@ -171,4 +190,4 @@ def build_app(catalog=None):
171
 
172
 
173
  if __name__ == "__main__":
174
- build_app().queue(default_concurrency_limit=2).launch(server_name="0.0.0.0")
 
14
  import plotly.graph_objects as go
15
 
16
  from taxonomy import build_taxonomy_tab
17
+ from style import APP_CSS, atlas_theme, section_header
18
  from catalog import Catalog
19
  from remote_catalog import RemoteCatalog, RemoteReadError
20
 
 
45
  binary = mode == "Binary labels"
46
  column = "Predicted CDS" if binary else "P(CDS)"
47
  figure = go.Figure()
48
+ tracks = [("CDS (either strand)", "#287557")] if binary else [("+ strand", "#287557"), ("− strand", "#ae754b")]
49
  for strand, color in tracks:
50
  rows = frame[frame["Strand"] == strand]
51
  figure.add_trace(go.Scatter(x=rows["Position (bp)"].tolist(), y=rows[column].tolist(),
52
  name=strand, mode="lines",
53
+ line=dict(color=color, width=2, dash="dash" if strand == "− strand" else "solid",
54
+ shape="hv" if binary else "linear")))
55
  if not binary:
56
+ figure.add_hline(y=threshold, line_dash="dot", line_color="#8b9b7b",
57
  annotation_text=f"Threshold {threshold:g}")
58
  figure.update_layout(title="Predicted CDS — either strand" if binary else "CDS probability by strand",
59
  xaxis_title="Position (bp; 0-based)", yaxis_title=column,
60
+ height=380, margin=dict(l=60, r=25, t=75, b=50), template="plotly_white",
61
+ paper_bgcolor="#fffefa", plot_bgcolor="#fffefa",
62
+ font=dict(family="Arial, Helvetica, sans-serif", color="#315641", size=12),
63
+ title_font=dict(family="Georgia, Times New Roman, serif", size=22, color="#173c30"),
64
+ hoverlabel=dict(bgcolor="#173c30", font_color="#ffffff", bordercolor="#173c30"),
65
+ hovermode="x unified", legend=dict(orientation="h", y=1.12, x=1, xanchor="right"))
66
+ figure.update_xaxes(gridcolor="#e9edde", zerolinecolor="#dbe3d3", linecolor="#dbe3d3")
67
+ figure.update_yaxes(gridcolor="#e9edde", zerolinecolor="#dbe3d3", linecolor="#dbe3d3")
68
  figure.update_yaxes(range=[-0.05, 1.05], tickvals=[0, 1] if binary else None)
69
  return figure
70
 
 
128
 
129
  with gr.Blocks(title="GenBank Annotation Explorer", delete_cache=(3600, 3600)) as demo:
130
  build_taxonomy_tab()
131
+ with gr.Column(elem_classes="atlas-panel"):
132
+ gr.HTML(section_header("01", "Find an accession", "Start with an assembly or contig to explore its predicted coding regions."), apply_default_css=False)
133
+ gr.Markdown(f"**{len(catalog.records):,} indexed segments** · **{len(catalog.manifest['assemblies']):,} assemblies** · "
134
+ f"{catalog.manifest['bases']:,} bases\n\n"
135
+ f"Search covers this prototype {scope}. Results are model predictions and assembly coverage may be partial.",
136
+ elem_classes=["quiet-note", "scope-note"])
137
+ with gr.Row():
138
+ accession = gr.Textbox(label="Accession ID", placeholder="Assembly (GCA_…) or contig accession", scale=5)
139
+ search_button = gr.Button("Find annotations", variant="primary", scale=1, elem_classes="action-button")
140
+ examples = [first["assembly_accession"], first["record_name"]]
141
+ if remote_mode:
142
+ with closing(catalog.connect()) as conn:
143
+ examples += [r[0] for r in conn.execute("SELECT record_name FROM segments WHERE id IN (SELECT min(id) FROM segments GROUP BY object_path)")]
144
+ examples += ["JBPJTW010000350.1"]
145
+ gr.Examples(examples=[[e] for e in dict.fromkeys(examples)], inputs=accession, label="Try an accession", elem_id="annotation-examples")
146
+ status = gr.Markdown("Enter an accession or select an example. IDs are case-insensitive; version suffixes are optional.", elem_classes="quiet-note")
147
+ results = gr.Dataframe(value=catalog.table([]), interactive=False, label="Matching segments", elem_id="annotation-results")
148
+ segment = gr.Dropdown(choices=[], label="Segment to explore", interactive=True)
149
+
150
+ with gr.Column(elem_classes="atlas-panel"):
151
+ gr.HTML(section_header("02", "Explore the coding landscape", "View CDS probabilities or apply a threshold to see one label per base."), apply_default_css=False)
152
+ with gr.Row():
153
+ start = gr.Number(label="Start (0-based, inclusive)", precision=0)
154
+ end = gr.Number(label="End (exclusive)", precision=0)
155
+ view = gr.Button("Update region", elem_classes="action-button")
156
+ with gr.Row():
157
+ mode = gr.Radio(["Probabilities", "Binary labels"], value="Probabilities", label="Viewer mode")
158
+ threshold = gr.Slider(0, 1, value=0.5, step=0.01, label="CDS threshold (max strand P > threshold)")
159
+ plot = gr.Plot(label="CDS tracks", elem_id="annotation-plot")
160
+ note = gr.Markdown("Choose a segment above to bring its coding landscape into view.", elem_classes="quiet-note")
161
+ with gr.Accordion("Segment metadata and provenance", open=False, elem_classes="atlas-disclosure"):
162
+ metadata = gr.JSON(label="Source metadata")
163
+
164
+ with gr.Column(elem_classes="atlas-panel"):
165
+ gr.HTML(section_header("03", "Take the annotations with you", "Download the original segment, with its accession and record name in the filename."), apply_default_css=False)
166
+ with gr.Row():
167
+ download = gr.Button("Prepare segment download", variant="primary", scale=1, elem_classes="action-button")
168
+ file = gr.File(label="Original segment annotations (Parquet)", interactive=False, scale=3)
169
+ gr.Markdown("Full per-base probabilities are preserved in the download, including when the viewer shows a binned overview.", elem_classes="quiet-note")
170
+ with gr.Accordion("Browse the index and its sources", open=False, elem_classes="atlas-disclosure"):
171
+ gr.Markdown(f"Showing the first {len(all_ids):,} indexed segments. Search an accession to find other indexed records. "
172
+ "An indexed file does not imply complete coverage of its assembly.\n\n"
173
+ "Source: [HuggingFaceBio/genbank-annotations](https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations). "
174
+ + (f"Annotations load on demand from {len(catalog.manifest['sources'])} bucket files. "
175
+ f"Index updated {catalog.manifest['created_at'][:10]}." if remote_mode else "Offline sample."), elem_classes="quiet-note")
176
+ gr.Dataframe(value=catalog.table(all_ids), interactive=False)
177
+ gr.JSON(value=catalog.manifest, label="Index provenance" if remote_mode else "Sample provenance")
178
+ gr.HTML('<footer class="workspace-footer"><span>Hugging Face Bio · Genome Atlas</span>'
179
+ '<a href="https://huggingface.co/buckets/HuggingFaceBio/genbank-annotations" target="_blank" rel="noopener noreferrer">Explore the annotation collection ↗</a></footer>', apply_default_css=False)
180
  outputs = [status, results, segment, file]
181
  for event in (search_button.click, accession.submit):
182
  event(search, accession, outputs).then(select_segment, [segment, mode, threshold], [metadata, start, end, plot, note, file])
 
190
 
191
 
192
  if __name__ == "__main__":
193
+ build_app().queue(default_concurrency_limit=2).launch(server_name="0.0.0.0", theme=atlas_theme(), css=APP_CSS)
style.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared visual language for the atlas and annotation workspace."""
2
+ from html import escape
3
+ from pathlib import Path
4
+
5
+ import gradio as gr
6
+
7
+ APP_CSS = (Path(__file__).resolve().parent / "app.css").read_text()
8
+
9
+
10
+ def atlas_theme():
11
+ green = gr.themes.colors.Color(
12
+ c50="#f1f6ed", c100="#e0eedc", c200="#c5ddc8", c300="#a4cebb",
13
+ c400="#72b295", c500="#40916f", c600="#287557", c700="#1d5c45",
14
+ c800="#173c30", c900="#102e24", c950="#0b211a")
15
+ stone = gr.themes.colors.Color(
16
+ c50="#fafbf6", c100="#f3f5ec", c200="#e1e7d9", c300="#cfd9c8",
17
+ c400="#9dac97", c500="#72816d", c600="#586b55", c700="#40543f",
18
+ c800="#2c422e", c900="#1c3222", c950="#112317")
19
+ theme = gr.themes.Base(primary_hue=green, secondary_hue=green, neutral_hue=stone,
20
+ font=["Arial", "Helvetica", "sans-serif"],
21
+ font_mono=["ui-monospace", "Consolas", "monospace"])
22
+ theme.set(
23
+ body_background_fill="#fcfcf8", body_text_color="#173c30", body_text_color_subdued="#647467",
24
+ background_fill_primary="#ffffff", background_fill_secondary="#f3f5ec",
25
+ border_color_primary="#dbe3d3", border_color_accent="#72b295",
26
+ color_accent="#287557", color_accent_soft="#e0eedc",
27
+ link_text_color="#287557", link_text_color_hover="#173c30", link_text_color_visited="#287557",
28
+ block_background_fill="#f7f8f0", block_border_color="#dbe3d3", block_border_width="1px",
29
+ block_label_background_fill="#f7f8f0", block_label_text_color="#52694f",
30
+ block_title_text_color="#254735", block_label_text_size="12px", block_title_text_size="13px",
31
+ block_label_text_weight="500", block_title_text_weight="600",
32
+ block_radius="14px", block_padding="16px", layout_gap="18px",
33
+ input_background_fill="#ffffff", input_border_color="#cfdcc9", input_border_width="1px",
34
+ input_border_color_focus="#40916f", input_border_color_hover="#9db89d",
35
+ input_placeholder_color="#788673", input_radius="10px", input_padding="12px 14px",
36
+ input_shadow_focus="0 0 0 3px #40916f20",
37
+ button_primary_background_fill="#245d45", button_primary_background_fill_hover="#173c30",
38
+ button_primary_border_color="#245d45", button_primary_border_color_hover="#173c30",
39
+ button_primary_text_color="#ffffff", button_primary_text_color_hover="#ffffff",
40
+ button_secondary_background_fill="#edf3e7", button_secondary_background_fill_hover="#deead6",
41
+ button_secondary_border_color="#cfdcc9", button_secondary_text_color="#315641",
42
+ button_large_radius="12px", button_small_radius="20px", button_border_width="1px",
43
+ checkbox_label_background_fill="#ffffff", checkbox_label_background_fill_selected="#deead6",
44
+ checkbox_label_border_color="#dbe3d3", checkbox_label_border_color_selected="#86b396",
45
+ checkbox_label_text_color_selected="#254735", checkbox_background_color_selected="#287557",
46
+ checkbox_border_color_selected="#287557", slider_color="#40916f",
47
+ table_border_color="#dbe3d3", table_even_background_fill="#ffffff",
48
+ table_odd_background_fill="#f4f7ee", table_text_color="#254735", table_radius="12px",
49
+ table_row_focus="#e0eedc", accordion_text_color="#315641",
50
+ panel_background_fill="#f7f8f0", panel_border_color="#dbe3d3",
51
+ code_background_fill="#edf2e6", block_shadow="none",
52
+ )
53
+ # The atlas is an intentionally light canvas; retain its palette under an OS dark preference.
54
+ for name in list(vars(theme)):
55
+ if name.endswith("_dark") and hasattr(theme, name[:-5]):
56
+ setattr(theme, name, getattr(theme, name[:-5]))
57
+ return theme
58
+
59
+
60
+ def section_header(number, title, description):
61
+ return (f'<header class="workspace-heading"><span class="workspace-step">{escape(number)}</span>'
62
+ f'<div><h2>{escape(title)}</h2><p>{escape(description)}</p></div></header>')
taxonomy.py CHANGED
@@ -224,10 +224,10 @@ def build_taxonomy_tab():
224
  raise gr.Error("Choose a eukaryotic group in this snapshot.") from exc
225
 
226
  tree.click(explore_tree, outputs=tree, show_progress="minimal", concurrency_limit=1)
227
- with gr.Accordion("Find a lineage", open=False):
228
  with gr.Row():
229
  query = gr.Textbox(label="Eukaryotic taxon", placeholder="Scientific name, taxon ID, or a group such as animals")
230
- find = gr.Button("Search lineages")
231
  matches = gr.Dropdown(choices=[], label="Matching lineages", interactive=True)
232
  status = gr.Markdown()
233
 
@@ -241,7 +241,7 @@ def build_taxonomy_tab():
241
  matches.input(taxonomy_view, matches, tree)
242
  find.click(taxonomy_search, query, [matches, status])
243
  query.submit(taxonomy_search, query, [matches, status])
244
- with gr.Accordion("About the tree and coverage", open=False):
245
  gr.Markdown("This tree follows **NCBI Taxonomy within Eukaryota**. Animals and fungi share the Opisthokonta branch; "
246
  "green plants include green algae. ‘Other lineages’ combines the remaining siblings for display and can be expanded. "
247
  "Each view shows up to six direct lineages plus any remainder. The opening view also opens the animal/fungal branch. "
 
224
  raise gr.Error("Choose a eukaryotic group in this snapshot.") from exc
225
 
226
  tree.click(explore_tree, outputs=tree, show_progress="minimal", concurrency_limit=1)
227
+ with gr.Accordion("Find a lineage", open=False, elem_classes="atlas-disclosure"):
228
  with gr.Row():
229
  query = gr.Textbox(label="Eukaryotic taxon", placeholder="Scientific name, taxon ID, or a group such as animals")
230
+ find = gr.Button("Search lineages", elem_classes="action-button")
231
  matches = gr.Dropdown(choices=[], label="Matching lineages", interactive=True)
232
  status = gr.Markdown()
233
 
 
241
  matches.input(taxonomy_view, matches, tree)
242
  find.click(taxonomy_search, query, [matches, status])
243
  query.submit(taxonomy_search, query, [matches, status])
244
+ with gr.Accordion("About the tree and coverage", open=False, elem_classes="atlas-disclosure"):
245
  gr.Markdown("This tree follows **NCBI Taxonomy within Eukaryota**. Animals and fungi share the Opisthokonta branch; "
246
  "green plants include green algae. ‘Other lineages’ combines the remaining siblings for display and can be expanded. "
247
  "Each view shows up to six direct lineages plus any remainder. The opening view also opens the animal/fungal branch. "