{"spec_id":"sequence-logo-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nsequence-logo-basic: Sequence Logo for Motif Visualization\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove the script's directory from sys.path so `import pygal` finds the\n# installed package rather than this file (which shares the package name).\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or os.getcwd()) != _this_dir]\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens (Imprint palette — default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# DNA nucleotide colors — semantic exception: standard bioinformatics DNA color\n# convention maps exactly to Imprint palette members\nDNA_COLORS = {\n    \"A\": \"#009E73\",  # Imprint palette pos 1 (brand green) — adenine\n    \"C\": \"#4467A3\",  # Imprint palette pos 3 (blue) — cytosine\n    \"G\": \"#BD8233\",  # Imprint palette pos 4 (ochre) — guanine\n    \"T\": \"#AE3030\",  # Imprint palette pos 5 (matte red) — thymine\n}\nANYPLOT_AMBER = \"#DDCC77\"  # caution/highlight — TATA core marker (outside categorical pool)\n\n# Data — TATA-box promoter element, 10-position motif\n# Positions 2-7 form the conserved TATAAA core recognized by TATA-binding protein (TBP)\nfrequencies = {\n    1: {\"A\": 0.28, \"C\": 0.22, \"G\": 0.22, \"T\": 0.28},\n    2: {\"A\": 0.05, \"C\": 0.05, \"G\": 0.05, \"T\": 0.85},\n    3: {\"A\": 0.85, \"C\": 0.05, \"G\": 0.05, \"T\": 0.05},\n    4: {\"A\": 0.05, \"C\": 0.05, \"G\": 0.05, \"T\": 0.85},\n    5: {\"A\": 0.80, \"C\": 0.05, \"G\": 0.10, \"T\": 0.05},\n    6: {\"A\": 0.78, \"C\": 0.05, \"G\": 0.12, \"T\": 0.05},\n    7: {\"A\": 0.75, \"C\": 0.05, \"G\": 0.15, \"T\": 0.05},\n    8: {\"A\": 0.10, \"C\": 0.10, \"G\": 0.65, \"T\": 0.15},\n    9: {\"A\": 0.25, \"C\": 0.30, \"G\": 0.20, \"T\": 0.25},\n    10: {\"A\": 0.30, \"C\": 0.20, \"G\": 0.25, \"T\": 0.25},\n}\n\nnucleotides = [\"A\", \"C\", \"G\", \"T\"]\nmax_entropy = 2.0  # DNA: 4 nucleotides → max 2 bits\n\n# Information content per position (IC = max_entropy - Shannon entropy)\ninfo_content = {}\nfor pos, freqs in frequencies.items():\n    entropy = sum(-f * np.log2(f) for f in freqs.values() if f > 0)\n    info_content[pos] = max(0.0, max_entropy - entropy)\n\n# Conserved positions (IC > 0.5 bits)\ncore_positions = {pos for pos, ic in info_content.items() if ic > 0.5}\n\n# Sort nucleotides ascending by IC-weighted average frequency so the nucleotide\n# dominant at high-conservation positions rises to the top of the global stack.\n# pygal StackedBar uses one fixed stacking order across all positions — true\n# per-position reordering (canonical for sequence logos) requires custom SVG.\ntotal_ic = sum(info_content.values()) or 1.0\nic_weighted_freq = {\n    nt: sum(frequencies[pos][nt] * info_content[pos] for pos in frequencies) / total_ic for nt in nucleotides\n}\nnucleotides_sorted = sorted(nucleotides, key=lambda nt: ic_weighted_freq[nt])\n\n# Build per-nucleotide stacked data (heights scaled by freq × IC)\nstacked_data = {nt: [] for nt in nucleotides_sorted}\nfor pos in sorted(frequencies):\n    ic = info_content[pos]\n    for nt in nucleotides_sorted:\n        height = round(frequencies[pos][nt] * ic, 4)\n        show_letter = height >= 0.08\n        stacked_data[nt].append(\n            {\n                \"value\": height,\n                \"label\": f\"Pos {pos}: {nt} = {frequencies[pos][nt]:.0%}, IC={ic:.2f} bits\",\n                \"formatter\": (lambda x, letter=nt, show=show_letter: letter if show else \"\"),\n            }\n        )\n\n# TATA core visual marker: amber cap on conserved positions highlights the binding site\ntata_cap = [\n    {\n        \"value\": 0.05,\n        \"color\": ANYPLOT_AMBER,\n        \"label\": \"TATA-box core — TBP binding site (IC > 0.5 bits)\",\n        \"formatter\": lambda x: \"\",  # suppress numeric value label on the marker cap\n    }\n    if pos in core_positions\n    else None\n    for pos in sorted(frequencies)\n]\n\n# X-axis labels — mark conserved positions (IC > 0.5 bits)\nx_labels = [f\"*{pos}*\" if pos in core_positions else str(pos) for pos in sorted(frequencies)]\n\n# Y-axis upper bound with headroom above the tallest bar (including TATA cap)\ny_max = round(max(info_content.values()) + 0.35, 1)\n\n# Imprint palette style — theme-adaptive chrome; amber appended for TATA core series\ncolors_in_order = tuple(DNA_COLORS[nt] for nt in nucleotides_sorted) + (ANYPLOT_AMBER,)\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=colors_in_order,\n    opacity=0.92,\n    opacity_hover=1.0,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=48,\n    title_font_family=\"sans-serif\",\n    label_font_family=\"sans-serif\",\n    major_label_font_family=\"sans-serif\",\n    legend_font_family=\"sans-serif\",\n    value_font_family=\"monospace\",\n    tooltip_font_size=36,\n    tooltip_font_family=\"monospace\",\n)\n\n# Plot\ntitle = \"sequence-logo-basic · python · pygal · anyplot.ai\"\nchart = pygal.StackedBar(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title,\n    x_title=\"Position (* = conserved TATA core, IC > 0.5 bits)\",\n    y_title=\"Information content (bits)\",\n    show_x_guides=False,\n    show_y_guides=True,\n    show_minor_y_labels=False,\n    margin=80,\n    margin_bottom=120,\n    margin_top=100,\n    spacing=6,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=5,\n    legend_box_size=40,\n    print_values=True,\n    print_values_position=\"center\",\n    rounded_bars=4,\n    y_labels_major_count=5,\n    truncate_legend=-1,\n    tooltip_border_radius=10,\n    tooltip_fancy_mode=True,\n    min_scale=0,\n    range=(0, y_max),\n    x_label_rotation=0,\n    js=[],\n)\n\nchart.x_labels = x_labels\n\nfor nt in nucleotides_sorted:\n    chart.add(nt, stacked_data[nt])\n\n# Amber cap marks the TATA-box core (positions 2-7) as conserved binding site\nchart.add(\"▲ TATA core\", tata_cap)\n\n# Save PNG and HTML\nchart.render_to_png(f\"plot-{THEME}.png\")\nchart.render_to_file(f\"plot-{THEME}.html\")\n"}