{"spec_id":"sequence-logo-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nsequence-logo-basic: Sequence Logo for Motif Visualization\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_cartesian,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_rect,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    scale_size_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\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 palette — semantic mapping for standard DNA color convention\n# A=green, C=blue, G=orange/ochre, T=red (domain standard, semantic exception applies)\ndna_colors = {\n    \"A\": \"#009E73\",  # Imprint position 1 — green\n    \"C\": \"#4467A3\",  # Imprint position 3 — blue\n    \"G\": \"#BD8233\",  # Imprint position 4 — ochre\n    \"T\": \"#AE3030\",  # Imprint position 5 — matte red\n}\n\n# Data — 10-position DNA transcription factor binding site motif\nposition_freqs = {\n    1: {\"A\": 0.05, \"C\": 0.80, \"G\": 0.05, \"T\": 0.10},\n    2: {\"A\": 0.70, \"C\": 0.05, \"G\": 0.20, \"T\": 0.05},\n    3: {\"A\": 0.05, \"C\": 0.05, \"G\": 0.85, \"T\": 0.05},\n    4: {\"A\": 0.10, \"C\": 0.10, \"G\": 0.10, \"T\": 0.70},\n    5: {\"A\": 0.25, \"C\": 0.25, \"G\": 0.25, \"T\": 0.25},\n    6: {\"A\": 0.60, \"C\": 0.10, \"G\": 0.20, \"T\": 0.10},\n    7: {\"A\": 0.05, \"C\": 0.05, \"G\": 0.05, \"T\": 0.85},\n    8: {\"A\": 0.90, \"C\": 0.02, \"G\": 0.05, \"T\": 0.03},\n    9: {\"A\": 0.05, \"C\": 0.10, \"G\": 0.75, \"T\": 0.10},\n    10: {\"A\": 0.15, \"C\": 0.55, \"G\": 0.15, \"T\": 0.15},\n}\n\n# Compute information content and build stacked letter segments\nrecords = []\nic_values = {}\nfor pos, freqs in position_freqs.items():\n    ic = 2.0 + sum(f * np.log2(f) for f in freqs.values() if f > 0)\n    ic_values[pos] = max(ic, 0.0)\n    sorted_letters = sorted(freqs.items(), key=lambda x: x[1])  # most frequent on top\n    y_bottom = 0.0\n    for letter, freq in sorted_letters:\n        height = freq * ic_values[pos]\n        if height > 0:\n            records.append(\n                {\n                    \"position\": pos,\n                    \"letter\": letter,\n                    \"ymin\": y_bottom,\n                    \"ymax\": y_bottom + height,\n                    \"y_mid\": y_bottom + height / 2,\n                    \"height\": height,\n                }\n            )\n            y_bottom += height\n\ndf = pd.DataFrame(records)\nbar_half_width = 0.40  # Reduced from 0.44 to add breathing room between adjacent glyph columns\ndf[\"xmin\"] = df[\"position\"] - bar_half_width\ndf[\"xmax\"] = df[\"position\"] + bar_half_width\n\n# Significantly conserved positions (IC >= 1 bit) — highlighted for data storytelling\nconserved_threshold = 1.0\nconserved_positions = sorted([pos for pos, ic in ic_values.items() if ic >= conserved_threshold])\n\n# Scale font size to approximate stretched-glyph fill for each segment.\n# Calibrated for (8, 4.5) canvas: 80% fill target with 0.7 cap-height factor.\nmax_ic = max(ic_values.values())\ny_range_bits = max_ic * 1.08\nplot_area_mm = 4.5 * 25.4 * 0.78  # ~89 mm usable plot height\nfont_scale_mm = plot_area_mm * 0.8 / (y_range_bits * 0.7)\ndf[\"fontsize\"] = df[\"height\"] * font_scale_mm\n\n# Only label segments tall enough to avoid crowding at bottom of stack\ndf_labels = df[df[\"height\"] > 0.12].copy()\n\ntitle = \"sequence-logo-basic · python · plotnine · anyplot.ai\"\nsubtitle = \"TF binding site motif — 10 positions, DNA (max 2 bits)\"\n\n# Build plot — uses plotnine's annotate() and geom_hline() for conservation storytelling\nplot = (\n    ggplot(df)\n    # Dashed threshold line marks the 1-bit conservation boundary\n    + geom_hline(yintercept=conserved_threshold, color=INK_MUTED, size=0.5, linetype=\"dashed\")\n    + annotate(\n        \"text\", x=9.5, y=conserved_threshold + 0.06, label=\">= 1 bit: conserved\", color=INK_MUTED, size=2.5, ha=\"right\"\n    )\n    + geom_rect(aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"letter\"), color=PAGE_BG, size=0.3)\n    + geom_text(\n        aes(x=\"position\", y=\"y_mid\", label=\"letter\", size=\"fontsize\"),\n        data=df_labels,\n        color=\"#F0EFE8\",  # near-white for contrast on colored rectangles\n        fontweight=\"bold\",\n        show_legend=False,\n    )\n    + scale_fill_manual(values=dna_colors, name=\"Nucleotide\")\n    + scale_size_identity()\n    + scale_x_continuous(breaks=list(range(1, 11)), minor_breaks=[])\n    + scale_y_continuous(expand=(0, 0, 0.22, 0), breaks=[0, 0.5, 1.0, 1.5])\n    + coord_cartesian(ylim=(0, None))\n    + labs(x=\"Position\", y=\"Information content (bits)\", title=title, subtitle=subtitle)\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_title=element_text(size=12, color=INK, fontweight=\"bold\"),\n        plot_subtitle=element_text(size=8, color=INK_MUTED),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=9, color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_border=element_blank(),\n        axis_line_x=element_line(color=INK_SOFT, size=0.6),\n        axis_line_y=element_line(color=INK_SOFT, size=0.6),\n        plot_background=element_rect(fill=PAGE_BG, color=None),\n        panel_background=element_rect(fill=PAGE_BG, color=None),\n        legend_background=element_rect(fill=ELEVATED_BG, color=None),\n    )\n)\n\n# Add triangle markers above each significantly conserved position using plotnine's annotate()\nfor pos in conserved_positions:\n    plot = plot + annotate(\"text\", x=pos, y=ic_values[pos] + 0.06, label=\"▲\", color=INK_SOFT, size=3)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}