{"spec_id":"sequence-logo-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nsequence-logo-basic: Sequence Logo for Motif Visualization\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport matplotlib.transforms as mtransforms\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap\nfrom matplotlib.font_manager import FontProperties\nfrom matplotlib.patches import PathPatch\nfrom matplotlib.textpath import TextPath\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\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK_SOFT,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — 10-position DNA motif; conserved core at positions 2–5, peak at position 3 (strong G)\nbases = [\"A\", \"C\", \"G\", \"T\"]\nfrequencies = np.array(\n    [\n        [0.30, 0.25, 0.30, 0.15],  # pos 1: moderate spread\n        [0.05, 0.10, 0.05, 0.80],  # pos 2: strong T — core start\n        [0.03, 0.04, 0.90, 0.03],  # pos 3: very strong G — core peak\n        [0.75, 0.10, 0.10, 0.05],  # pos 4: strong A — core\n        [0.10, 0.60, 0.20, 0.10],  # pos 5: moderate C — core end\n        [0.25, 0.25, 0.30, 0.20],  # pos 6: low conservation\n        [0.55, 0.15, 0.20, 0.10],  # pos 7: moderate A\n        [0.26, 0.24, 0.26, 0.24],  # pos 8: near uniform\n        [0.10, 0.15, 0.10, 0.65],  # pos 9: moderate T\n        [0.05, 0.80, 0.10, 0.05],  # pos 10: strong C\n    ]\n)\nn_positions = frequencies.shape[0]\n\n# Information content (bits) per position: IC = 2 + sum(f * log2(f))\ninfo_content = np.zeros(n_positions)\nfor i in range(n_positions):\n    entropy = sum(f * np.log2(f) for f in frequencies[i] if f > 0)\n    info_content[i] = 2.0 + entropy\n\n# Imprint palette for DNA bases (semantic mapping: A=green, C=blue, G=ochre, T=red)\nbase_colors = {\"A\": \"#009E73\", \"C\": \"#4467A3\", \"G\": \"#BD8233\", \"T\": \"#AE3030\"}\n\n# Imprint sequential colormap for frequency heatmap\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\n# Frequency DataFrame for heatmap panel\nfreq_df = pd.DataFrame(frequencies.T, index=bases, columns=range(1, n_positions + 1))\n\n# Canvas: 3200×1800 px (landscape), two-panel layout\nfig, (ax_logo, ax_heat) = plt.subplots(2, 1, figsize=(8, 4.5), dpi=400, height_ratios=[3.5, 1], facecolor=PAGE_BG)\nfig.subplots_adjust(left=0.09, right=0.87, top=0.91, bottom=0.14, hspace=0.70)\nax_logo.set_facecolor(PAGE_BG)\nax_heat.set_facecolor(PAGE_BG)\n\n# Sequence logo — letter glyphs via TextPath/PathPatch\nfp = FontProperties(family=\"monospace\", weight=\"bold\")\nletter_width = 0.78\n\nfor pos in range(n_positions):\n    ic = info_content[pos]\n    letter_heights = frequencies[pos] * ic\n    sorted_indices = np.argsort(letter_heights)\n    y_offset = 0.0\n\n    for idx in sorted_indices:\n        height = letter_heights[idx]\n        if height < 0.01:\n            continue\n        letter = bases[idx]\n        color = base_colors[letter]\n        x_center = pos\n        x_left = x_center - letter_width / 2\n\n        tp = TextPath((0, 0), letter, size=1, prop=fp)\n        bbox = tp.get_extents()\n        if bbox.width == 0 or bbox.height == 0:\n            continue\n\n        scale_x = letter_width / bbox.width\n        scale_y = height / bbox.height\n        tx = x_left - bbox.x0 * scale_x\n        ty = y_offset - bbox.y0 * scale_y\n\n        transform = mtransforms.Affine2D().scale(scale_x, scale_y).translate(tx, ty) + ax_logo.transData\n        patch = PathPatch(tp, facecolor=color, edgecolor=\"none\", transform=transform)\n        ax_logo.add_patch(patch)\n        y_offset += height\n\n# Logo axis styling\nax_logo.set_xlim(-0.6, n_positions - 0.4)\nax_logo.set_ylim(0, 2.1)\nax_logo.set_xticks(range(n_positions))\nax_logo.set_xticklabels(range(1, n_positions + 1))\nax_logo.set_title(\n    \"sequence-logo-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, pad=8\n)\nax_logo.set_xlabel(\"Position\", fontsize=10, color=INK)\nax_logo.set_ylabel(\"Information content (bits)\", fontsize=10, color=INK)\nax_logo.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nsns.despine(ax=ax_logo, top=True, right=True)\nax_logo.yaxis.grid(True, alpha=0.15, linewidth=0.5, color=INK_SOFT)\nax_logo.set_axisbelow(True)\n\n# Highlight the most conserved position (position 3 — strong G)\nmax_ic_pos = int(np.argmax(info_content))\nax_logo.axvspan(max_ic_pos - 0.42, max_ic_pos + 0.42, color=INK_MUTED, alpha=0.12, zorder=0)\nax_logo.annotate(\n    f\"Most conserved\\n({info_content[max_ic_pos]:.1f} bits)\",\n    xy=(max_ic_pos, info_content[max_ic_pos]),\n    xytext=(max_ic_pos + 3.0, 1.80),\n    fontsize=7,\n    fontstyle=\"italic\",\n    color=INK_SOFT,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_MUTED, \"lw\": 0.8, \"connectionstyle\": \"arc3,rad=0.3\"},\n    ha=\"center\",\n    va=\"center\",\n)\n\n# Frequency heatmap (seaborn panel — Imprint sequential colormap)\nsns.heatmap(\n    freq_df,\n    ax=ax_heat,\n    cmap=imprint_seq,\n    annot=True,\n    fmt=\".2f\",\n    annot_kws={\"size\": 5, \"color\": INK_SOFT},\n    linewidths=0.3,\n    linecolor=PAGE_BG,\n    cbar_kws={\"label\": \"Freq.\", \"shrink\": 0.85, \"aspect\": 12, \"pad\": 0.02},\n    vmin=0,\n    vmax=1,\n)\nax_heat.set_xlabel(\"Position\", fontsize=10, color=INK)\nax_heat.set_ylabel(\"\", fontsize=10)\nax_heat.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax_heat.tick_params(axis=\"y\", rotation=0)\nsns.despine(ax=ax_heat, top=True, right=True, left=True, bottom=True)\n\n# Color y-axis labels to match DNA color scheme\n# In dark theme, #4467A3 (C=blue) has ~3:1 contrast against #1A1A17 — use INK_SOFT instead\nfor tick_label in ax_heat.get_yticklabels():\n    base = tick_label.get_text()\n    if base in base_colors:\n        if THEME == \"dark\" and base == \"C\":\n            tick_label.set_color(INK_SOFT)\n        else:\n            tick_label.set_color(base_colors[base])\n        tick_label.set_fontweight(\"bold\")\n\n# Theme-adaptive colorbar text\ncbar = ax_heat.collections[0].colorbar\nif cbar is not None:\n    cbar.ax.yaxis.label.set_color(INK_SOFT)\n    cbar.ax.tick_params(colors=INK_SOFT, labelsize=7)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}