{"spec_id":"genome-track-multi","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ngenome-track-multi: Genome Track Viewer\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\n\n# Theme-adaptive chrome tokens (Imprint palette)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nAMBER = \"#DDCC77\"\n\nnp.random.seed(42)\n\n# Genomic region: chr7:27,200,000-27,280,000 (HOXA gene cluster)\nchrom = \"chr7\"\nregion_start = 27200000\nregion_end = 27280000\n\n# Track 1: Gene annotations\ngenes = pd.DataFrame(\n    {\n        \"name\": [\"HOXA1\", \"HOXA2\", \"HOXA3\", \"HOXA4\", \"HOXA5\"],\n        \"start\": [27204000, 27220000, 27238000, 27252000, 27264000],\n        \"end\": [27212000, 27229000, 27248000, 27260000, 27274000],\n        \"strand\": [\"+\", \"+\", \"+\", \"-\", \"+\"],\n    }\n)\n\n# Exons within each gene\nexons = pd.DataFrame(\n    {\n        \"gene\": [\n            \"HOXA1\",\n            \"HOXA1\",\n            \"HOXA1\",\n            \"HOXA2\",\n            \"HOXA2\",\n            \"HOXA3\",\n            \"HOXA3\",\n            \"HOXA3\",\n            \"HOXA3\",\n            \"HOXA4\",\n            \"HOXA4\",\n            \"HOXA4\",\n            \"HOXA5\",\n            \"HOXA5\",\n            \"HOXA5\",\n        ],\n        \"start\": [\n            27204000,\n            27206500,\n            27210000,\n            27220000,\n            27225000,\n            27238000,\n            27241000,\n            27244000,\n            27246500,\n            27252000,\n            27255000,\n            27258000,\n            27264000,\n            27268000,\n            27271500,\n        ],\n        \"end\": [\n            27205200,\n            27207800,\n            27212000,\n            27221500,\n            27229000,\n            27239500,\n            27242500,\n            27245200,\n            27248000,\n            27253500,\n            27256500,\n            27260000,\n            27265500,\n            27269500,\n            27274000,\n        ],\n    }\n)\n\n# Track 2: Coverage — simulated RNA-seq read depth\ncoverage_positions = np.arange(region_start, region_end, 200)\nbase_coverage = np.random.exponential(5, len(coverage_positions))\nfor _, exon in exons.iterrows():\n    mask = (coverage_positions >= exon[\"start\"]) & (coverage_positions <= exon[\"end\"])\n    base_coverage[mask] += np.random.exponential(40, mask.sum())\ncoverage_df = pd.DataFrame({\"position\": coverage_positions, \"depth\": np.clip(base_coverage, 0, 120)})\n\n# Track 3: Variants (SNPs and indels)\nvariant_positions = np.sort(np.random.choice(range(region_start + 1000, region_end - 1000), size=18, replace=False))\nvariant_types = np.random.choice([\"SNP\", \"SNP\", \"SNP\", \"Indel\"], size=18)\nvariant_quality = np.random.uniform(20, 99, size=18)\nvariants_df = pd.DataFrame({\"position\": variant_positions, \"type\": variant_types, \"quality\": variant_quality})\n\n# Track 4: Regulatory elements\nregulatory = pd.DataFrame(\n    {\n        \"start\": [27201000, 27215000, 27233000, 27249000, 27262000, 27275000],\n        \"end\": [27203000, 27218000, 27236000, 27251000, 27263500, 27278000],\n        \"element\": [\"Promoter\", \"Enhancer\", \"Promoter\", \"Enhancer\", \"Promoter\", \"Enhancer\"],\n    }\n)\n\n# Track layout\ntracks = {\n    \"Genes\": {\"center\": 4.2, \"half_h\": 0.42, \"pad_bot\": 0.25, \"pad_top\": 0.55},\n    \"Coverage\": {\"center\": 2.7, \"half_h\": 0.65, \"pad_bot\": 0.25, \"pad_top\": 0.30},\n    \"Variants\": {\"center\": 1.3, \"half_h\": 0.42, \"pad_bot\": 0.25, \"pad_top\": 0.30},\n    \"Regulatory\": {\"center\": 0.0, \"half_h\": 0.42, \"pad_bot\": 0.25, \"pad_top\": 0.30},\n}\n\nscale = 1000.0\nx_start = region_start / scale\nx_end = region_end / scale\n\n# Gene track data\nt = tracks[\"Genes\"]\ngene_intron_df = pd.DataFrame(\n    {\"x\": genes[\"start\"] / scale, \"xend\": genes[\"end\"] / scale, \"y\": t[\"center\"], \"yend\": t[\"center\"]}\n)\nexon_df = pd.DataFrame(\n    {\n        \"xmin\": exons[\"start\"] / scale,\n        \"xmax\": exons[\"end\"] / scale,\n        \"ymin\": t[\"center\"] - t[\"half_h\"],\n        \"ymax\": t[\"center\"] + t[\"half_h\"],\n        \"gene\": exons[\"gene\"],\n    }\n)\nexon_df[\"exon_size\"] = ((exons[\"end\"] - exons[\"start\"]) / 1000).round(1).astype(str) + \" kb\"\ngene_label_df = pd.DataFrame(\n    {\"x\": (genes[\"start\"] + genes[\"end\"]) / 2 / scale, \"y\": t[\"center\"] + t[\"half_h\"] + 0.28, \"label\": genes[\"name\"]}\n)\nstrand_arrows = []\nfor _, g in genes.iterrows():\n    mid = (g[\"start\"] + g[\"end\"]) / 2 / scale\n    arrow_char = \"▶\" if g[\"strand\"] == \"+\" else \"◀\"\n    strand_arrows.append({\"x\": mid, \"y\": t[\"center\"] - t[\"half_h\"] - 0.24, \"label\": arrow_char})\nstrand_df = pd.DataFrame(strand_arrows)\n\n# Coverage track data\nt = tracks[\"Coverage\"]\ncov_plot_df = coverage_df.copy()\ncov_plot_df[\"x\"] = cov_plot_df[\"position\"] / scale\nmax_depth = cov_plot_df[\"depth\"].max()\ncov_plot_df[\"y\"] = t[\"center\"] - t[\"half_h\"] + (cov_plot_df[\"depth\"] / max_depth) * (2 * t[\"half_h\"])\ncov_plot_df[\"ybase\"] = t[\"center\"] - t[\"half_h\"]\ncov_plot_df[\"depth_label\"] = cov_plot_df[\"depth\"].round(1).astype(str) + \"x\"\ncov_plot_df[\"pos_label\"] = (cov_plot_df[\"position\"] / 1000).round(1).astype(str) + \" kb\"\n\n# Variant track data\nt = tracks[\"Variants\"]\nvar_plot_df = variants_df.copy()\nvar_plot_df[\"x\"] = var_plot_df[\"position\"] / scale\nvar_plot_df[\"y_base\"] = t[\"center\"]\nmin_stem = 0.5  # raised from 0.3 — improves low-quality variant visibility\nmax_stem = 2 * t[\"half_h\"] + 0.15\nvar_plot_df[\"y_top\"] = t[\"center\"] + min_stem * max_stem + (var_plot_df[\"quality\"] / 100) * (1 - min_stem) * max_stem\nvar_plot_df[\"qual_label\"] = \"QUAL: \" + var_plot_df[\"quality\"].round(1).astype(str)\nvar_plot_df[\"pos_label\"] = (var_plot_df[\"position\"] / 1000).round(1).astype(str) + \" kb\"\n\n# Regulatory track data\nt = tracks[\"Regulatory\"]\nreg_plot_df = pd.DataFrame(\n    {\n        \"xmin\": regulatory[\"start\"] / scale,\n        \"xmax\": regulatory[\"end\"] / scale,\n        \"ymin\": t[\"center\"] - t[\"half_h\"],\n        \"ymax\": t[\"center\"] + t[\"half_h\"],\n        \"element\": regulatory[\"element\"],\n        \"size_label\": ((regulatory[\"end\"] - regulatory[\"start\"]) / 1000).round(1).astype(str) + \" kb\",\n    }\n)\n\n# Track background shading (alternating light/dark)\nbg_rows = []\nfor name, t in tracks.items():\n    bg_rows.append(\n        {\n            \"xmin\": x_start,\n            \"xmax\": x_end,\n            \"ymin\": t[\"center\"] - t[\"half_h\"] - t[\"pad_bot\"],\n            \"ymax\": t[\"center\"] + t[\"half_h\"] + t[\"pad_top\"],\n            \"track\": name,\n        }\n    )\ntrack_bg = pd.DataFrame(bg_rows)\n\n# Divider lines\ntrack_order = [\"Regulatory\", \"Variants\", \"Coverage\", \"Genes\"]\ndivider_positions = []\nfor i in range(len(track_order) - 1):\n    lo = tracks[track_order[i]]\n    hi = tracks[track_order[i + 1]]\n    y_mid = (lo[\"center\"] + lo[\"half_h\"] + lo[\"pad_top\"] + hi[\"center\"] - hi[\"half_h\"] - hi[\"pad_bot\"]) / 2\n    divider_positions.append(y_mid)\ndivider_df = pd.DataFrame({\"x\": [x_start] * 3, \"xend\": [x_end] * 3, \"y\": divider_positions, \"yend\": divider_positions})\n\n# Track labels shifted right to clear leftmost data elements\ntrack_labels_df = pd.DataFrame(\n    {\"x\": [x_start + 3.5] * 4, \"y\": [tracks[n][\"center\"] for n in track_order], \"label\": track_order}\n)\n\n# Highlight region: variant-dense area near HOXA3\nhighlight_df = pd.DataFrame(\n    {\n        \"xmin\": [27241.0],\n        \"xmax\": [27243.0],\n        \"ymin\": [tracks[\"Regulatory\"][\"center\"] - tracks[\"Regulatory\"][\"half_h\"] - tracks[\"Regulatory\"][\"pad_bot\"]],\n        \"ymax\": [tracks[\"Genes\"][\"center\"] + tracks[\"Genes\"][\"half_h\"] + tracks[\"Genes\"][\"pad_top\"]],\n    }\n)\n\n# Peak coverage annotation\npeak_idx = cov_plot_df[\"depth\"].idxmax()\npeak_x = cov_plot_df.loc[peak_idx, \"x\"]\npeak_depth = cov_plot_df.loc[peak_idx, \"depth\"]\nt = tracks[\"Coverage\"]\npeak_annotation_df = pd.DataFrame(\n    {\"x\": [peak_x + 2.0], \"y\": [t[\"center\"] + t[\"half_h\"] - 0.05], \"label\": [f\"Peak: {peak_depth:.0f}x\"]}\n)\n\n# Colors — Imprint palette assignments (fixes green/red colorblind pair)\nexon_color = IMPRINT[2]  # #4467A3 blue — gene exons (structural)\ncov_fill = IMPRINT[0]  # #009E73 green — coverage (first categorical series)\nsnp_color = IMPRINT[1]  # #C475FD lavender — SNP\nindel_color = IMPRINT[4]  # #AE3030 red — Indel (semantic bad/error)\npromoter_color = IMPRINT[3]  # #BD8233 ochre — Promoter (replaces green, fixes colorblind issue)\nenhancer_color = IMPRINT[5]  # #2ABCCD cyan — Enhancer\n\n# Highlight colors: amber (theme-independent warning/focus)\nhighlight_fill = \"#FFF8E1\" if THEME == \"light\" else \"#2A2000\"\nhighlight_border = AMBER\n\ntick_positions = list(range(int(x_start), int(x_end) + 1, 20))\ntick_labels = [f\"{v} kb\" for v in tick_positions]\n\n# Title: genome-track-multi · python · letsplot · anyplot.ai  (51 chars < 67 baseline, no scaling needed)\ntitle_str = \"genome-track-multi · python · letsplot · anyplot.ai\"\n\nplot = (\n    ggplot()\n    # Highlight region\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        data=highlight_df,\n        fill=highlight_fill,\n        color=highlight_border,\n        size=0.5,\n        alpha=0.4,\n    )\n    # Track background shading\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        data=track_bg,\n        fill=ELEVATED_BG,\n        color=INK_MUTED,\n        size=0.2,\n        alpha=0.5,\n    )\n    # Divider lines\n    + geom_segment(\n        aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), data=divider_df, color=INK_SOFT, size=0.3, linetype=\"dashed\"\n    )\n    # Gene track: intron lines\n    + geom_segment(aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), data=gene_intron_df, color=exon_color, size=1.0)\n    # Gene track: exon rectangles\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        data=exon_df,\n        fill=exon_color,\n        color=INK,\n        size=0.5,\n        alpha=0.85,\n        tooltips=layer_tooltips().line(\"Gene: @gene\").line(\"Size: @exon_size\"),\n    )\n    # Gene labels (italic, above exons)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=gene_label_df, size=4, color=INK, fontface=\"italic\")\n    # Strand direction arrows — size raised to 3.5 mm for legibility\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=strand_df, size=3.5, color=INK_SOFT)\n    # Coverage: filled area\n    + geom_ribbon(\n        aes(x=\"x\", ymin=\"ybase\", ymax=\"y\"),\n        data=cov_plot_df,\n        fill=cov_fill,\n        color=cov_fill,\n        alpha=0.55,\n        size=0.4,\n        tooltips=layer_tooltips().line(\"Position: @pos_label\").line(\"Depth: @depth_label\"),\n    )\n    # Coverage peak annotation\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=peak_annotation_df, size=3.5, color=INK, fontface=\"bold\")\n    # Variant lollipop stems\n    + geom_segment(aes(x=\"x\", xend=\"x\", y=\"y_base\", yend=\"y_top\", color=\"type\"), data=var_plot_df, size=1.0)\n    # Variant lollipop heads\n    + geom_point(\n        aes(x=\"x\", y=\"y_top\", color=\"type\"),\n        data=var_plot_df,\n        size=5,\n        stroke=0.8,\n        tooltips=layer_tooltips().line(\"@type\").line(\"@qual_label\").line(\"Position: @pos_label\"),\n    )\n    # Regulatory elements\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"element\"),\n        data=reg_plot_df,\n        alpha=0.85,\n        size=0.4,\n        color=PAGE_BG,\n        tooltips=layer_tooltips().line(\"@element\").line(\"Size: @size_label\"),\n    )\n    # Track labels at center y — avoids overlap with gene names at top of Genes track\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=track_labels_df, size=4.5, color=INK, fontface=\"bold\", hjust=0)\n    # Scales\n    + scale_color_manual(values={\"SNP\": snp_color, \"Indel\": indel_color}, name=\"Variant Type\")\n    + scale_fill_manual(values={\"Promoter\": promoter_color, \"Enhancer\": enhancer_color}, name=\"Regulatory\")\n    + scale_x_continuous(\n        name=f\"Genomic Position ({chrom})\", breaks=tick_positions, labels=tick_labels, expand=[0.01, 0.01]\n    )\n    + scale_y_continuous(expand=[0.04, 0.04])\n    + labs(\n        title=title_str,\n        subtitle=\"HOXA Gene Cluster — chr7:27,200–27,280 kb  |  Highlight: variant-rich region near HOXA3\",\n    )\n    + coord_cartesian(xlim=[x_start - 1, x_end + 1])\n    + theme_minimal()\n    + theme(\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        plot_subtitle=element_text(size=11, color=INK_SOFT),\n        axis_title_x=element_text(size=12, color=INK),\n        axis_title_y=element_blank(),\n        axis_text_x=element_text(size=10, color=INK_SOFT, angle=0),\n        axis_text_y=element_blank(),\n        axis_ticks_y=element_blank(),\n        panel_grid_major_x=element_line(color=INK_SOFT, size=0.15),\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor=element_blank(),\n        legend_title=element_text(size=10, face=\"bold\", color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"bottom\",\n        legend_box=\"horizontal\",\n        legend_direction=\"horizontal\",\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_margin=[10, 10, 5, 10],\n    )\n    + ggsize(800, 450)\n)\n\n# Save — scale=4 → 3200×1800 px\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}