Spaces:
Paused
Paused
Download ui.py from HUBioDataLab/ProtHGT: direct link, hf CLI and curl.
- Browser
- Download file 9.83 kB
-
https://huggingface.co/spaces/HUBioDataLab/ProtHGT/resolve/main/ui.py
- Command line
-
hf download hf://spaces/HUBioDataLab/ProtHGT/ui.py
-
curl -L -o ui.py https://huggingface.co/spaces/HUBioDataLab/ProtHGT/resolve/main/ui.py
9.83 kB
| """Shared look-and-feel for the ProtHGT Streamlit pages (header, CSS, stepper).""" | |
| import streamlit as st | |
| ACCENT = "#3B4BD8" | |
| GITHUB_URL = "https://github.com/HUBioDataLab/ProtHGT" | |
| PAPER_URL = "https://www.biorxiv.org/content/10.1101/2025.04.19.649272" | |
| # Minimum score behind each strictness preset (used by the app and the user guide). | |
| # Replace "Balanced" with your validation-set Fmax threshold(s) to match the paper exactly. | |
| STRICTNESS = { | |
| "Precise": (0.70, "Fewer, high-confidence terms"), | |
| "Balanced": (0.50, "Recommended default"), | |
| "Exploratory": (0.30, "More candidates to screen"), | |
| } | |
| # How to read the knowledge graph view. Shown in the User guide and in the "How to read this graph" | |
| # expander of the Knowledge graph tab, so the two stay in sync. | |
| KG_VIEW_GUIDE = """ | |
| The view shows the part of the ProtHGT knowledge graph around one query protein, drawn at the centre | |
| with a dark ring. Direct neighbours sit on an inner ring, grouped by kind (confirmed predictions, novel | |
| predictions, known annotations, then other node types); second-degree neighbours sit on an outer ring | |
| next to the neighbour they connect to. Choose the protein, then click **Show graph**. | |
| - **Predicted GO terms** – how many of the highest-scoring predictions are shown for each predicted | |
| GO category. | |
| - **Neighbours per edge type** – how many knowledge-graph neighbours are shown for each relationship | |
| type (domains, interactions, pathways, …), including known GO annotations outside the top predictions. | |
| - **Second-degree neighbours** – set above 0 to also expand the neighbours' own connections, excluding | |
| edges back to the query protein, up to that many per edge type. Keep it low: the graph quickly | |
| becomes cluttered. Second-degree edges always come from the knowledge graph, never from predictions. | |
| Node colours mark the entity type (see the legend at the top of the graph). Predictions are drawn for the | |
| selected protein only; select another protein in **Protein** to see its predictions. | |
| Edge colours mark the kind of link: | |
| - **Purple** – predicted GO term that is also annotated in the knowledge graph. Known annotations that | |
| the model predicted are purple even when they rank below the top terms set by **Predicted GO terms**. | |
| - **Red** – novel prediction (one of the top predicted terms) not annotated in the knowledge graph. | |
| - **Blue** – GO annotation in the knowledge graph that the model did not predict in this run. | |
| - **Grey** – other relationships, such as protein–protein interactions, domains, drug targets, pathways, | |
| domain–GO mappings and GO hierarchy links. | |
| Interacting with the graph: | |
| - **Hover** a node to highlight its connections and show their relationship names, with a card giving the | |
| node's name, type and a link to the source database (UniProt, QuickGO, InterPro, …). Hover an edge to see | |
| the relationship and, for GO terms, the score. | |
| - **Drag** nodes to rearrange them; **scroll** to zoom and drag the background to pan. | |
| - **Click a legend item** to hide that node type or edge kind; nodes left without visible edges are hidden | |
| too. Click it again to bring it back. | |
| - Use the icons at the top right to **reset** the view, save it as a **PNG**, or download the edges currently | |
| shown as **JSON** (with their scores and edge kind; hidden edge kinds and node types are left out). | |
| For exploration beyond second-degree connections, download the complete knowledge graph from the link at | |
| the top of the Knowledge graph tab. | |
| """ | |
| _CSS = """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=DM+Mono:wght@400;500&display=swap'); | |
| /* Material icons (incl. st.status's stExpanderIcon*) must keep their icon font */ | |
| .stApp :is(p, h1, h2, h3, h4, h5, label, li, a, button, input, textarea, select, div, span):not([data-testid^="stIcon"]):not([data-testid^="stExpanderIcon"]) { | |
| font-family: 'Manrope', sans-serif; | |
| } | |
| code, .mono { font-family: 'DM Mono', monospace !important; } | |
| /* Hide the sidebar entirely: navigation lives in the header */ | |
| [data-testid="stSidebar"], [data-testid="stSidebarCollapsedControl"] { display: none; } | |
| /* Streamlit's fixed top bar (3.75rem) overlaps the page: make it transparent and start content below it */ | |
| header[data-testid="stHeader"] { background: transparent; } | |
| .block-container { max-width: 1100px; padding-top: 4.25rem; padding-bottom: 4rem; } | |
| /* Header */ | |
| .phgt-brand { font-weight: 800; font-size: 1.35rem; letter-spacing: -0.01em; margin: 0 !important; line-height: 2.4rem; } | |
| div[data-testid="stPageLink"] a { justify-content: flex-end; } | |
| div[data-testid="stPageLink"] p { font-weight: 500; color: #4A4E5C; } | |
| /* Hero */ | |
| .phgt-hero h1 { font-size: 2.1rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; margin: 1.6rem 0 0.4rem; padding: 0; } | |
| .phgt-hero p { font-size: 1.05rem; color: #4A4E5C; margin: 0 0 1.4rem; } /* full width, like the stepper */ | |
| .phgt-hero-spacer { height: 1.5rem; } /* step 3 has no hero: keep the stepper clear of the header */ | |
| /* Stepper */ | |
| .phgt-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; list-style: none; margin: 0 0 1.5rem; padding: 0; } | |
| .phgt-step { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: 12px; background: #FFFFFF; border: 1px solid #E4E2DC; } | |
| .phgt-step.done { background: #EEF0FC; border-color: #EEF0FC; } | |
| .phgt-step.current { border: 2px solid ACCENT; } | |
| .phgt-dot { flex: none; width: 30px; height: 30px; border-radius: 999px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; background: #EDECE7; color: #4A4E5C; box-sizing: border-box; } | |
| .phgt-step.done .phgt-dot { background: ACCENT; color: #FFFFFF; } | |
| .phgt-step.current .phgt-dot { background: #1E288A; color: #FFFFFF; } | |
| .phgt-step small { display: block; font-size: 12px; color: #4A4E5C; } | |
| .phgt-step span.t { font-weight: 700; font-size: 15px; } | |
| @media (max-width: 640px) { | |
| /* keep the header links on one wrapped row instead of stacking */ | |
| div[data-testid="stHorizontalBlock"]:has(.phgt-brand) { flex-direction: row !important; flex-wrap: wrap !important; gap: 0 14px !important; } | |
| div[data-testid="stHorizontalBlock"]:has(.phgt-brand) > div[data-testid="stColumn"] { width: max-content !important; flex: 0 0 max-content !important; min-width: 0 !important; } | |
| div[data-testid="stHorizontalBlock"]:has(.phgt-brand) > div[data-testid="stColumn"]:first-child { flex: 1 0 100% !important; } | |
| div[data-testid="stHorizontalBlock"]:has(.phgt-brand) div[data-testid="stPageLink"] { width: auto !important; } | |
| .phgt-step small { display: none; } | |
| .phgt-step span.t { font-size: 13px; } | |
| .phgt-hero h1 { font-size: 1.8rem; } | |
| } | |
| /* Cards & chips */ | |
| div[data-testid="stVerticalBlockBorderWrapper"] { border-radius: 16px; } | |
| /* Dataframes use the same frame as the cards (Streamlit sets the border inline, hence !important) */ | |
| div[data-testid="stDataFrameResizable"] { border: 1px solid #E4E2DC !important; border-radius: 16px !important; overflow: hidden; } | |
| div[data-testid="stDataFrame"] .stDataFrameGlideDataEditor { border-radius: 16px; } | |
| .phgt-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 10px; } | |
| .phgt-chip { padding: 4px 9px; border-radius: 8px; background: #F3F2EE; font: 500 13px 'DM Mono', monospace; } | |
| .phgt-muted { color: #4A4E5C; font-size: 0.9rem; } | |
| /* Small category label above a card title (e.g. "Gene Ontology" over "Molecular function") */ | |
| .phgt-eyebrow { display: block; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: ACCENT; margin-bottom: 2px; } | |
| .phgt-meter { height: 6px; border-radius: 3px; background: #EDECE7; margin: 6px 0 4px; } | |
| .phgt-meter > div { height: 6px; border-radius: 3px; background: ACCENT; } | |
| .phgt-count { display: block; padding-bottom: 0.6rem; } /* breathing room below the protein count */ | |
| /* Multiselect chips use the accent tint instead of Streamlit red */ | |
| span[data-baseweb="tag"] { background-color: #E2E5FB !important; color: #1E288A !important; font-family: 'DM Mono', monospace; } | |
| span[data-baseweb="tag"] svg { fill: #1E288A !important; } | |
| /* Primary buttons */ | |
| .stButton > button, .stDownloadButton > button { border-radius: 12px; min-height: 44px; font-weight: 600; } | |
| .stButton > button[kind="primary"] { font-weight: 700; } | |
| /* Metrics */ | |
| div[data-testid="stMetric"] { background: #FFFFFF; border: 1px solid #E4E2DC; border-radius: 14px; padding: 14px 16px; } | |
| </style> | |
| """.replace("ACCENT", ACCENT) | |
| def apply_style(): | |
| st.markdown(_CSS, unsafe_allow_html=True) | |
| def render_header(current="predict"): | |
| apply_style() | |
| cols = st.columns([3, 1.1, 1.1, 0.9, 1.1], vertical_alignment="center") | |
| with cols[0]: | |
| st.markdown('<p class="phgt-brand">ProtHGT</p>', unsafe_allow_html=True) | |
| with cols[1]: | |
| st.page_link("ProtHGT_app.py", label="Predict") | |
| with cols[2]: | |
| st.page_link("pages/User_Guide.py", label="User guide") | |
| with cols[3]: | |
| st.page_link("pages/About.py", label="About") | |
| with cols[4]: | |
| st.page_link(GITHUB_URL, label="GitHub") | |
| def render_stepper(step): | |
| labels = ["Add proteins", "Choose what to predict", "Review results"] | |
| items = [] | |
| for i, label in enumerate(labels, start=1): | |
| if i < step: | |
| cls, mark, state = "done", "✓", "" | |
| elif i == step: | |
| cls, mark, state = "current", str(i), ' aria-current="step"' | |
| else: | |
| cls, mark, state = "", str(i), "" | |
| items.append( | |
| f'<li class="phgt-step {cls}"{state}><span class="phgt-dot">{mark}</span>' | |
| f'<span><small>Step {i}</small><span class="t">{label}</span></span></li>' | |
| ) | |
| st.markdown(f'<ol class="phgt-steps" aria-label="Progress">{"".join(items)}</ol>', unsafe_allow_html=True) | |