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

Source Code for Module Bio.Graphics.BasicChromosome

  1  # This code is part of the Biopython distribution and governed by its 
  2  # license.  Please see the LICENSE file that should have been included 
  3  # as part of this package. 
  4  # 
  5   
  6  """Draw representations of organism chromosomes with added information. 
  7   
  8  These classes are meant to model the drawing of pictures of chromosomes. 
  9  This can be useful for lots of things, including displaying markers on 
 10  a chromosome (ie. for genetic mapping) and showing syteny between two 
 11  chromosomes. 
 12   
 13  The structure of these classes is intended to be a Composite, so that 
 14  it will be easy to plug in and switch different parts without 
 15  breaking the general drawing capabilities of the system. The 
 16  relationship between classes is that everything derives from 
 17  _ChromosomeComponent, which specifies the overall interface. The parts 
 18  then are related so that an Organism contains Chromosomes, and these 
 19  Chromosomes contain ChromosomeSegments. This representation differents 
 20  from the canonical composite structure in that we don't really have 
 21  'leaf' nodes here -- all components can potentially hold sub-components. 
 22   
 23  Most of the time the ChromosomeSegment class is what you'll want to 
 24  customize for specific drawing tasks. 
 25   
 26  For providing drawing capabilities, these classes use reportlab: 
 27   
 28  http://www.reportlab.com 
 29   
 30  This provides nice output in PDF, SVG and postscript.  If you have 
 31  reportlab's renderPM module installed you can also use PNG etc. 
 32  """ 
 33   
 34  # reportlab 
 35  from reportlab.lib.pagesizes import letter 
 36  from reportlab.lib.units import inch 
 37  from reportlab.lib import colors 
 38  from reportlab.pdfbase.pdfmetrics import stringWidth 
 39   
 40  from reportlab.graphics.shapes import Drawing, String, Line, Rect, Wedge, ArcPath 
 41  from reportlab.graphics.widgetbase import Widget 
 42   
 43  from Bio.Graphics import _write 
 44  from Bio.Graphics.GenomeDiagram._Colors import ColorTranslator as _ColorTranslator 
 45   
 46   
 47  _color_trans = _ColorTranslator() 
 48   
 49   
50 -class _ChromosomeComponent(Widget):
51 """Base class specifying the interface for a component of the system. 52 53 This class should not be instantiated directly, but should be used 54 from derived classes. 55 """
56 - def __init__(self):
57 """Initialize a chromosome component. 58 59 Attributes: 60 61 o _sub_components -- Any components which are contained under 62 this parent component. This attribute should be accessed through 63 the add() and remove() functions. 64 """ 65 self._sub_components = []
66
67 - def add(self, component):
68 """Add a sub_component to the list of components under this item. 69 """ 70 assert isinstance(component, _ChromosomeComponent), \ 71 "Expected a _ChromosomeComponent object, got %s" % component 72 73 self._sub_components.append(component)
74
75 - def remove(self, component):
76 """Remove the specified component from the subcomponents. 77 78 Raises a ValueError if the component is not registered as a 79 sub_component. 80 """ 81 try: 82 self._sub_components.remove(component) 83 except ValueError: 84 raise ValueError("Component %s not found in sub_components." % 85 component)
86
87 - def draw(self):
88 """Draw the specified component. 89 """ 90 raise AssertionError("Subclasses must implement.")
91 92
93 -class Organism(_ChromosomeComponent):
94 """Top level class for drawing chromosomes. 95 96 This class holds information about an organism and all of it's 97 chromosomes, and provides the top level object which could be used 98 for drawing a chromosome representation of an organism. 99 100 Chromosomes should be added and removed from the Organism via the 101 add and remove functions. 102 """
103 - def __init__(self, output_format='pdf'):
104 _ChromosomeComponent.__init__(self) 105 106 # customizable attributes 107 self.page_size = letter 108 self.title_size = 20 109 110 # Do we need this given we don't draw a legend? 111 # If so, should be a public API... 112 self._legend_height = 0 # 2 * inch 113 114 self.output_format = output_format
115
116 - def draw(self, output_file, title):
117 """Draw out the information for the Organism. 118 119 Arguments: 120 121 o output_file -- The name of a file specifying where the 122 document should be saved, or a handle to be written to. 123 The output format is set when creating the Organism object. 124 Alternatively, output_file=None will return the drawing using 125 the low-level ReportLab objects (for further processing, such 126 as adding additional graphics, before writing). 127 128 o title -- The output title of the produced document. 129 """ 130 width, height = self.page_size 131 cur_drawing = Drawing(width, height) 132 133 self._draw_title(cur_drawing, title, width, height) 134 135 cur_x_pos = inch * .5 136 if len(self._sub_components) > 0: 137 x_pos_change = (width - inch) / len(self._sub_components) 138 # no sub_components 139 else: 140 pass 141 142 for sub_component in self._sub_components: 143 # set the drawing location of the chromosome 144 sub_component.start_x_position = cur_x_pos + 0.05 * x_pos_change 145 sub_component.end_x_position = cur_x_pos + 0.95 * x_pos_change 146 sub_component.start_y_position = height - 1.5 * inch 147 sub_component.end_y_position = self._legend_height + 1 * inch 148 149 # do the drawing 150 sub_component.draw(cur_drawing) 151 152 # update the locations for the next chromosome 153 cur_x_pos += x_pos_change 154 155 self._draw_legend(cur_drawing, self._legend_height + 0.5 * inch, width) 156 157 if output_file is None: 158 # Let the user take care of writing to the file... 159 return cur_drawing 160 161 return _write(cur_drawing, output_file, self.output_format)
162
163 - def _draw_title(self, cur_drawing, title, width, height):
164 """Write out the title of the organism figure. 165 """ 166 title_string = String(width / 2, height - inch, title) 167 title_string.fontName = 'Helvetica-Bold' 168 title_string.fontSize = self.title_size 169 title_string.textAnchor = "middle" 170 171 cur_drawing.add(title_string)
172
173 - def _draw_legend(self, cur_drawing, start_y, width):
174 """Draw a legend for the figure. 175 176 Subclasses should implement this (see also self._legend_height) to 177 provide specialized legends. 178 """ 179 pass
180 181
182 -class Chromosome(_ChromosomeComponent):
183 """Class for drawing a chromosome of an organism. 184 185 This organizes the drawing of a single organisms chromosome. This 186 class can be instantiated directly, but the draw method makes the 187 most sense to be called in the context of an organism. 188 """
189 - def __init__(self, chromosome_name):
190 """Initialize a Chromosome for drawing. 191 192 Arguments: 193 194 o chromosome_name - The label for the chromosome. 195 196 Attributes: 197 198 o start_x_position, end_x_position - The x positions on the page 199 where the chromosome should be drawn. This allows multiple 200 chromosomes to be drawn on a single page. 201 202 o start_y_position, end_y_position - The y positions on the page 203 where the chromosome should be contained. 204 205 Configuration Attributes: 206 207 o title_size - The size of the chromosome title. 208 209 o scale_num - A number of scale the drawing by. This is useful if 210 you want to draw multiple chromosomes of different sizes at the 211 same scale. If this is not set, then the chromosome drawing will 212 be scaled by the number of segements in the chromosome (so each 213 chromosome will be the exact same final size). 214 """ 215 _ChromosomeComponent.__init__(self) 216 217 self._name = chromosome_name 218 219 self.start_x_position = -1 220 self.end_x_position = -1 221 self.start_y_position = -1 222 self.end_y_position = -1 223 224 self.title_size = 20 225 self.scale_num = None 226 227 self.label_size = 6 228 self.chr_percent = 0.25 229 self.label_sep_percent = self.chr_percent * 0.5 230 self._color_labels = False
231
232 - def subcomponent_size(self):
233 """Return the scaled size of all subcomponents of this component. 234 """ 235 total_sub = 0 236 for sub_component in self._sub_components: 237 total_sub += sub_component.scale 238 239 return total_sub
240
241 - def draw(self, cur_drawing):
242 """Draw a chromosome on the specified template. 243 244 Ideally, the x_position and y_*_position attributes should be 245 set prior to drawing -- otherwise we're going to have some problems. 246 """ 247 for position in (self.start_x_position, self.end_x_position, 248 self.start_y_position, self.end_y_position): 249 assert position != -1, "Need to set drawing coordinates." 250 251 # first draw all of the sub-sections of the chromosome -- this 252 # will actually be the picture of the chromosome 253 cur_y_pos = self.start_y_position 254 if self.scale_num: 255 y_pos_change = ((self.start_y_position * .95 - self.end_y_position) / 256 self.scale_num) 257 elif len(self._sub_components) > 0: 258 y_pos_change = ((self.start_y_position * .95 - self.end_y_position) / 259 self.subcomponent_size()) 260 # no sub_components to draw 261 else: 262 pass 263 264 left_labels = [] 265 right_labels = [] 266 for sub_component in self._sub_components: 267 this_y_pos_change = sub_component.scale * y_pos_change 268 269 # set the location of the component to draw 270 sub_component.start_x_position = self.start_x_position 271 sub_component.end_x_position = self.end_x_position 272 sub_component.start_y_position = cur_y_pos 273 sub_component.end_y_position = cur_y_pos - this_y_pos_change 274 275 # draw the sub component 276 sub_component._left_labels = [] 277 sub_component._right_labels = [] 278 sub_component.draw(cur_drawing) 279 left_labels += sub_component._left_labels 280 right_labels += sub_component._right_labels 281 282 # update the position for the next component 283 cur_y_pos -= this_y_pos_change 284 285 self._draw_labels(cur_drawing, left_labels, right_labels) 286 self._draw_label(cur_drawing, self._name)
287
288 - def _draw_label(self, cur_drawing, label_name):
289 """Draw a label for the chromosome. 290 """ 291 x_position = 0.5 * (self.start_x_position + self.end_x_position) 292 y_position = self.end_y_position 293 294 label_string = String(x_position, y_position, label_name) 295 label_string.fontName = 'Times-BoldItalic' 296 label_string.fontSize = self.title_size 297 label_string.textAnchor = 'middle' 298 299 cur_drawing.add(label_string)
300
301 - def _draw_labels(self, cur_drawing, left_labels, right_labels):
302 """Layout and draw sub-feature labels for the chromosome. 303 304 Tries to place each label at the same vertical position as the 305 feature it applies to, but will adjust the positions to avoid or 306 at least reduce label overlap. 307 308 Draws the label text and a coloured line linking it to the 309 location (i.e. feature) it applies to. 310 """ 311 if not self._sub_components: 312 return 313 color_label = self._color_labels 314 315 segment_width = (self.end_x_position - self.start_x_position) \ 316 * self.chr_percent 317 label_sep = (self.end_x_position - self.start_x_position) \ 318 * self.label_sep_percent 319 segment_x = self.start_x_position \ 320 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 321 322 y_limits = [] 323 for sub_component in self._sub_components: 324 y_limits.extend((sub_component.start_y_position, sub_component.end_y_position)) 325 y_min = min(y_limits) 326 y_max = max(y_limits) 327 del y_limits 328 # Now do some label placement magic... 329 # from reportlab.pdfbase import pdfmetrics 330 # font = pdfmetrics.getFont('Helvetica') 331 # h = (font.face.ascent + font.face.descent) * 0.90 332 h = self.label_size 333 for x1, x2, labels, anchor in [ 334 (segment_x, 335 segment_x - label_sep, 336 _place_labels(left_labels, y_min, y_max, h), 337 "end"), 338 (segment_x + segment_width, 339 segment_x + segment_width + label_sep, 340 _place_labels(right_labels, y_min, y_max, h), 341 "start"), 342 ]: 343 for (y1, y2, color, back_color, name) in labels: 344 cur_drawing.add(Line(x1, y1, x2, y2, 345 strokeColor=color, 346 strokeWidth=0.25)) 347 label_string = String(x2, y2, name, 348 textAnchor=anchor) 349 label_string.fontName = 'Helvetica' 350 label_string.fontSize = h 351 if color_label: 352 label_string.fillColor = color 353 if back_color: 354 w = stringWidth(name, label_string.fontName, label_string.fontSize) 355 if x1 > x2: 356 w = w * -1.0 357 cur_drawing.add(Rect(x2, y2 - 0.1 * h, w, h, 358 strokeColor=back_color, 359 fillColor=back_color)) 360 cur_drawing.add(label_string)
361 362
363 -class ChromosomeSegment(_ChromosomeComponent):
364 """Draw a segment of a chromosome. 365 366 This class provides the important configurable functionality of drawing 367 a Chromosome. Each segment has some customization available here, or can 368 be subclassed to define additional functionality. Most of the interesting 369 drawing stuff is likely to happen at the ChromosomeSegment level. 370 """
371 - def __init__(self):
372 """Initialize a ChromosomeSegment. 373 374 Attributes: 375 o start_x_position, end_x_position - Defines the x range we have 376 to draw things in. 377 378 o start_y_position, end_y_position - Defines the y range we have 379 to draw things in. 380 381 Configuration Attributes: 382 383 o scale - A scaling value for the component. By default this is 384 set at 1 (ie -- has the same scale as everything else). Higher 385 values give more size to the component, smaller values give less. 386 387 o fill_color - A color to fill in the segment with. Colors are 388 available in reportlab.lib.colors 389 390 o label - A label to place on the chromosome segment. This should 391 be a text string specifying what is to be included in the label. 392 393 o label_size - The size of the label. 394 395 o chr_percent - The percentage of area that the chromosome 396 segment takes up. 397 """ 398 _ChromosomeComponent.__init__(self) 399 400 self.start_x_position = -1 401 self.end_x_position = -1 402 self.start_y_position = -1 403 self.end_y_position = -1 404 405 # --- attributes for configuration 406 self.scale = 1 407 self.fill_color = None 408 self.label = None 409 self.label_size = 6 410 self.chr_percent = .25
411
412 - def draw(self, cur_drawing):
413 """Draw a chromosome segment. 414 415 Before drawing, the range we are drawing in needs to be set. 416 """ 417 for position in (self.start_x_position, self.end_x_position, 418 self.start_y_position, self.end_y_position): 419 assert position != -1, "Need to set drawing coordinates." 420 421 self._draw_subcomponents(cur_drawing) # Anything behind 422 self._draw_segment(cur_drawing) 423 self._overdraw_subcomponents(cur_drawing) # Anything on top 424 self._draw_label(cur_drawing)
425
426 - def _draw_subcomponents(self, cur_drawing):
427 """Draw any subcomponents of the chromosome segment. 428 429 This should be overridden in derived classes if there are 430 subcomponents to be drawn. 431 """ 432 pass
433
434 - def _draw_segment(self, cur_drawing):
435 """Draw the current chromosome segment. 436 """ 437 # set the coordinates of the segment -- it'll take up the MIDDLE part 438 # of the space we have. 439 segment_y = self.end_y_position 440 segment_width = (self.end_x_position - self.start_x_position) \ 441 * self.chr_percent 442 segment_height = self.start_y_position - self.end_y_position 443 segment_x = self.start_x_position \ 444 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 445 446 # first draw the sides of the segment 447 right_line = Line(segment_x, segment_y, 448 segment_x, segment_y + segment_height) 449 left_line = Line(segment_x + segment_width, segment_y, 450 segment_x + segment_width, segment_y + segment_height) 451 452 cur_drawing.add(right_line) 453 cur_drawing.add(left_line) 454 455 # now draw the box, if it is filled in 456 if self.fill_color is not None: 457 fill_rectangle = Rect(segment_x, segment_y, 458 segment_width, segment_height) 459 fill_rectangle.fillColor = self.fill_color 460 fill_rectangle.strokeColor = None 461 462 cur_drawing.add(fill_rectangle)
463
464 - def _overdraw_subcomponents(self, cur_drawing):
465 """Draw any subcomponents of the chromosome segment over the main part. 466 467 This should be overridden in derived classes if there are 468 subcomponents to be drawn. 469 """ 470 pass
471
472 - def _draw_label(self, cur_drawing):
473 """Add a label to the chromosome segment. 474 475 The label will be applied to the right of the segment. 476 477 This may be overlapped by any sub-feature labels on other segments! 478 """ 479 if self.label is not None: 480 481 label_x = 0.5 * (self.start_x_position + self.end_x_position) + \ 482 (self.chr_percent + 0.05) * (self.end_x_position - 483 self.start_x_position) 484 label_y = ((self.start_y_position - self.end_y_position) / 2 + 485 self.end_y_position) 486 487 label_string = String(label_x, label_y, self.label) 488 label_string.fontName = 'Helvetica' 489 label_string.fontSize = self.label_size 490 491 cur_drawing.add(label_string)
492 493
494 -def _spring_layout(desired, minimum, maximum, gap=0):
495 """Function to try and layout label co-ordinates (or other floats, PRIVATE). 496 497 Originally written for the y-axis vertical positioning of labels on a 498 chromosome diagram (where the minimum gap between y-axis co-ordinates is 499 the label height), it could also potentially be used for x-axis placement, 500 or indeed radial placement for circular chromosomes within GenomeDiagram. 501 502 In essence this is an optimisation problem, balancing the desire to have 503 each label as close as possible to its data point, but also to spread out 504 the labels to avoid overlaps. This could be described with a cost function 505 (modelling the label distance from the desired placement, and the inter- 506 label separations as springs) and solved as a multi-variable minimization 507 problem - perhaps with NumPy or SciPy. 508 509 For now however, the implementation is a somewhat crude ad hoc algorithm. 510 511 NOTE - This expects the input data to have been sorted! 512 """ 513 count = len(desired) 514 if count <= 1: 515 return desired # Easy! 516 if minimum >= maximum: 517 raise ValueError("Bad min/max %f and %f" % (minimum, maximum)) 518 if min(desired) < minimum or max(desired) > maximum: 519 raise ValueError("Data %f to %f out of bounds (%f to %f)" 520 % (min(desired), max(desired), minimum, maximum)) 521 equal_step = float(maximum - minimum) / (count - 1) 522 523 if equal_step < gap: 524 import warnings 525 from Bio import BiopythonWarning 526 warnings.warn("Too many labels to avoid overlap", BiopythonWarning) 527 # Crudest solution 528 return [minimum + i * equal_step for i in range(count)] 529 530 good = True 531 if gap: 532 prev = desired[0] 533 for next in desired[1:]: 534 if prev - next < gap: 535 good = False 536 break 537 if good: 538 return desired 539 540 span = maximum - minimum 541 for split in [0.5 * span, span / 3.0, 2 * span / 3.0, 0.25 * span, 0.75 * span]: 542 midpoint = minimum + split 543 low = [x for x in desired if x <= midpoint - 0.5 * gap] 544 high = [x for x in desired if x > midpoint + 0.5 * gap] 545 if len(low) + len(high) < count: 546 # Bad split point, points right on boundary 547 continue 548 elif not low and len(high) * gap <= (span - split) + 0.5 * gap: 549 # Give a little of the unused low space to the high points 550 return _spring_layout(high, midpoint + 0.5 * gap, maximum, gap) 551 elif not high and len(low) * gap <= split + 0.5 * gap: 552 # Give a little of the unused highspace to the low points 553 return _spring_layout(low, minimum, midpoint - 0.5 * gap, gap) 554 elif len(low) * gap <= split - 0.5 * gap \ 555 and len(high) * gap <= (span - split) - 0.5 * gap: 556 return _spring_layout(low, minimum, midpoint - 0.5 * gap, gap) + \ 557 _spring_layout(high, midpoint + 0.5 * gap, maximum, gap) 558 559 # This can be count-productive now we can split out into the telomere or 560 # spacer-segment's vertical space... 561 # Try not to spread out as far as the min/max unless needed 562 low = min(desired) 563 high = max(desired) 564 if (high - low) / (count - 1) >= gap: 565 # Good, we don't need the full range, and can position the 566 # min and max exactly as well :) 567 equal_step = (high - low) / (count - 1) 568 return [low + i * equal_step for i in range(count)] 569 570 low = 0.5 * (minimum + min(desired)) 571 high = 0.5 * (max(desired) + maximum) 572 if (high - low) / (count - 1) >= gap: 573 # Good, we don't need the full range 574 equal_step = (high - low) / (count - 1) 575 return [low + i * equal_step for i in range(count)] 576 577 # Crudest solution 578 return [minimum + i * equal_step for i in range(count)]
579 580 # assert False, _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) 581 # assert _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) == [0.0, 0.125, 0.25, 0.375, 0.5, 0.75, 1.0] 582 # assert _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) == [0.0, 0.16666666666666666, 0.33333333333333331, 0.5, 0.66666666666666663, 0.83333333333333326, 1.0] 583 584
585 -def _place_labels(desired_etc, minimum, maximum, gap=0):
586 # Want a list of lists/tuples for desired_etc 587 desired_etc.sort() 588 placed = _spring_layout([row[0] for row in desired_etc], 589 minimum, maximum, gap) 590 for old, y2 in zip(desired_etc, placed): 591 # (y1, a, b, c, ..., z) --> (y1, y2, a, b, c, ..., z) 592 yield (old[0], y2) + tuple(old[1:])
593 594
595 -class AnnotatedChromosomeSegment(ChromosomeSegment):
596 - def __init__(self, bp_length, features, 597 default_feature_color=colors.blue, 598 name_qualifiers=('gene', 'label', 'name', 'locus_tag', 'product')):
599 """Like the ChromosomeSegment, but accepts a list of features. 600 601 The features can either be SeqFeature objects, or tuples of values: 602 start (int), end (int), strand (+1, -1, O or None), label (string), 603 ReportLab color (string or object), and optional ReportLab fill color. 604 605 Note we require 0 <= start <= end <= bp_length, and within the vertical 606 space allocated to this segmenet lines will be places according to the 607 start/end coordinates (starting from the top). 608 609 Positive stand features are drawn on the right, negative on the left, 610 otherwise all the way across. 611 612 We recommend using consisent units for all the segment's scale values 613 (e.g. their length in base pairs). 614 615 When providing features as SeqFeature objects, the default color 616 is used, unless the feature's qualifiers include an Artemis colour 617 string (functionality also in GenomeDiagram). The caption also follows 618 the GenomeDiagram approach and takes the first qualifier from the list 619 or tuple specified in name_qualifiers. 620 621 Note additional attribute label_sep_percent controls the percentage of 622 area that the chromosome segment takes up, by default half of the 623 chr_percent attribute (half of 25%, thus 12.5%) 624 625 """ 626 ChromosomeSegment.__init__(self) 627 self.bp_length = bp_length 628 self.features = features 629 self.default_feature_color = default_feature_color 630 self.name_qualifiers = name_qualifiers 631 self.label_sep_percent = self.chr_percent * 0.5
632
633 - def _overdraw_subcomponents(self, cur_drawing):
634 """Draw any annotated features on the chromosome segment. 635 636 Assumes _draw_segment already called to fill out the basic shape, 637 and assmes that uses the same boundaries. 638 """ 639 # set the coordinates of the segment -- it'll take up the MIDDLE part 640 # of the space we have. 641 segment_y = self.end_y_position 642 segment_width = (self.end_x_position - self.start_x_position) \ 643 * self.chr_percent 644 label_sep = (self.end_x_position - self.start_x_position) \ 645 * self.label_sep_percent 646 segment_height = self.start_y_position - self.end_y_position 647 segment_x = self.start_x_position \ 648 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 649 650 left_labels = [] 651 right_labels = [] 652 for f in self.features: 653 try: 654 # Assume SeqFeature objects 655 start = f.location.start 656 end = f.location.end 657 strand = f.strand 658 try: 659 # Handles Artemis colour integers, HTML colors, etc 660 color = _color_trans.translate(f.qualifiers['color'][0]) 661 except Exception: # TODO: ValueError? 662 color = self.default_feature_color 663 fill_color = color 664 name = "" 665 for qualifier in self.name_qualifiers: 666 if qualifier in f.qualifiers: 667 name = f.qualifiers[qualifier][0] 668 break 669 except AttributeError: 670 # Assume tuple of ints, string, and color 671 start, end, strand, name, color = f[:5] 672 color = _color_trans.translate(color) 673 if len(f) > 5: 674 fill_color = _color_trans.translate(f[5]) 675 else: 676 fill_color = color 677 assert 0 <= start <= end <= self.bp_length 678 if strand == +1: 679 # Right side only 680 x = segment_x + segment_width * 0.6 681 w = segment_width * 0.4 682 elif strand == -1: 683 # Left side only 684 x = segment_x 685 w = segment_width * 0.4 686 else: 687 # Both or neither - full width 688 x = segment_x 689 w = segment_width 690 local_scale = segment_height / self.bp_length 691 fill_rectangle = Rect(x, segment_y + segment_height - local_scale * start, 692 w, local_scale * (start - end)) 693 fill_rectangle.fillColor = fill_color 694 fill_rectangle.strokeColor = color 695 cur_drawing.add(fill_rectangle) 696 if name: 697 if fill_color == color: 698 back_color = None 699 else: 700 back_color = fill_color 701 value = (segment_y + segment_height - local_scale * start, color, back_color, name) 702 if strand == -1: 703 self._left_labels.append(value) 704 else: 705 self._right_labels.append(value)
706 707
708 -class TelomereSegment(ChromosomeSegment):
709 """A segment that is located at the end of a linear chromosome. 710 711 This is just like a regular segment, but it draws the end of a chromosome 712 which is represented by a half circle. This just overrides the 713 _draw_segment class of ChromosomeSegment to provide that specialized 714 drawing. 715 """
716 - def __init__(self, inverted=0):
717 """Initialize a segment at the end of a chromosome. 718 719 See ChromosomeSegment for all of the attributes that can be 720 customized in a TelomereSegments. 721 722 Arguments: 723 724 o inverted -- Whether or not the telomere should be inverted 725 (ie. drawn on the bottom of a chromosome) 726 """ 727 ChromosomeSegment.__init__(self) 728 729 self._inverted = inverted
730
731 - def _draw_segment(self, cur_drawing):
732 """Draw a half circle representing the end of a linear chromosome. 733 """ 734 # set the coordinates of the segment -- it'll take up the MIDDLE part 735 # of the space we have. 736 width = (self.end_x_position - self.start_x_position) \ 737 * self.chr_percent 738 height = self.start_y_position - self.end_y_position 739 center_x = 0.5 * (self.end_x_position + self.start_x_position) 740 start_x = center_x - 0.5 * width 741 if self._inverted: 742 center_y = self.start_y_position 743 start_angle = 180 744 end_angle = 360 745 else: 746 center_y = self.end_y_position 747 start_angle = 0 748 end_angle = 180 749 750 cap_wedge = Wedge(center_x, center_y, width / 2, 751 start_angle, end_angle, height) 752 cap_wedge.strokeColor = None 753 cap_wedge.fillColor = self.fill_color 754 cur_drawing.add(cap_wedge) 755 756 # Now draw an arc for the curved edge of the wedge, 757 # omitting the flat end. 758 cap_arc = ArcPath() 759 cap_arc.addArc(center_x, center_y, width / 2, 760 start_angle, end_angle, height) 761 cur_drawing.add(cap_arc)
762 763
764 -class SpacerSegment(ChromosomeSegment):
765 """A segment that is located at the end of a linear chromosome. 766 767 Doesn't draw anything, just empty space which can be helpful 768 for layout purposes (e.g. making room for feature labels). 769 """ 770
771 - def draw(self, cur_diagram):
772 pass
773