Source code for dna_features_viewer.CircularGraphicRecord.CircularGraphicRecord

"""Implements the CircularGraphicRecord class.
"""

import matplotlib.patches as mpatches
import numpy as np

from ..GraphicRecord import GraphicRecord
from .ArrowWedge import ArrowWedge


[docs]class CircularGraphicRecord(GraphicRecord): """Set of Genetic Features of a same DNA sequence, to be plotted together. Parameters ---------- sequence_length Length of the DNA sequence, in number of nucleotides features list of GraphicalFeature objects. top_position The index in the sequence that will end up at the top of the circle feature_level_height Width in inches of one "level" for feature arrows. annotation_height Width in inches of one "level" for feature annotations. labels_spacing Distance in basepairs to keep between labels to avoid "quasi-collisions" **kw Other keyword arguments - do not use, these parameters are allowed for making it easier to use GraphicRecord and CircularGraphicRecord interchangeably. """ default_elevate_outline_annotations = True min_y_height_of_text_line = 0.1 def __init__( self, sequence_length, features, top_position=0, feature_level_height=0.2, annotation_height="auto", labels_spacing=12, **kw ): self.radius = 1.0 self.sequence_length = sequence_length self.features = features self.top_position = top_position self.feature_level_height = feature_level_height self.annotation_height = annotation_height self.labels_spacing = labels_spacing
[docs] def initialize_ax(self, ax, draw_line, with_ruler): """Initialize the ax with a circular line, sets limits, aspect etc. """ if draw_line: circle = mpatches.Circle( (0, -self.radius), self.radius, facecolor="none", edgecolor="k" ) ax.add_patch(circle) ax.axis("off") if with_ruler: # only display the xaxis ticks ax.set_frame_on(False) ax.yaxis.set_visible(False) ax.xaxis.tick_bottom() else: # don't display anything ax.axis("off") ax.set_xlim(-1.1 * self.radius, 1.1 * self.radius) ax.set_ylim(-self.radius, 3 * self.radius) ax.set_aspect("equal")
[docs] def finalize_ax( self, ax, features_levels, annotations_max_level, auto_figure_height=False, ideal_yspan=None, annotations_are_elevated=True ): """Final display range and figure dimension tweakings.""" annotation_height = self.determine_annotation_height( annotations_max_level ) ymin = -2 * self.radius - self.feature_level_height * ( features_levels + 1 ) ymax = ( self.feature_level_height * (features_levels + 1) + annotation_height * (annotations_max_level + 1) ) if ideal_yspan is not None: ymax = max(annotation_height * ideal_yspan + ymin, ymax) xmin = -self.radius - self.feature_level_height * (features_levels + 1) xmax = -xmin ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ratio = 1.0 * (ymax - ymin) / (xmax - xmin) if auto_figure_height: figure_width = ax.figure.get_size_inches()[0] ax.figure.set_size_inches(figure_width, figure_width * ratio)
[docs] def plot_feature(self, ax, feature, level): """Plot an ArrowWedge representing the feature at the giben height level. """ a_start = self.position_to_angle(feature.start) a_end = self.position_to_angle(feature.end) a_start, a_end = sorted([a_start, a_end]) r = self.radius + level * self.feature_level_height patch = ArrowWedge( center=(0, -self.radius), radius=r, theta1=a_start, theta2=a_end, width=0.7 * self.feature_level_height, direction=feature.strand, edgecolor=feature.linecolor, linewidth=feature.linewidth, facecolor=feature.color, zorder=1, ) ax.add_patch(patch)
[docs] def position_to_angle(self, position): """Convert a sequence position into an angle in the figure.""" a = 360.0 * (position - self.top_position) / self.sequence_length return 90 - a
[docs] def coordinates_in_plot(self, position, level): """Convert a sequence position and height level to (x, y) coordinates. """ r = self.radius + level * self.feature_level_height angle = self.position_to_angle(position) rad_angle = np.deg2rad(angle) return np.array( [r * np.cos(rad_angle), r * np.sin(rad_angle) - self.radius] )
[docs] def determine_annotation_height(self, max_annotations_level): """Auto-select the annotations height. Annotation height is 0.2 at most, or else whatever will make the figure a 5*radius tall rectangle where the circular plasmid occupies the bottom-2 5th and the annotations occupy the top-3 5th. """ return min(0.25, 3.0 * self.radius / (1.0 + max_annotations_level))
def compute_padding(self, ax): "" ax_width = ax.get_window_extent(ax.figure.canvas.get_renderer()).width xmin, xmax = ax.get_xlim() return 3 * self.labels_spacing * (xmax - xmin) / (1.0 * ax_width)