1 """Display information distributed across a Chromosome-like object.
2
3 These classes are meant to show the distribution of some kind of information
4 as it changes across any kind of segment. It was designed with chromosome
5 distributions in mind, but could also work for chromosome regions, BAC clones
6 or anything similar.
7
8 Reportlab is used for producing the graphical output.
9 """
10
11 import math
12
13
14 from reportlab.lib.pagesizes import letter
15 from reportlab.lib.units import inch
16 from reportlab.lib import colors
17
18 from reportlab.graphics.shapes import Drawing, String
19 from reportlab.graphics.charts.barcharts import VerticalBarChart
20 from reportlab.graphics.charts.barcharts import BarChartProperties
21 from reportlab.graphics.widgetbase import TypedPropertyCollection
22
23 from Bio.Graphics import _write
24
25
26 -class DistributionPage(object):
27 """Display a grouping of distributions on a page.
28
29 This organizes Distributions, and will display them nicely
30 on a single page.
31 """
32 - def __init__(self, output_format = 'pdf'):
33 self.distributions = []
34
35
36 self.number_of_columns = 1
37 self.page_size = letter
38 self.title_size = 20
39
40 self.output_format = output_format
41
42 - def draw(self, output_file, title):
43 """Draw out the distribution information.
44
45 Arguments:
46
47 o output_file - The name of the file to output the information to,
48 or a handle to write to.
49
50 o title - A title to display on the graphic.
51 """
52 width, height = self.page_size
53 cur_drawing = Drawing(width, height)
54
55 self._draw_title(cur_drawing, title, width, height)
56
57
58 cur_x_pos = inch * .5
59 end_x_pos = width - inch * .5
60 cur_y_pos = height - 1.5 * inch
61 end_y_pos = .5 * inch
62 x_pos_change = ((end_x_pos - cur_x_pos) /
63 float(self.number_of_columns))
64 num_y_rows = math.ceil(float(len(self.distributions))
65 / float(self.number_of_columns))
66 y_pos_change = (cur_y_pos - end_y_pos) / num_y_rows
67
68 self._draw_distributions(cur_drawing, cur_x_pos, x_pos_change,
69 cur_y_pos, y_pos_change, num_y_rows)
70 self._draw_legend(cur_drawing, 2.5 * inch, width)
71
72 return _write(cur_drawing, output_file, self.output_format)
73
74 - def _draw_title(self, cur_drawing, title, width, height):
75 """Add the title of the figure to the drawing.
76 """
77 title_string = String(width / 2, height - inch, title)
78 title_string.fontName = 'Helvetica-Bold'
79 title_string.fontSize = self.title_size
80 title_string.textAnchor = "middle"
81
82 cur_drawing.add(title_string)
83
84 - def _draw_distributions(self, cur_drawing, start_x_pos, x_pos_change,
85 start_y_pos, y_pos_change, num_y_drawings):
86 """Draw all of the distributions on the page.
87
88 Arguments:
89
90 o cur_drawing - The drawing we are working with.
91
92 o start_x_pos - The x position on the page to start drawing at.
93
94 o x_pos_change - The change in x position between each figure.
95
96 o start_y_pos - The y position on the page to start drawing at.
97
98 o y_pos_change - The change in y position between each figure.
99
100 o num_y_drawings - The number of drawings we'll have in the y
101 (up/down) direction.
102 """
103 for y_drawing in range(int(num_y_drawings)):
104
105
106 if (y_drawing + 1) * self.number_of_columns > \
107 len(self.distributions):
108 num_x_drawings = len(self.distributions) - \
109 y_drawing * self.number_of_columns
110 else:
111 num_x_drawings = self.number_of_columns
112 for x_drawing in range(num_x_drawings):
113 dist_num = y_drawing * self.number_of_columns + x_drawing
114 cur_distribution = self.distributions[dist_num]
115
116
117 x_pos = start_x_pos + x_drawing * x_pos_change
118 end_x_pos = x_pos + x_pos_change
119 end_y_pos = start_y_pos - y_drawing * y_pos_change
120 y_pos = end_y_pos - y_pos_change
121
122
123 cur_distribution.draw(cur_drawing, x_pos, y_pos, end_x_pos,
124 end_y_pos)
125
126 - def _draw_legend(self, cur_drawing, start_y, width):
127 """Add a legend to the figure.
128
129 Subclasses can implement to provide a specialized legend.
130 """
131 pass
132
133
135 """Display the distribution of values as a bunch of bars.
136 """
138 """Initialize a Bar Chart display of distribution info.
139
140 Class attributes:
141
142 o display_info - the information to be displayed in the distribution.
143 This should be ordered as a list of lists, where each internal list
144 is a data set to display in the bar chart.
145 """
146 self.display_info = display_info
147
148 self.x_axis_title = ""
149 self.y_axis_title = ""
150 self.chart_title = ""
151 self.chart_title_size = 10
152
153 self.padding_percent = 0.15
154
155 - def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
156 """Draw a bar chart with the info in the specified range.
157 """
158 bar_chart = VerticalBarChart()
159 if self.chart_title:
160 self._draw_title(cur_drawing, self.chart_title,
161 start_x, start_y, end_x, end_y)
162
163 x_start, x_end, y_start, y_end = \
164 self._determine_position(start_x, start_y, end_x, end_y)
165
166 bar_chart.x = x_start
167 bar_chart.y = y_start
168 bar_chart.width = abs(x_start - x_end)
169 bar_chart.height = abs(y_start - y_end)
170
171
172 bar_chart.data = self.display_info
173 bar_chart.valueAxis.valueMin = min(self.display_info[0])
174 bar_chart.valueAxis.valueMax = max(self.display_info[0])
175 for data_set in self.display_info[1:]:
176 if min(data_set) < bar_chart.valueAxis.valueMin:
177 bar_chart.valueAxis.valueMin = min(data_set)
178 if max(data_set) > bar_chart.valueAxis.valueMax:
179 bar_chart.valueAxis.valueMax = max(data_set)
180
181
182 if len(self.display_info) == 1:
183 bar_chart.groupSpacing = 0
184 style = TypedPropertyCollection(BarChartProperties)
185 style.strokeWidth = 0
186 style.strokeColor = colors.green
187 style[0].fillColor = colors.green
188
189 bar_chart.bars = style
190
191
192
193
194
195
196 cur_drawing.add(bar_chart)
197
198 - def _draw_title(self, cur_drawing, title, start_x, start_y, end_x, end_y):
199 """Add the title of the figure to the drawing.
200 """
201 x_center = start_x + (end_x - start_x) / 2
202 y_pos = end_y + (self.padding_percent * (start_y - end_y)) / 2
203 title_string = String(x_center, y_pos, title)
204 title_string.fontName = 'Helvetica-Bold'
205 title_string.fontSize = self.chart_title_size
206 title_string.textAnchor = "middle"
207
208 cur_drawing.add(title_string)
209
211 """Calculate the position of the chart with blank space.
212
213 This uses some padding around the chart, and takes into account
214 whether the chart has a title. It returns 4 values, which are,
215 in order, the x_start, x_end, y_start and y_end of the chart
216 itself.
217 """
218 x_padding = self.padding_percent * (end_x - start_x)
219 y_padding = self.padding_percent * (start_y - end_y)
220
221 new_x_start = start_x + x_padding
222 new_x_end = end_x - x_padding
223
224 if self.chart_title:
225 new_y_start = start_y - y_padding - self.chart_title_size
226 else:
227 new_y_start = start_y - y_padding
228
229 new_y_end = end_y + y_padding
230
231 return new_x_start, new_x_end, new_y_start, new_y_end
232
233
235 """Display the distribution of values as connected lines.
236
237 This distribution displays the change in values across the object as
238 lines. This also allows multiple distributions to be displayed on a
239 single graph.
240 """
243
244 - def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
246