Carry the genome atlas style through the annotation workspace
Browse files- README.md +2 -0
- app.css +37 -0
- app.py +63 -44
- style.py +62 -0
- 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)", "#
|
| 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,
|
|
|
|
| 53 |
if not binary:
|
| 54 |
-
figure.add_hline(y=threshold, line_dash="dot", line_color="#
|
| 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=
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
examples += [
|
| 137 |
-
examples
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
with gr.
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
with gr.
|
| 157 |
-
gr.
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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. "
|