-
Notifications
You must be signed in to change notification settings - Fork 90
/
CircularGraphicRecord.py
156 lines (131 loc) · 5.31 KB
/
CircularGraphicRecord.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""Implements the CircularGraphicRecord class.
"""
import matplotlib.patches as mpatches
import numpy as np
from ..GraphicRecord import GraphicRecord
from .ArrowWedge import ArrowWedge
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
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")
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)
def plot_feature(self, ax, feature, level):
"""Plot an ArrowWedge representing the feature at the given 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)
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
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])
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)