Sequence Logo for Motif Visualization — Makie.jl

A sequence logo visualizes the consensus of multiple aligned DNA, RNA, or protein sequences. At each position, letters are stacked vertically with height proportional to information content (measured in bits), and individual letter heights within the stack reflect their relative frequency. This is the standard visualization for identifying conserved positions in transcription factor binding sites, splice sites, and protein domains.

Sequence Logo for Motif Visualization rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# sequence-logo-basic: Sequence Logo for Motif Visualization
# Library: makie 0.22.10 | Julia 1.11.9
# Quality: 90/100 | Created: 2026-06-02

using CairoMakie
using Colors
using Random

Random.seed!(42)

# Theme tokens — Imprint palette, theme-adaptive chrome
const THEME       = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG     = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

# DNA letter colors — Imprint palette semantic exception (bioinformatics convention)
# A=green, C=blue, G=ochre, T=red  (standard bioinformatics color scheme)
const DNA_COLORS = Dict(
    "A" => colorant"#009E73",  # Imprint position 1 — brand green
    "C" => colorant"#4467A3",  # Imprint position 3 — blue
    "G" => colorant"#BD8233",  # Imprint position 4 — ochre
    "T" => colorant"#AE3030",  # Imprint position 5 — matte red
)
const LETTERS = ["A", "C", "G", "T"]

# TATA-box transcription factor binding site: 10-position DNA motif
# Rows: [freq_A, freq_C, freq_G, freq_T] summing to 1.0 per position
const MOTIF_FREQS = [
    [0.10, 0.10, 0.10, 0.70],   # 1  — T-dominant
    [0.80, 0.05, 0.10, 0.05],   # 2  — A-dominant
    [0.05, 0.05, 0.10, 0.80],   # 3  — T-dominant
    [0.75, 0.10, 0.05, 0.10],   # 4  — A-dominant
    [0.60, 0.15, 0.15, 0.10],   # 5  — A-dominant
    [0.65, 0.10, 0.15, 0.10],   # 6  — A-dominant
    [0.15, 0.30, 0.30, 0.25],   # 7  — mixed (less conserved)
    [0.05, 0.05, 0.80, 0.10],   # 8  — G-dominant
    [0.10, 0.10, 0.05, 0.75],   # 9  — T-dominant
    [0.55, 0.15, 0.20, 0.10],   # 10 — A-dominant
]

n_pos = length(MOTIF_FREQS)

# Information content: IC = 2 + Σ f·log2(f)  (bits; max = 2 for DNA)
ic_vals = [begin
    ic = 2.0
    for f in freqs
        f > 0 && (ic += f * log2(f))
    end
    max(0.0, ic)
end for freqs in MOTIF_FREQS]

# Per-position stacks: sorted ascending by contribution (least → bottom, most → top)
stack_data = [begin
    ic = ic_vals[p]
    items = [(LETTERS[i], MOTIF_FREQS[p][i] * ic)
             for i in eachindex(LETTERS) if MOTIF_FREQS[p][i] > 0.005]
    sort!(items; by = x -> x[2])
    out = Tuple{String, Float64, Float64}[]
    y = 0.0
    for (ltr, contrib) in items
        push!(out, (ltr, contrib, y))
        y += contrib
    end
    out
end for p in 1:n_pos]

# Title with font-size scaled for length
title_str  = "TATA-box Motif · sequence-logo-basic · julia · makie · anyplot.ai"
n_chars    = length(title_str)
title_size = max(12, round(Int, 20 * min(1.0, 67.0 / n_chars)))

# Scaled-glyph font-size constants: axis height ≈ 70% of 900 pts canvas
# Y-range = 2 bits → ~315 pts/bit; fill 80% of each bar's height with the letter
const PTS_PER_BIT = 315.0
const FILL_FACTOR = 0.80

# Figure
fig = Figure(
    size            = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = title_str,
    titlesize          = Float32(title_size),
    titlecolor         = INK,
    xlabel             = "Position",
    ylabel             = "Information content (bits)",
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xlabelsize         = 14,
    ylabelsize         = 14,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xtickcolor         = INK_SOFT,
    ytickcolor         = INK_SOFT,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    xgridvisible       = false,
    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
    xminorgridvisible  = false,
    yminorgridvisible  = false,
    xticks             = 1:n_pos,
    yticks             = 0.0:0.5:2.0,
    limits             = (0.35, n_pos + 0.65, -0.05, 2.15),
)

# Subtle shaded band highlighting the TATA-box conserved core (positions 2–6)
poly!(ax, Rect2f(1.5, 0.0, 5.0, 2.0);
      color       = RGBAf(INK.r, INK.g, INK.b, 0.04),
      strokewidth = 0.8,
      strokecolor = RGBAf(INK.r, INK.g, INK.b, 0.10))

# Sequence logo: stacked colored rectangles + proportionally-scaled letter glyphs
bar_w = 0.88

for (pos, stacks) in enumerate(stack_data)
    for (letter, contrib, y_bot) in stacks
        contrib < 0.01 && continue
        poly!(ax, Rect2f(pos - bar_w / 2, y_bot, bar_w, contrib);
              color       = DNA_COLORS[letter],
              strokewidth = 0.4,
              strokecolor = PAGE_BG)
        # Scaled-glyph rendering: fontsize grows with bar height
        if contrib > 0.025
            glyph_size = max(6, round(Int, contrib * PTS_PER_BIT * FILL_FACTOR))
            text!(ax, Float64(pos), y_bot + contrib / 2;
                  text     = letter,
                  color    = (:white, 0.92),
                  fontsize = glyph_size,
                  align    = (:center, :center),
                  font     = :bold)
        end
    end
end

# Legend
leg_elems = [PolyElement(color = DNA_COLORS[l], strokecolor = :transparent) for l in LETTERS]
Legend(fig[1, 2], leg_elems, LETTERS, "Nucleotide";
       backgroundcolor = ELEVATED_BG,
       framevisible    = true,
       framecolor      = INK_SOFT,
       labelcolor      = INK,
       titlecolor      = INK,
       labelsize       = 12,
       titlesize       = 12,
       padding         = (8, 8, 8, 8),
       rowgap          = 4)

save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/sequence-logo-basic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "sequence-logo-basic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/sequence-logo-basic/julia/makie",
  "hub": "https://anyplot.ai/sequence-logo-basic",
  "code_json": "https://api.anyplot.ai/specs/sequence-logo-basic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/sequence-logo-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/sequence-logo-basic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/sequence-logo-basic/julia/makie/plot-dark.png",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Sequence Logo for Motif Visualization on anyplot.ai.

Other implementations