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