1 """Draw representations of organism chromosomes with added information.
2
3 These classes are meant to model the drawing of pictures of chromosomes.
4 This can be useful for lots of things, including displaying markers on
5 a chromosome (ie. for genetic mapping) and showing syteny between two
6 chromosomes.
7
8 The structure of these classes is intended to be a Composite, so that
9 it will be easy to plug in and switch different parts without
10 breaking the general drawing capabilities of the system. The
11 relationship between classes is that everything derives from
12 _ChromosomeComponent, which specifies the overall interface. The parts
13 then are related so that an Organism contains Chromosomes, and these
14 Chromosomes contain ChromosomeSegments. This representation differents
15 from the canonical composite structure in that we don't really have
16 'leaf' nodes here -- all components can potentially hold sub-components.
17
18 Most of the time the ChromosomeSegment class is what you'll want to
19 customize for specific drawing tasks.
20
21 For providing drawing capabilities, these classes use reportlab:
22
23 http://www.reportlab.com
24
25 This provides nice output in PDF, SVG and postscript. If you have
26 reportlab's renderPM module installed you can also use PNG etc.
27 """
28
29 import os
30
31
32 from reportlab.pdfgen import canvas
33 from reportlab.lib.pagesizes import letter
34 from reportlab.lib.units import inch
35 from reportlab.lib import colors
36
37 from reportlab.graphics.shapes import Drawing, String, Line, Rect, Wedge
38 from reportlab.graphics import renderPDF, renderPS
39 from reportlab.graphics.widgetbase import Widget
40
41 from Bio.Graphics import _write
42
44 """Base class specifying the interface for a component of the system.
45
46 This class should not be instantiated directly, but should be used
47 from derived classes.
48 """
50 """Initialize a chromosome component.
51
52 Attributes:
53
54 o _sub_components -- Any components which are contained under
55 this parent component. This attribute should be accessed through
56 the add() and remove() functions.
57 """
58 self._sub_components = []
59
60 - def add(self, component):
61 """Add a sub_component to the list of components under this item.
62 """
63 assert isinstance(component, _ChromosomeComponent), \
64 "Expected a _ChromosomeComponent object, got %s" % component
65
66 self._sub_components.append(component)
67
69 """Remove the specified component from the subcomponents.
70
71 Raises a ValueError if the component is not registered as a
72 sub_component.
73 """
74 try:
75 self._sub_components.remove(component)
76 except ValueError:
77 raise ValueError("Component %s not found in sub_components." %
78 component)
79
81 """Draw the specified component.
82 """
83 raise AssertionError("Subclasses must implement.")
84
86 """Top level class for drawing chromosomes.
87
88 This class holds information about an organism and all of it's
89 chromosomes, and provides the top level object which could be used
90 for drawing a chromosome representation of an organism.
91
92 Chromosomes should be added and removed from the Organism via the
93 add and remove functions.
94 """
95 - def __init__(self, output_format = 'pdf'):
103
104 - def draw(self, output_file, title):
105 """Draw out the information for the Organism.
106
107 Arguments:
108
109 o output_file -- The name of a file specifying where the
110 document should be saved, or a handle to be written to.
111 The output format is set when creating the Organism object.
112
113 o title -- The output title of the produced document.
114 """
115 width, height = self.page_size
116 cur_drawing = Drawing(width, height)
117
118 self._draw_title(cur_drawing, title, width, height)
119
120 cur_x_pos = inch * .5
121 if len(self._sub_components) > 0:
122 x_pos_change = (width - inch) / len(self._sub_components)
123
124 else:
125 pass
126
127 for sub_component in self._sub_components:
128
129 sub_component.start_x_position = cur_x_pos
130 sub_component.end_x_position = cur_x_pos + .9 * x_pos_change
131 sub_component.start_y_position = height - 1.5 * inch
132 sub_component.end_y_position = 3 * inch
133
134
135 sub_component.draw(cur_drawing)
136
137
138 cur_x_pos += x_pos_change
139
140 self._draw_legend(cur_drawing, 2.5 * inch, width)
141
142 return _write(cur_drawing, output_file, self.output_format)
143
144 - def _draw_title(self, cur_drawing, title, width, height):
145 """Write out the title of the organism figure.
146 """
147 title_string = String(width / 2, height - inch, title)
148 title_string.fontName = 'Helvetica-Bold'
149 title_string.fontSize = self.title_size
150 title_string.textAnchor = "middle"
151
152 cur_drawing.add(title_string)
153
155 """Draw a legend for the figure.
156
157 Subclasses should implement this to provide specialized legends.
158 """
159 pass
160
162 """Class for drawing a chromosome of an organism.
163
164 This organizes the drawing of a single organisms chromosome. This
165 class can be instantiated directly, but the draw method makes the
166 most sense to be called in the context of an organism.
167 """
169 """Initialize a Chromosome for drawing.
170
171 Arguments:
172
173 o chromosome_name - The label for the chromosome.
174
175 Attributes:
176
177 o start_x_position, end_x_position - The x positions on the page
178 where the chromosome should be drawn. This allows multiple
179 chromosomes to be drawn on a single page.
180
181 o start_y_position, end_y_position - The y positions on the page
182 where the chromosome should be contained.
183
184 Configuration Attributes:
185
186 o title_size - The size of the chromosome title.
187
188 o scale_num - A number of scale the drawing by. This is useful if
189 you want to draw multiple chromosomes of different sizes at the
190 same scale. If this is not set, then the chromosome drawing will
191 be scaled by the number of segements in the chromosome (so each
192 chromosome will be the exact same final size).
193 """
194 _ChromosomeComponent.__init__(self)
195
196 self._name = chromosome_name
197
198 self.start_x_position = -1
199 self.end_x_position = -1
200 self.start_y_position = -1
201 self.end_y_position = -1
202
203 self.title_size = 20
204 self.scale_num = None
205
207 """Return the scaled size of all subcomponents of this component.
208 """
209 total_sub = 0
210 for sub_component in self._sub_components:
211 total_sub += sub_component.scale
212
213 return total_sub
214
215 - def draw(self, cur_drawing):
216 """Draw a chromosome on the specified template.
217
218 Ideally, the x_position and y_*_position attributes should be
219 set prior to drawing -- otherwise we're going to have some problems.
220 """
221 for position in (self.start_x_position, self.end_x_position,
222 self.start_y_position, self.end_y_position):
223 assert position != -1, "Need to set drawing coordinates."
224
225
226
227 cur_y_pos = self.start_y_position
228 if self.scale_num:
229 y_pos_change = ((self.start_y_position * .95 - self.end_y_position)
230 / self.scale_num)
231 elif len(self._sub_components) > 0:
232 y_pos_change = ((self.start_y_position * .95 - self.end_y_position)
233 / self.subcomponent_size())
234
235 else:
236 pass
237
238 for sub_component in self._sub_components:
239 this_y_pos_change = sub_component.scale * y_pos_change
240
241
242 sub_component.start_x_position = self.start_x_position
243 sub_component.end_x_position = self.end_x_position
244 sub_component.start_y_position = cur_y_pos
245 sub_component.end_y_position = cur_y_pos - this_y_pos_change
246
247
248 sub_component.draw(cur_drawing)
249
250
251 cur_y_pos -= this_y_pos_change
252
253 self._draw_label(cur_drawing, self._name)
254
256 """Draw a label for the chromosome.
257 """
258 x_position = self.start_x_position
259 y_position = self.end_y_position
260
261 label_string = String(x_position, y_position, label_name)
262 label_string.fontName = 'Times-BoldItalic'
263 label_string.fontSize = self.title_size
264 label_string.textAnchor = 'start'
265
266 cur_drawing.add(label_string)
267
269 """Draw a segment of a chromosome.
270
271 This class provides the important configurable functionality of drawing
272 a Chromosome. Each segment has some customization available here, or can
273 be subclassed to define additional functionality. Most of the interesting
274 drawing stuff is likely to happen at the ChromosomeSegment level.
275 """
277 """Initialize a ChromosomeSegment.
278
279 Attributes:
280 o start_x_position, end_x_position - Defines the x range we have
281 to draw things in.
282
283 o start_y_position, end_y_position - Defines the y range we have
284 to draw things in.
285
286 Configuration Attributes:
287
288 o scale - A scaling value for the component. By default this is
289 set at 1 (ie -- has the same scale as everything else). Higher
290 values give more size to the component, smaller values give less.
291
292 o fill_color - A color to fill in the segment with. Colors are
293 available in reportlab.lib.colors
294
295 o label - A label to place on the chromosome segment. This should
296 be a text string specifying what is to be included in the label.
297
298 o label_size - The size of the label.
299
300 o chr_percent - The percentage of area that the chromosome
301 segment takes up.
302 """
303 _ChromosomeComponent.__init__(self)
304
305 self.start_x_position = -1
306 self.end_x_position = -1
307 self.start_y_position = -1
308 self.end_y_position = -1
309
310
311 self.scale = 1
312 self.fill_color = None
313 self.label = None
314 self.label_size = 6
315 self.chr_percent = .25
316
317 - def draw(self, cur_drawing):
318 """Draw a chromosome segment.
319
320 Before drawing, the range we are drawing in needs to be set.
321 """
322 for position in (self.start_x_position, self.end_x_position,
323 self.start_y_position, self.end_y_position):
324 assert position != -1, "Need to set drawing coordinates."
325
326 self._draw_subcomponents(cur_drawing)
327 self._draw_segment(cur_drawing)
328 self._draw_label(cur_drawing)
329
331 """Draw any subcomponents of the chromosome segment.
332
333 This should be overridden in derived classes if there are
334 subcomponents to be drawn.
335 """
336 pass
337
339 """Draw the current chromosome segment.
340 """
341
342
343 segment_x = self.start_x_position
344 segment_y = self.end_y_position
345 segment_width = (self.end_x_position - self.start_x_position) \
346 * self.chr_percent
347 segment_height = self.start_y_position - self.end_y_position
348
349
350 right_line = Line(segment_x, segment_y,
351 segment_x, segment_y + segment_height)
352 left_line = Line(segment_x + segment_width, segment_y,
353 segment_x + segment_width, segment_y + segment_height)
354
355 cur_drawing.add(right_line)
356 cur_drawing.add(left_line)
357
358
359 if self.fill_color is not None:
360 fill_rectangle = Rect(segment_x, segment_y,
361 segment_width, segment_height)
362 fill_rectangle.fillColor = self.fill_color
363 fill_rectangle.strokeColor = None
364
365 cur_drawing.add(fill_rectangle)
366
368 """Add a label to the chromosome segment.
369 """
370
371 if self.label is not None:
372
373 label_x = self.start_x_position + \
374 (self.chr_percent + 0.05) * (self.end_x_position -
375 self.start_x_position)
376 label_y = ((self.start_y_position - self.end_y_position) / 2 +
377 self.end_y_position)
378
379 label_string = String(label_x, label_y, self.label)
380 label_string.fontName = 'Helvetica'
381 label_string.fontSize = self.label_size
382
383 cur_drawing.add(label_string)
384
386 """A segment that is located at the end of a linear chromosome.
387
388 This is just like a regular segment, but it draws the end of a chromosome
389 which is represented by a half circle. This just overrides the
390 _draw_segment class of ChromosomeSegment to provide that specialized
391 drawing.
392 """
394 """Initialize a segment at the end of a chromosome.
395
396 See ChromosomeSegment for all of the attributes that can be
397 customized in a TelomereSegments.
398
399 Arguments:
400
401 o inverted -- Whether or not the telomere should be inverted
402 (ie. drawn on the bottom of a chromosome)
403 """
404 ChromosomeSegment.__init__(self)
405
406 self._inverted = inverted
407
409 """Draw a half circle representing the end of a linear chromosome.
410 """
411
412
413 width = (self.end_x_position - self.start_x_position) \
414 * self.chr_percent
415 height = self.start_y_position - self.end_y_position
416
417 center_x = self.start_x_position + width / 2
418 if self._inverted:
419 center_y = self.start_y_position
420 start_angle = 180
421 end_angle = 360
422 else:
423 center_y = self.end_y_position
424 start_angle = 0
425 end_angle = 180
426
427 cap_wedge = Wedge(center_x, center_y, width / 2,
428 start_angle, end_angle, height / 2)
429
430 cap_wedge.fillColor = self.fill_color
431 cur_drawing.add(cap_wedge)
432
433
434 if self._inverted:
435 cover_line = Line(self.start_x_position, self.start_y_position,
436 self.start_x_position + width,
437 self.start_y_position)
438 else:
439 cover_line = Line(self.start_x_position, self.end_y_position,
440 self.start_x_position + width,
441 self.end_y_position)
442
443 if self.fill_color is not None:
444 cover_color = self.fill_color
445 else:
446 cover_color = colors.white
447
448 cover_line.strokeColor = cover_color
449 cur_drawing.add(cover_line)
450