{"spec_id":"sequence-logo-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# sequence-logo-basic: Sequence Logo for Motif Visualization\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-06-02\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# DNA letter colors — Imprint palette semantic exception (bioinformatics convention)\n# A=green, C=blue, G=ochre, T=red  (standard bioinformatics color scheme)\nconst DNA_COLORS = Dict(\n    \"A\" => colorant\"#009E73\",  # Imprint position 1 — brand green\n    \"C\" => colorant\"#4467A3\",  # Imprint position 3 — blue\n    \"G\" => colorant\"#BD8233\",  # Imprint position 4 — ochre\n    \"T\" => colorant\"#AE3030\",  # Imprint position 5 — matte red\n)\nconst LETTERS = [\"A\", \"C\", \"G\", \"T\"]\n\n# TATA-box transcription factor binding site: 10-position DNA motif\n# Rows: [freq_A, freq_C, freq_G, freq_T] summing to 1.0 per position\nconst MOTIF_FREQS = [\n    [0.10, 0.10, 0.10, 0.70],   # 1  — T-dominant\n    [0.80, 0.05, 0.10, 0.05],   # 2  — A-dominant\n    [0.05, 0.05, 0.10, 0.80],   # 3  — T-dominant\n    [0.75, 0.10, 0.05, 0.10],   # 4  — A-dominant\n    [0.60, 0.15, 0.15, 0.10],   # 5  — A-dominant\n    [0.65, 0.10, 0.15, 0.10],   # 6  — A-dominant\n    [0.15, 0.30, 0.30, 0.25],   # 7  — mixed (less conserved)\n    [0.05, 0.05, 0.80, 0.10],   # 8  — G-dominant\n    [0.10, 0.10, 0.05, 0.75],   # 9  — T-dominant\n    [0.55, 0.15, 0.20, 0.10],   # 10 — A-dominant\n]\n\nn_pos = length(MOTIF_FREQS)\n\n# Information content: IC = 2 + Σ f·log2(f)  (bits; max = 2 for DNA)\nic_vals = [begin\n    ic = 2.0\n    for f in freqs\n        f > 0 && (ic += f * log2(f))\n    end\n    max(0.0, ic)\nend for freqs in MOTIF_FREQS]\n\n# Per-position stacks: sorted ascending by contribution (least → bottom, most → top)\nstack_data = [begin\n    ic = ic_vals[p]\n    items = [(LETTERS[i], MOTIF_FREQS[p][i] * ic)\n             for i in eachindex(LETTERS) if MOTIF_FREQS[p][i] > 0.005]\n    sort!(items; by = x -> x[2])\n    out = Tuple{String, Float64, Float64}[]\n    y = 0.0\n    for (ltr, contrib) in items\n        push!(out, (ltr, contrib, y))\n        y += contrib\n    end\n    out\nend for p in 1:n_pos]\n\n# Title with font-size scaled for length\ntitle_str  = \"TATA-box Motif · sequence-logo-basic · julia · makie · anyplot.ai\"\nn_chars    = length(title_str)\ntitle_size = max(12, round(Int, 20 * min(1.0, 67.0 / n_chars)))\n\n# Scaled-glyph font-size constants: axis height ≈ 70% of 900 pts canvas\n# Y-range = 2 bits → ~315 pts/bit; fill 80% of each bar's height with the letter\nconst PTS_PER_BIT = 315.0\nconst FILL_FACTOR = 0.80\n\n# Figure\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = title_str,\n    titlesize          = Float32(title_size),\n    titlecolor         = INK,\n    xlabel             = \"Position\",\n    ylabel             = \"Information content (bits)\",\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n    xticks             = 1:n_pos,\n    yticks             = 0.0:0.5:2.0,\n    limits             = (0.35, n_pos + 0.65, -0.05, 2.15),\n)\n\n# Subtle shaded band highlighting the TATA-box conserved core (positions 2–6)\npoly!(ax, Rect2f(1.5, 0.0, 5.0, 2.0);\n      color       = RGBAf(INK.r, INK.g, INK.b, 0.04),\n      strokewidth = 0.8,\n      strokecolor = RGBAf(INK.r, INK.g, INK.b, 0.10))\n\n# Sequence logo: stacked colored rectangles + proportionally-scaled letter glyphs\nbar_w = 0.88\n\nfor (pos, stacks) in enumerate(stack_data)\n    for (letter, contrib, y_bot) in stacks\n        contrib < 0.01 && continue\n        poly!(ax, Rect2f(pos - bar_w / 2, y_bot, bar_w, contrib);\n              color       = DNA_COLORS[letter],\n              strokewidth = 0.4,\n              strokecolor = PAGE_BG)\n        # Scaled-glyph rendering: fontsize grows with bar height\n        if contrib > 0.025\n            glyph_size = max(6, round(Int, contrib * PTS_PER_BIT * FILL_FACTOR))\n            text!(ax, Float64(pos), y_bot + contrib / 2;\n                  text     = letter,\n                  color    = (:white, 0.92),\n                  fontsize = glyph_size,\n                  align    = (:center, :center),\n                  font     = :bold)\n        end\n    end\nend\n\n# Legend\nleg_elems = [PolyElement(color = DNA_COLORS[l], strokecolor = :transparent) for l in LETTERS]\nLegend(fig[1, 2], leg_elems, LETTERS, \"Nucleotide\";\n       backgroundcolor = ELEVATED_BG,\n       framevisible    = true,\n       framecolor      = INK_SOFT,\n       labelcolor      = INK,\n       titlecolor      = INK,\n       labelsize       = 12,\n       titlesize       = 12,\n       padding         = (8, 8, 8, 8),\n       rowgap          = 4)\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}