From 1f85e4aec9841c806b0baf5b64d64228426e66d1 Mon Sep 17 00:00:00 2001 From: Boxiang Liu Date: Tue, 7 Jul 2026 18:20:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20regional=20plot=20frontend=20=E2=80=94?= =?UTF-8?q?=20Plotly=20Locus=20View=20on=20the=20gene=20page=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #27. Wires the #25 backend into the gene page as a LocusZoom-style Plotly plot, styled to Liu Fei's mockup (#23). - gene.html: blue/slate palette; tissue + LD-population selectors; a Plotly scattergl of position vs −log10(p) colored by r² (first paint from /api/gene/{key}/regional); click a point to re-pick the lead (/api/ld, client-side re-color); genome-wide-significance line; LD legend + caveat. - viz.py: lead marker -> orange #f97316 (per Liu Fei), was purple. - web.py: gene page passes the tissue list to the selector. - Template-render tested; 100% coverage. Verified live: /gene/TP53 renders the plot UI with 25 real tissues. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/locusview/templates/gene.html | 137 ++++++++++++++++++++++++++++-- src/locusview/viz.py | 2 +- src/locusview/web.py | 1 + tests/test_web.py | 11 ++- 4 files changed, 144 insertions(+), 7 deletions(-) diff --git a/src/locusview/templates/gene.html b/src/locusview/templates/gene.html index 1f2756b..1fb4149 100644 --- a/src/locusview/templates/gene.html +++ b/src/locusview/templates/gene.html @@ -4,14 +4,34 @@ {{ gene.symbol }} — locusview + @@ -22,6 +42,39 @@

{{ gene.symbol }}

(strand {{ gene.strand }}) · internal gene id {{ gene.gene_id }}

+

Regional plot

+
+
+
+ + +
+
+ + +
+
+

Loading…

+
+ lead (click any point to re-pick) + 0.8–1.0 + 0.6–0.8 + 0.4–0.6 + 0.2–0.4 + 0.0–0.2 + no LD data +
+

+ LD = 1000G phase 3 (selected population) — a single-population approximation and a visual aid to + locus structure, not part of the association inference. The dotted line is genome-wide + significance (5×10−8). +

+
+

eQTLs

Showing eQTLs from {{ n_tissues }} of {{ total_tissues }} datasets (bounded for the MVP). @@ -55,5 +108,79 @@

eQTLs

{% endif %}

locusview v{{ version }}

+ + diff --git a/src/locusview/viz.py b/src/locusview/viz.py index c613433..546e30d 100644 --- a/src/locusview/viz.py +++ b/src/locusview/viz.py @@ -8,7 +8,7 @@ import math # LocusZoom.js default r² color scheme. Bins are broken at 0.2 / 0.4 / 0.6 / 0.8. -LD_LEAD_COLOR = "#9632B8" # the reference/lead variant (drawn as a diamond) +LD_LEAD_COLOR = "#f97316" # the reference/lead variant (orange diamond, per Liu Fei's design) LD_NULL_COLOR = "#AAAAAA" # no LD data — distinct from low r² LD_BINS: list[tuple[float, str]] = [ (0.2, "#463699"), # indigo 0.0–0.2 diff --git a/src/locusview/web.py b/src/locusview/web.py index b054549..0b7a0e2 100644 --- a/src/locusview/web.py +++ b/src/locusview/web.py @@ -114,6 +114,7 @@ def gene_page(name: str) -> HTMLResponse: "gene.html", gene=gene, rows=rows, + datasets=datasets, n_tissues=len(datasets), total_tissues=len(all_datasets), ) diff --git a/tests/test_web.py b/tests/test_web.py index 1f021b8..f05eb02 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -45,6 +45,15 @@ def test_gene_page_renders_eqtls() -> None: assert "rs12345" in response.text # variant rendered +def test_gene_page_has_regional_plot_ui() -> None: + html = _gene_client().get("/gene/TP53").text + assert 'id="lv-plot"' in html # Plotly container + assert 'id="lv-tissue"' in html and "Stomach" in html # tissue selector + options + assert 'id="lv-population"' in html # LD population selector + assert "cdn.plot.ly" in html # Plotly loaded + assert "/api/gene/" in html # JS wires the regional endpoint + + def test_gene_page_unknown_gene_is_404() -> None: response = _gene_client().get("/gene/NOPE") assert response.status_code == 404 @@ -98,7 +107,7 @@ def test_regional_endpoint_attaches_r2_and_lead() -> None: by_rs = {v["rs_id"]: v for v in body["variants"]} assert by_rs[111]["is_lead"] is True and by_rs[111]["r2"] == 1.0 assert by_rs[222]["r2"] == 0.9 and by_rs[333]["r2"] == 0.1 - assert by_rs[111]["color"] == "#9632B8" # lead diamond color + assert by_rs[111]["color"] == "#f97316" # lead diamond color (orange, per Liu Fei) def test_regional_unknown_gene_is_404() -> None: