Package Bio :: Package Graphics :: Module BasicChromosome
[hide private]
[frames] | no frames]

Source Code for Module Bio.Graphics.BasicChromosome

  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  # standard library 
 29  import os 
 30   
 31  # reportlab 
 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   
43 -class _ChromosomeComponent(Widget):
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 """
49 - def __init__(self):
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
68 - def remove(self, component):
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
80 - def draw(self):
81 """Draw the specified component. 82 """ 83 raise AssertionError("Subclasses must implement.")
84
85 -class Organism(_ChromosomeComponent):
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'):
96 _ChromosomeComponent.__init__(self) 97 98 # customizable attributes 99 self.page_size = letter 100 self.title_size = 20 101 102 self.output_format = output_format
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 # no sub_components 124 else: 125 pass 126 127 for sub_component in self._sub_components: 128 # set the drawing location of the chromosome 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 # do the drawing 135 sub_component.draw(cur_drawing) 136 137 # update the locations for the next chromosome 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
154 - def _draw_legend(self, cur_drawing, start_y, width):
155 """Draw a legend for the figure. 156 157 Subclasses should implement this to provide specialized legends. 158 """ 159 pass
160
161 -class Chromosome(_ChromosomeComponent):
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 """
168 - def __init__(self, chromosome_name):
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
206 - def subcomponent_size(self):
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 # first draw all of the sub-sections of the chromosome -- this 226 # will actually be the picture of the chromosome 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 # no sub_components to draw 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 # set the location of the component to draw 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 # draw the sub component 248 sub_component.draw(cur_drawing) 249 250 # update the position for the next component 251 cur_y_pos -= this_y_pos_change 252 253 self._draw_label(cur_drawing, self._name)
254
255 - def _draw_label(self, cur_drawing, label_name):
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
268 -class ChromosomeSegment(_ChromosomeComponent):
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 """
276 - def __init__(self):
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 # --- attributes for configuration 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
330 - def _draw_subcomponents(self, cur_drawing):
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
338 - def _draw_segment(self, cur_drawing):
339 """Draw the current chromosome segment. 340 """ 341 # set the coordinates of the segment -- it'll take up the left part 342 # of the space we have. 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 # first draw the sides of the segment 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 # now draw the box, if it is filled in 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
367 - def _draw_label(self, cur_drawing):
368 """Add a label to the chromosome segment. 369 """ 370 # the label will be applied to the right of the segment 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
385 -class TelomereSegment(ChromosomeSegment):
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 """
393 - def __init__(self, inverted = 0):
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
408 - def _draw_segment(self, cur_drawing):
409 """Draw a half circle representing the end of a linear chromosome. 410 """ 411 # set the coordinates of the segment -- it'll take up the left part 412 # of the space we have. 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 # draw a line to cover up the the bottom part of the wedge 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