Building a synetny plot figure - single line with superimposed shapes of specific lengths

Hello,

I am looking to construct a synteny plot in my DASH app. This consists of multiple tracks, each of which contains a gene symbol (an arrow) of a specific length - an example of what this looks like is showed below.

I can get data for the length/position/colour of each arrow and number of rows, but I’m not sure how to actually make these images (i.e. collections of arrows on self-contained tracks, all as a single self contained figure, ideally with annotations saying what genome the genes are found in). I have looked at Dash Bio but can’t see anything immediately relevant - I might be able to hack something out of this stack overflow answer but I’m not sure if a better option exists.

Can anyone point me in the right direction?

Thanks!
Tim

@TimothyK
I don’t know what a synteny is. That’s why in the following I call element a rectangle that
has a triangle at one ends, or just a triangle. From your example it seems that triangles have different heights.
Your data are most likely given in a data frame. Here are given in lists.
I illustrate only one horizontal plot (is this a synteny?).
The number of traces in an h-plot is equal to its number of elements.
If you want more such h-plots, then either:

  1. add to the same fig.data more h-plots, each one corresponding
    to a different startpoint, where the startpoint
    is of coordinates (0, 0+somevalue);

or

  1. add each such h-plot as a subplot;

I think that the figure generated as in the first case will look better.

import plotly.graph_objects as go
import numpy as np

def elementName(position, length=1, hrect=0.32, htri=0.25,  orient='r', fcolor='red', lw=1):
    x0, y0 = position
    A = (x0, y0-hrect/2)
    B = (x0, y0+hrect/2)
    if orient == 'r':  #right oriented element
        C = (x0+length, y0+hrect/2)
        D = (x0+length+htri, y0)
        E = (x0+length, y0-hrect/2)
    elif orient == 'l':  #left oriented element
        C = (x0-length, y0+hrect/2)
        D = (x0-length-htri, y0)
        E = (x0-length, y0-hrect/2)
    else: ("error")     
    x = [A[0], B[0], C[0], D[0], E[0], A[0]]
    y = [A[1], B[1], C[1], D[1], E[1], A[1]]
    return go.Scatter(x=x, y=y, mode="lines", line_color="black", line_width=lw,
                      fill="toself", fillcolor=fcolor)
##########   Data to create an example ########################
start = [0.0, 0.0]#inital position of the leftmost element
lengths = [1, 0, 0.8, 1.2, 2, 2.4, 1.6, 0, 0, 0, 0,  1.95, 2.56, 3] #rectangle length of an element
hrect = 0.4 #rectangle height
htri = [0.21, 0.3, 0.25, 0.4, 0.32, 0.28, 0.26, 0.25, 0.25, 0.25, 0.25, 0.25, 0.3, 0.3 ] #triangle heights
orients = ['l', 'r', 'l', 'r', 'l', 'r', 'l', 'l', 'l', 'l', 'r', 'r', 'r', 'r'] #arrow orientation
ecolors = ['lightblue', 'magenta', 'green', 'orange', 'blue', 'brown', 'hotpink', 'lightcoral',
          'lavender','red', 'RoyalBlue', 'indigo', 'green', 'yellow']# element color
#check if the lists lengths, hrect, orients, ecolors have the same len, n
n = 14 #is the common len
dist = [0 , 0,  0,  0.2,  0,  0.3, 0.34, 0, 0, 0, 0.3, 0.6, 0.55] #distance (gap) between two consecutive elements
#len(dist)=n-1
#########

startpoint = [0.0, 0.0]
position = np.copy(startpoint)
if  orients[0] =='l':
        position[0] = startpoint[0]+ htri[0] + lengths[0]
        position[1] = startpoint[1]

start_annot= position[0]    
data = []

for k in range(n):
    data.append(elementName(position, length=lengths[k], hrect=hrect, 
                          htri=htri[k], orient=orients[k], fcolor=ecolors[k], lw=1))
    if k < n-1:  
        if orients[k] ==  orients[k+1] == 'r':
            position[0] += (lengths[k] + htri[k]+ dist[k])
        elif orients[k] ==  orients[k+1] == 'l':   
            position[0] += dist[k] + htri[k+1] + lengths[k+1]  
        elif orients[k] == 'r' and orients[k+1] == 'l': 
            position[0] += lengths[k] + htri[k] + dist[k] + htri[k+1] + lengths[k+1]
        elif orients[k] == 'l' and orients[k+1] == 'r':
            position[0] += dist[k]
        else:
            "this is not a real  case"
    
fig = go.Figure(data=data)
ax_style=dict(showgrid=False, showticklabels=False, zeroline=False)
fig.update_layout(height=270, width=600, yaxis_range=[-1.5, 1.5], template='none', xaxis=ax_style, 
                  yaxis=ax_style, yaxis_zeroline=True, showlegend=False)
fig.add_annotation(x=0, y= start[0]+hrect+0.5, text='NZ=BM01237eio91038393',
            xref="paper", yref="paper", showarrow=False)

synteny

2 Likes

This looks ideal - thank you very much!

1 Like