Sequence motifs in pure Julia

Hi everyone,

I’m writing for a little package for myself and to maybe share with the community one day, if it’s mature enough. I want to plot sequence motifs of letters (or peptides for those who are curious about the context).
For those that are not familiar with such plots, see a detailed description on this wikipedia page or see take a look at this image:

The question of how to calculate the order and sizes of letters is solved. The problem I face is the actual plotting of letters that are stretched in the y-dimension with the according size, nicely adhering to each other with no gaps or overlaps in the y-axis, and nicely aligned on the x-axis.

I figured that the easiest way to do this is to use CairoMakie.

My plan is the following:

  1. Define the alphabet of letters (in my case amino acids for the bioinformaticians out there) as individual little images.
  2. Stack those little images with the according height adjustment one on top of the other for each position on the x-axis.

So far I managed to create images for each letter I need that (more or less) nicely fill out the full canvas box with this function and subsequent figure generation:

using CairoMakie

function render_letter_image(letter::Char; color=:black, fontsize=1200, res=(845, 910))
    fig = Figure(figure_padding = 0)
    ax = Axis(fig[1, 1],
        xgridvisible = false,
        ygridvisible = false
        )
    hidedecorations!(ax)
    hidespines!(ax)

    limits!(ax, 0, 1, 0, 1)

    text_dims = text!(ax, string(letter);
        position = (0.5, 0.362),
        align = (:center, :center),
        color = color,
        font = "Helvetica",
        fontsize = fontsize
    )
    
    resize!(fig.scene, res...)  # resizes the canvas
    return rotr90(colorbuffer(fig.scene))
end

# example usage
img = render_letter_image('Q')

fig2 = Figure(figure_padding = 0)
ax2 = Axis(fig2[1, 1])
image!(ax2, img)
hidedecorations!(ax2)
hidespines!(ax2)

res2 = (600, 300) # just some random example numbers simulating the hard fixed width and the variable height
resize!(fig2.scene, res2...) # redefine size of letter based on width and height indicated (height is the "bits" axis dimension in the picture above)

fig2 # plot the canvas

My next step would be to simulate the stacking of letters for just one position. as soon as I figure that out I can just do that for each individual position.

Where Im currently stuck is this stacking of figures. How do I solve this? the output of my function above is a colourbuffer struct. I tried to just stack them like this:


function render_letter_image_vector(images, heights; width = 200)
    
    fig = Figure()
    ax = Axis(fig[1, 1],
        xgridvisible = false,
        ygridvisible = false
    )

    height_in_plot = cumsum(heights) - (heights ./ 2) # individual letter y positions
    for (i, img) in enumerate(images) # images is a vector of colourbuffers
             
              # I have no idea how to approach this stacking in a Axes of a Figure part. Everything I try fails.

              # image!(ax, image)# 1, height_in_plot)

    end
    return fig
end

# my test to try plotting the alphabet I need
images_ex = [render_letter_image(char) for char in "ACDEFGHIKLMNPQRSTVWY"];
heights_ex = rand(20) * 500
sum(heights_ex)*1.03

fig3 = render_letter_image_vector(images_ex, heights_ex)

Is there anyone out there that could assist me? Probably other bioinfarmitcs-proximate people would also be happy about a nice sequence motif plotting option in pure Julia:)

Thanks in advance!

Something like this?

f = Figure()
ax = Axis(f[1, 1], width = 800, height = 200)
groups = 61:83
n_per_group = 4
bits = rand(n_per_group, length(groups))
bits_accum = cumsum(sort!(bits, dims = 1), dims = 1)
midpoints = (vcat(zeros(length(groups))', bits_accum[1:end-1, :]) .+ bits_accum) .* 0.5
letters = rand('A':'Z', n_per_group, length(groups))
colors = rand(RGBf, n_per_group * length(groups))
scatter!(ax, repeat(groups, inner = n_per_group), vec(midpoints), marker = Rect, markerspace = :data,
    markersize = Vec.(1, vec(bits)), color = tuple.(colors, 0.2),
)
scatter!(ax, repeat(groups, inner = n_per_group), vec(midpoints), marker = vec(letters), markerspace = :data,
    markersize = Vec.(1, 1.33 .* vec(bits)), color = colors,
    font = "TeX Gyre Heros Makie Bold",
)
resize_to_layout!(f)
f

I had to add a manual factor stretching the markers a bit more in y direction so that they touch, that depends on the font’s metrics. I added the rectangles for comparison which aren’t stretched, so you know that the y heights of the letters are correct this way.

3 Likes

You.are.amazing! Thank you!

Your code basically gave me the solution to what I needed, which looks more or less like this:

using CairoMakie

amino_acid_colors = Dict(
    # Nonpolar (hydrophobic)
    'A' => colorant"gray",
    'V' => colorant"gray",
    'L' => colorant"gray",
    'I' => colorant"gray",
    'M' => colorant"gray",
    'F' => colorant"brown",
    'W' => colorant"brown",
    'P' => colorant"gray",
    'G' => colorant"gray",

    # Polar uncharged
    'S' => colorant"green",
    'T' => colorant"green",
    'C' => colorant"green",
    'Y' => colorant"olive",
    'N' => colorant"green",
    'Q' => colorant"green",

    # Positively charged
    'K' => colorant"blue",
    'R' => colorant"blue",
    'H' => colorant"blue",

    # Negatively charged
    'D' => colorant"red",
    'E' => colorant"red",

    # for completeness sake, all letters
    'O' => colorant"black",
    'X' => colorant"black",
    'Z' => colorant"black",
    'B' => colorant"black",
    'J' => colorant"black",
    'U' => colorant"black"
)


groups = 1:9 # nr of AA positions
n_per_group = 4 # nr of depicted AAs per position
bits = rand(n_per_group, length(groups)) # size of each letter
bits_accum = cumsum(sort!(bits, dims = 1), dims = 1) # bottom base y-coordinate of each letter
midpoints = (vcat(zeros(length(groups))', bits_accum[1:end-1, :]) .+ bits_accum) .* 0.5 # centered y-coordinate of each letter
letters = rand('A':'Z', n_per_group * length(groups)) # letters to depict
colors = [amino_acid_colors[letter] for letter in letters] # colors to depict

f = Figure(figure_padding = 1)
ax = Axis(f[1, 1],
          width = length(groups)*40, 
          height = 200,
          xticks = 1:9,
          xgridvisible = false,
          ygridvisible = false,
          xautolimitmargin = (0, 0),
          yautolimitmargin = (0, 0))

scatter!(ax, repeat(groups, inner = n_per_group), vec(midpoints), marker = vec(letters), markerspace = :data,
    markersize = Vec.(1*1.35, 1.35 .* vec(bits)), color = colors,
    font = "Helvetica bold",
)
resize_to_layout!(f)
f

Maybe one last request: I just cant figure out how to force the 0 value of the y axis to be aligned with the x-axis, so that they are on the same level. similarly I want the left edge of the first letter to meet the y-axis.

Only if you can spare the time. Thank you so much!

xlims!(ax, 0, nothing)
ylims!(ax, 0, nothing)

ought to do it - simply setting the x and y limits to begin at 0 and end wherever is appropriate.

1 Like

Thanks!