sequence logos in matplotlib: aligning xticks

前端 未结 3 1500
死守一世寂寞
死守一世寂寞 2020-12-03 13:14

I am trying to draw sequence logos using matplotlib. The entire code is available on gist

The relevant portion is:

class Scale(matplotlib.patheffects         


        
相关标签:
3条回答
  • 2020-12-03 13:25

    I would suggest to work in data coordinates and unit coordinates throughout the script.

    gact.py
    Script to plot a letter at a given position with a given scale. One obstacle is that the letters created by TextPath have their lower left corner at relative position (0,0) and that they don't span the complete range up to (1,1). We therefore need to shift the TextPath to the left such that the lower center of the letter is at (0,0) and introduce a globscale scaling parameter, which makes the letter 1 unit in height. Unfortunately this depends on the font being used, so for a different font, one has to adapt the x coordinate and the globscale parameter again.

    import matplotlib as mpl
    from matplotlib.text import TextPath
    from matplotlib.patches import PathPatch
    from matplotlib.font_manager import FontProperties
    
    fp = FontProperties(family="Arial", weight="bold") 
    globscale = 1.35
    LETTERS = { "T" : TextPath((-0.305, 0), "T", size=1, prop=fp),
                "G" : TextPath((-0.384, 0), "G", size=1, prop=fp),
                "A" : TextPath((-0.35, 0), "A", size=1, prop=fp),
                "C" : TextPath((-0.366, 0), "C", size=1, prop=fp) }
    COLOR_SCHEME = {'G': 'orange', 
                    'A': 'red', 
                    'C': 'blue', 
                    'T': 'darkgreen'}
    
    def letterAt(letter, x, y, yscale=1, ax=None):
        text = LETTERS[letter]
    
        t = mpl.transforms.Affine2D().scale(1*globscale, yscale*globscale) + \
            mpl.transforms.Affine2D().translate(x,y) + ax.transData
        p = PathPatch(text, lw=0, fc=COLOR_SCHEME[letter],  transform=t)
        if ax != None:
            ax.add_artist(p)
        return p
    

    running code

    import matplotlib.pyplot as plt
    import gact
    
    fig, ax = plt.subplots(figsize=(10,3))
    
    all_scores = ALL_SCORES2
    x = 1
    maxi = 0
    for scores in all_scores:
        y = 0
        for base, score in scores:
            gact.letterAt(base, x,y, score, ax)
            y += score
        x += 1
        maxi = max(maxi, y)
    
    plt.xticks(range(1,x))
    plt.xlim((0, x)) 
    plt.ylim((0, maxi)) 
    plt.tight_layout()      
    plt.show()
    

    0 讨论(0)
  • 2020-12-03 13:47

    I was able to workaround by using the screen coordinates:

    def draw_logo(all_scores, fontfamily='Arial', size=80):
        mpl.rcParams['font.family'] = fontfamily
    
        fig, ax = plt.subplots(figsize=(len(all_scores), 2.5))
    
        font = FontProperties()
        font.set_size(size)
        font.set_weight('bold')
    
        #font.set_family(fontfamily)
    
        ax.set_xticks(range(1,len(all_scores)+1))    
        ax.set_yticks(range(0,3))
        ax.set_xticklabels(range(1,len(all_scores)+1), rotation=90)
        ax.set_yticklabels(np.arange(0,3,1))    
        seaborn.despine(ax=ax, trim=True)
    
        trans_offset = transforms.offset_copy(ax.transData, 
                                          fig=fig, 
                                          x=1, 
                                          y=0, 
                                          units='dots')
    
       for index, scores in enumerate(all_scores):
          yshift = 0
          for base, score in scores:
             txt = ax.text(index+1, 
                          0, 
                          base, 
                          transform=trans_offset,
                          fontsize=80, 
                          color=COLOR_SCHEME[base],
                          ha='center',
                          fontproperties=font,
    
                         )
            txt.set_path_effects([Scale(1.0, score)])
            fig.canvas.draw()
            window_ext = txt.get_window_extent(txt._renderer)
            yshift = window_ext.height*score
            trans_offset = transforms.offset_copy(txt._transform, 
                                                  fig=fig,
                                                  y=yshift,
                                                  units='points')
        trans_offset = transforms.offset_copy(ax.transData, 
                                              fig=fig, 
                                              x=1, 
                                              y=0, 
                                              units='points')    
    plt.show()
    

    Examples:

    Link to Jupyter Notebook

    pyseqlogo

    dmslogo

    0 讨论(0)
  • 2020-12-03 13:49

    I simplified the code to make it generalizable to DPI/screen coordinates. The major change is that it draws the first character and then measures its screen width/height and then uses those parameters for the rest.

    from matplotlib.patheffects import RendererBase
    from matplotlib.transforms import offset_copy
    from matplotlib.patches import Rectangle
    
    class Scale(RendererBase):
        def __init__(self, sx, sy=None):
            self._sx = sx
            self._sy = sy
    
        def draw_path(self, renderer, gc, tpath, affine, rgbFace):
            affine = affine.identity().scale(self._sx, self._sy) + affine
            renderer.draw_path(gc, tpath, affine, rgbFace)
    
    iupac = "XACMGRSVTWYHKDBN"
    
    iupac_colors = ['black'] * 16
    iupac_colors[1<<0] = 'blue'
    iupac_colors[1<<1] = 'red'
    iupac_colors[1<<2] = 'green'
    iupac_colors[1<<3] = 'gold'     
    
    fig, ax = plt.subplots()
    fig.set_size_inches(4, 1)
    
    
    w=25
    ax.set_xlim(0, w)
    
    xoffset=int((w-pwm.shape[1])/2)
    
    wscale=1
    for i in range(pwm.shape[1]):
        yshift = 0
        for j in range(4):
            base=iupac[(1<<j)]
            color=iupac_colors[(1<<j)]
            scale=pwm[j, i]
    
            t = ax.text(i+xoffset, yshift, base, ha='left', va='baseline', color=color, fontsize=80, family='monospace', weight='bold')
    
            if i==0 and j==0:
                fig.canvas.draw()
                ext = t.get_window_extent(t._renderer)
    
                x0, x1, y0, y1 = ext.x0, ext.x1, ext.y0, ext.y1
                nx0, ny0 = ax.transData.inverted().transform((x0, y0))
                nx1, ny1 = ax.transData.inverted().transform((x1, y1))
    
                wscale=1.0/(nx1-nx0)
                hscale=1.0/(ny1)
                height=ext.height
    
            t.set_path_effects([Scale(wscale, scale*hscale)])
    
            w=0
            h=(height)*scale*hscale
    
            x, y = fig.transFigure.inverted().transform((w, h))
    
            yshift += y
    
    ax.set_xticks(np.arange(xoffset, xoffset+pwm.shape[1])+0.5)
    ax.set_xticklabels(np.arange(pwm.shape[1])+1)
    sns.despine(ax=ax)
    

    0 讨论(0)
提交回复
热议问题