{"spec_id":"sequence-logo-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nsequence-logo-basic: Sequence Logo for Motif Visualization\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# DNA sequence logo colors — Imprint palette, matching biological convention\n# A=green (#009E73), C=blue (#4467A3), G=ochre (#BD8233), T=red (#AE3030)\nDNA_COLORS = {\"A\": \"#009E73\", \"C\": \"#4467A3\", \"G\": \"#BD8233\", \"T\": \"#AE3030\"}\n\n# Data — transcription factor binding site motif (10-position DNA)\n# Each row: [A, C, G, T] frequencies summing to 1\npwm = np.array(\n    [\n        [0.05, 0.80, 0.05, 0.10],  # pos 1: C dominant\n        [0.10, 0.15, 0.10, 0.65],  # pos 2: T dominant\n        [0.02, 0.96, 0.01, 0.01],  # pos 3: C highly conserved (~1.8 bits)\n        [0.25, 0.25, 0.25, 0.25],  # pos 4: uniform (0 bits)\n        [0.70, 0.05, 0.15, 0.10],  # pos 5: A dominant\n        [0.10, 0.10, 0.70, 0.10],  # pos 6: G dominant\n        [0.001, 0.001, 0.001, 0.997],  # pos 7: T near-perfect conservation (~1.97 bits)\n        [0.60, 0.15, 0.15, 0.10],  # pos 8: A dominant\n        [0.10, 0.10, 0.65, 0.15],  # pos 9: G dominant\n        [0.15, 0.55, 0.10, 0.20],  # pos 10: C dominant\n    ]\n)\n\nletters = [\"A\", \"C\", \"G\", \"T\"]\nn_positions = len(pwm)\n\n# Information content: IC = 2 + sum(f * log2(f)) bits (max 2 bits for DNA)\ninfo_content = np.array([max(0.0, 2.0 + sum(f * np.log2(f) for f in row if f > 0)) for row in pwm])\n\n# Letter heights = frequency * information content at each position\nletter_heights = pwm * info_content[:, np.newaxis]\n\n# SVG glyph paths in normalized 0-1 unit square (x: 0=left, 1=right; y: 0=bottom, 1=top)\nGLYPH_PATHS = {\n    # A: apex at top (y=1), legs at bottom (y=0), crossbar at y≈0.3, inner hole above crossbar\n    \"A\": \"M 0.5 1 L 0.05 0 L 0.25 0 L 0.35 0.3 L 0.65 0.3 L 0.75 0 L 0.95 0 L 0.5 1 Z M 0.4 0.48 L 0.6 0.48 L 0.55 0.62 L 0.45 0.62 Z\",\n    \"C\": \"M 0.85 0.2 C 0.65 -0.05 0.2 0 0.1 0.3 C 0 0.6 0.15 0.95 0.5 1 C 0.7 1.02 0.85 0.9 0.88 0.8 L 0.68 0.7 C 0.6 0.82 0.5 0.82 0.4 0.78 C 0.28 0.7 0.25 0.5 0.3 0.35 C 0.35 0.2 0.5 0.15 0.6 0.18 C 0.68 0.2 0.72 0.28 0.75 0.32 Z\",\n    \"G\": \"M 0.85 0.2 C 0.65 -0.05 0.2 0 0.1 0.3 C 0 0.6 0.15 0.95 0.5 1 C 0.7 1.02 0.85 0.9 0.88 0.8 L 0.68 0.7 C 0.6 0.82 0.5 0.82 0.4 0.78 C 0.28 0.7 0.25 0.5 0.3 0.35 C 0.35 0.2 0.5 0.15 0.6 0.18 C 0.68 0.2 0.72 0.28 0.75 0.32 L 0.85 0.2 Z M 0.55 0.45 L 0.85 0.45 L 0.85 0.55 L 0.55 0.55 Z\",\n    # T: crossbar at top (y=0.76–1), stem extends down to y=0\n    \"T\": \"M 0.05 1 L 0.05 0.76 L 0.38 0.76 L 0.38 0 L 0.62 0 L 0.62 0.76 L 0.95 0.76 L 0.95 1 Z\",\n}\n\n# Plot\nfig = go.Figure()\nBAR_HALF_W = 0.38\n\nfor pos in range(n_positions):\n    sorted_idx = np.argsort(letter_heights[pos])\n    y_bottom = 0.0\n\n    for idx in sorted_idx:\n        h = float(letter_heights[pos][idx])\n        if h < 0.005:\n            continue\n        letter = letters[idx]\n\n        # Invisible bar carries hover tooltip for the nucleotide at this position\n        fig.add_trace(\n            go.Bar(\n                x=[pos + 1],\n                y=[h],\n                base=y_bottom,\n                width=BAR_HALF_W * 2,\n                marker={\"color\": \"rgba(0,0,0,0)\", \"line\": {\"width\": 0}},\n                showlegend=False,\n                hovertemplate=(\n                    f\"<b>Position {pos + 1}</b><br>\"\n                    f\"Nucleotide: {letter}<br>\"\n                    f\"Frequency: {pwm[pos][idx]:.0%}<br>\"\n                    f\"Height: {h:.3f} bits<extra></extra>\"\n                ),\n            )\n        )\n\n        # Transform normalized glyph path into data coordinates\n        tokens = GLYPH_PATHS[letter].split()\n        out, i, x0 = [], 0, (pos + 1) - BAR_HALF_W\n        while i < len(tokens):\n            cmd = tokens[i]\n            if cmd == \"Z\":\n                out.append(\"Z\")\n                i += 1\n            elif cmd in (\"M\", \"L\"):\n                out += [\n                    cmd,\n                    f\"{x0 + float(tokens[i + 1]) * 2 * BAR_HALF_W:.4f}\",\n                    f\"{y_bottom + float(tokens[i + 2]) * h:.4f}\",\n                ]\n                i += 3\n            elif cmd == \"C\":\n                out.append(\"C\")\n                for j in range(3):\n                    out += [\n                        f\"{x0 + float(tokens[i + 1 + j * 2]) * 2 * BAR_HALF_W:.4f}\",\n                        f\"{y_bottom + float(tokens[i + 2 + j * 2]) * h:.4f}\",\n                    ]\n                i += 7\n            else:\n                i += 1\n\n        fig.add_shape(\n            type=\"path\",\n            path=\" \".join(out),\n            fillcolor=DNA_COLORS[letter],\n            line={\"width\": 0.3, \"color\": DNA_COLORS[letter]},\n            layer=\"above\",\n            xref=\"x\",\n            yref=\"y\",\n        )\n        y_bottom += h\n\n# Legend entries (one square marker per nucleotide)\nfor letter in letters:\n    fig.add_trace(\n        go.Scatter(\n            x=[None],\n            y=[None],\n            mode=\"markers\",\n            marker={\"size\": 18, \"color\": DNA_COLORS[letter], \"symbol\": \"square\"},\n            name=f\"  {letter}  \",\n            showlegend=True,\n        )\n    )\n\n# Annotate highly conserved positions\nfor pos_idx in [2, 6]:\n    ic_val = info_content[pos_idx]\n    fig.add_annotation(\n        x=pos_idx + 1,\n        y=ic_val + 0.08,\n        text=f\"▼ {ic_val:.2f} bits\",\n        font={\"size\": 14, \"color\": INK, \"family\": \"Arial, sans-serif\"},\n        showarrow=False,\n        yanchor=\"bottom\",\n        xanchor=\"center\",\n    )\n\n# Mark zero-information position\nfig.add_annotation(\n    x=4,\n    y=-0.08,\n    text=\"no signal\",\n    font={\"size\": 12, \"color\": INK_MUTED, \"family\": \"Arial, sans-serif\"},\n    showarrow=False,\n    yanchor=\"top\",\n    xanchor=\"center\",\n)\n\ntitle_text = \"sequence-logo-basic · python · plotly · anyplot.ai\"\nn = len(title_text)\ntitle_size = max(11, round(16 * (67 / n if n > 67 else 1.0)))\n\n# Style\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    template=None,\n    barmode=\"overlay\",\n    bargap=0,\n    title={\n        \"text\": title_text,\n        \"font\": {\"size\": title_size, \"family\": \"Arial, Helvetica, sans-serif\", \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Position\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickvals\": list(range(1, n_positions + 1)),\n        \"showline\": True,\n        \"linewidth\": 1.5,\n        \"linecolor\": INK_SOFT,\n        \"mirror\": False,\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"ticks\": \"outside\",\n        \"ticklen\": 6,\n        \"tickwidth\": 1.2,\n        \"tickcolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Information content (bits)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"range\": [0, 2.15],\n        \"showline\": True,\n        \"linewidth\": 1.5,\n        \"linecolor\": INK_SOFT,\n        \"mirror\": False,\n        \"gridwidth\": 0.5,\n        \"gridcolor\": GRID,\n        \"zeroline\": True,\n        \"zerolinewidth\": 1.5,\n        \"zerolinecolor\": INK_SOFT,\n        \"ticks\": \"outside\",\n        \"ticklen\": 6,\n        \"tickwidth\": 1.2,\n        \"tickcolor\": INK_SOFT,\n        \"dtick\": 0.5,\n    },\n    legend={\n        \"font\": {\"size\": 10, \"family\": \"Arial Black, Impact, sans-serif\", \"color\": INK_SOFT},\n        \"orientation\": \"h\",\n        \"yanchor\": \"bottom\",\n        \"y\": 1.04,\n        \"xanchor\": \"center\",\n        \"x\": 0.5,\n        \"bgcolor\": \"rgba(0,0,0,0)\",\n        \"tracegroupgap\": 20,\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    hoverlabel={\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"font\": {\"size\": 12, \"family\": \"Arial, sans-serif\", \"color\": INK},\n    },\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}