Source code for lib5c.plotters.extendable.motif_extendable_heatmap

"""
Module for the MotifExtendableHeatmap class, which adds motif track plotting
functionality for the extendable heatmap system.
"""

import matplotlib as mpl

from lib5c.plotters.extendable.base_extendable_heatmap import \
    BaseExtendableHeatmap
from lib5c.util.bed import check_intersect


[docs]class MotifExtendableHeatmap(BaseExtendableHeatmap): """ ExtendableHeatmap mixin class providing motif track plotting functionality. """
[docs] def add_motif_tracks(self, motifs, size='3%', pad=0.0, axis_limits=(0, 1), intron_height=0.05, exon_height=0.5, name='motif', motif_linewidth=1, colors=None, track_label=None): """ Adds motif tracks for a single set of motifs to both the bottom and left side of the heatmap by calling ``add_motif_track()`` twice. Parameters ---------- motifs : list of dict Each dict should represent a motif instance and could have the following keys:: { 'chrom' : str, 'start' : int, 'end' : int, 'strand': '+' or '-' } size : str The size of the new axis as a percentage of the main heatmap width. Should be passed as a string ending in '%'. pad : float The padding to use between the existing parts of the figure and the newly added axis. axis_limits : tuple of float Axis limits for the non-genomic axis of the motif track. intron_height : float Controls height of the rectangle spanning the length of the motif. exon_height : float Controls the size of the arrowhead that indicates the motif orientation. name : str Base name for the new axis. This name will be prefixed with the orientation of the track ('vertical' or 'horizontal'). motif_linewidth : float The linewidth to use when plotting. colors : dict, optional Map from the value of the 'strand' key in the ``motifs`` dicts (usually '+' or '-') to color name for motifs with that strand value (i.e., orientation). If not provided for a given strand, color is black by default. track_label : str, optional Pass a string to label the track. Returns ------- list of pyplot axes The newly added motif track axes. """ ax_h = self.add_motif_track( motifs, loc='bottom', size=size, pad=pad, name=name, axis_limits=axis_limits, intron_height=intron_height, exon_height=exon_height, colors=colors, track_label=track_label, motif_linewidth=motif_linewidth ) ax_v = self.add_motif_track( motifs, loc='left', size=size, pad=pad, name=name, axis_limits=axis_limits, intron_height=intron_height, exon_height=exon_height, colors=colors, track_label=track_label, motif_linewidth=motif_linewidth ) return [ax_h, ax_v]
[docs] def add_motif_track(self, motifs, loc='bottom', size='3%', pad=0.0, name='motif', axis_limits=(0, 1), intron_height=0.05, motif_linewidth=1, exon_height=0.5, colors=None, track_label=None): """ Adds one motif track along either the x- or y-axis of the heatmap. Parameters ---------- motifs : list of dict Each dict should represent a motif instance and could have the following keys:: { 'chrom' : str, 'start' : int, 'end' : int, 'strand': '+' or '-' } loc : {'top', 'bottom', 'left', 'right'} Which side of the heatmap to add the new motif track to. size : str The size of the new axis as a percentage of the main heatmap width. Should be passed as a string ending in '%'. pad : float The padding to use between the existing parts of the figure and the newly added axis. name : str Base name for the new axis. This name will be prefixed with the orientation of the track ('vertical' or 'horizontal'). axis_limits : tuple of float Axis limits for the non-genomic axis of the motif track. intron_height : float Controls height of the rectangle spanning the length of the motif. motif_linewidth : float The linewidth to use when plotting. exon_height : float Controls the size of the arrowhead that indicates the motif orientation. colors : dict, optional Map from the value of the 'strand' key in the ``motifs`` dicts (usually '+' or '-') to color name for motifs with that strand value (i.e., orientation). If not provided for a given strand, color is black by default. track_label : str, optional Pass a string to label the track. Returns ------- pyplot axis The newly added motif track axis. """ # deduce orientation, either h or v, and save the correct grange if loc in ['bottom', 'top']: orientation = 'horizontal' grange = self.grange_x else: orientation = 'vertical' grange = self.grange_y # create new axis ax = self.add_margin_ax(loc=loc, size=size, pad=pad, new_ax_name="{}_{}".format(orientation, name), axis_limits=axis_limits) # deduce midpoint of the non-genomic axis midpoint = (axis_limits[0] + axis_limits[1]) / 2. # compute track_width and track_height, assuming horizontal orientation track_width = grange['end'] - grange['start'] track_height = abs(axis_limits[1] - axis_limits[0]) # create patches for each motif for motif in motifs: # skip motifs that don't intersect the window if not check_intersect(motif, grange): continue # get the color for this motif try: color = colors[motif['strand']] except (TypeError, KeyError): color = 'k' # plot small rectangle for length of motif intron_coords = [motif['start'], midpoint - intron_height / 2.] intron_dims = [motif['end'] - motif['start'], intron_height] if orientation == 'vertical': intron_coords.reverse() intron_dims.reverse() ax.add_artist(mpl.patches.Rectangle( intron_coords, *intron_dims, fc=color, ec=color, lw=motif_linewidth)) # plot a little arrow based on the strand information if present # TODO: figure out why this factor of 40 is necessary arrow_size = (exon_height / 60.) * (track_width / track_height) if motif['strand'] == '+': xs = [motif['start'], motif['start'] - arrow_size] upper_ys = [midpoint, midpoint + exon_height / 2.] lower_ys = [midpoint, midpoint - exon_height / 2.] elif motif['strand'] == '-': xs = [motif['end'], motif['end'] + arrow_size] upper_ys = [midpoint, midpoint + exon_height / 2.] lower_ys = [midpoint, midpoint - exon_height / 2.] else: continue if orientation == 'vertical': ax.plot(upper_ys, xs, lw=motif_linewidth, color=color) ax.plot(lower_ys, xs, lw=motif_linewidth, color=color) else: ax.plot(xs, upper_ys, lw=motif_linewidth, color=color) ax.plot(xs, lower_ys, lw=motif_linewidth, color=color) # done looping over all motifs # write label of motif track, if specified if track_label: name_coords = [grange['start'] + track_width / 80, axis_limits[1]] ha = 'left' va = 'top' rotation = 0 if orientation == 'vertical': name_coords.reverse() ha = 'right' rotation = 270 ax.text(name_coords[0], name_coords[1], track_label, fontsize=7, ha=ha, va=va, rotation=rotation) # return axis for this track return ax