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, 325 sub_component.end_y_position)) 326 y_min = min(y_limits) 327 y_max = max(y_limits) 328 del y_limits 329 # Now do some label placement magic... 330 # from reportlab.pdfbase import pdfmetrics 331 # font = pdfmetrics.getFont('Helvetica') 332 # h = (font.face.ascent + font.face.descent) * 0.90 333 h = self.label_size 334 for x1, x2, labels, anchor in [ 335 (segment_x, 336 segment_x - label_sep, 337 _place_labels(left_labels, y_min, y_max, h), 338 "end"), 339 (segment_x + segment_width, 340 segment_x + segment_width + label_sep, 341 _place_labels(right_labels, y_min, y_max, h), 342 "start"), 343 ]: 344 for (y1, y2, color, back_color, name) in labels: 345 cur_drawing.add(Line(x1, y1, x2, y2, 346 strokeColor=color, 347 strokeWidth=0.25)) 348 label_string = String(x2, y2, name, 349 textAnchor=anchor) 350 label_string.fontName = 'Helvetica' 351 label_string.fontSize = h 352 if color_label: 353 label_string.fillColor = color 354 if back_color: 355 w = stringWidth(name, 356 label_string.fontName, 357 label_string.fontSize) 358 if x1 > x2: 359 w = w * -1.0 360 cur_drawing.add(Rect(x2, y2 - 0.1 * h, w, h, 361 strokeColor=back_color, 362 fillColor=back_color)) 363 cur_drawing.add(label_string)
364 365
366 -class ChromosomeSegment(_ChromosomeComponent):
367 """Draw a segment of a chromosome. 368 369 This class provides the important configurable functionality of drawing 370 a Chromosome. Each segment has some customization available here, or can 371 be subclassed to define additional functionality. Most of the interesting 372 drawing stuff is likely to happen at the ChromosomeSegment level. 373 """
374 - def __init__(self):
375 """Initialize a ChromosomeSegment. 376 377 Attributes: 378 o start_x_position, end_x_position - Defines the x range we have 379 to draw things in. 380 381 o start_y_position, end_y_position - Defines the y range we have 382 to draw things in. 383 384 Configuration Attributes: 385 386 o scale - A scaling value for the component. By default this is 387 set at 1 (ie -- has the same scale as everything else). Higher 388 values give more size to the component, smaller values give less. 389 390 o fill_color - A color to fill in the segment with. Colors are 391 available in reportlab.lib.colors 392 393 o label - A label to place on the chromosome segment. This should 394 be a text string specifying what is to be included in the label. 395 396 o label_size - The size of the label. 397 398 o chr_percent - The percentage of area that the chromosome 399 segment takes up. 400 """ 401 _ChromosomeComponent.__init__(self) 402 403 self.start_x_position = -1 404 self.end_x_position = -1 405 self.start_y_position = -1 406 self.end_y_position = -1 407 408 # --- attributes for configuration 409 self.scale = 1 410 self.fill_color = None 411 self.label = None 412 self.label_size = 6 413 self.chr_percent = .25
414
415 - def draw(self, cur_drawing):
416 """Draw a chromosome segment. 417 418 Before drawing, the range we are drawing in needs to be set. 419 """ 420 for position in (self.start_x_position, self.end_x_position, 421 self.start_y_position, self.end_y_position): 422 assert position != -1, "Need to set drawing coordinates." 423 424 self._draw_subcomponents(cur_drawing) # Anything behind 425 self._draw_segment(cur_drawing) 426 self._overdraw_subcomponents(cur_drawing) # Anything on top 427 self._draw_label(cur_drawing)
428
429 - def _draw_subcomponents(self, cur_drawing):
430 """Draw any subcomponents of the chromosome segment. 431 432 This should be overridden in derived classes if there are 433 subcomponents to be drawn. 434 """ 435 pass
436
437 - def _draw_segment(self, cur_drawing):
438 """Draw the current chromosome segment. 439 """ 440 # set the coordinates of the segment -- it'll take up the MIDDLE part 441 # of the space we have. 442 segment_y = self.end_y_position 443 segment_width = (self.end_x_position - self.start_x_position) \ 444 * self.chr_percent 445 segment_height = self.start_y_position - self.end_y_position 446 segment_x = self.start_x_position \ 447 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 448 449 # first draw the sides of the segment 450 right_line = Line(segment_x, segment_y, 451 segment_x, segment_y + segment_height) 452 left_line = Line(segment_x + segment_width, segment_y, 453 segment_x + segment_width, segment_y + segment_height) 454 455 cur_drawing.add(right_line) 456 cur_drawing.add(left_line) 457 458 # now draw the box, if it is filled in 459 if self.fill_color is not None: 460 fill_rectangle = Rect(segment_x, segment_y, 461 segment_width, segment_height) 462 fill_rectangle.fillColor = self.fill_color 463 fill_rectangle.strokeColor = None 464 465 cur_drawing.add(fill_rectangle)
466
467 - def _overdraw_subcomponents(self, cur_drawing):
468 """Draw any subcomponents of the chromosome segment over the main part. 469 470 This should be overridden in derived classes if there are 471 subcomponents to be drawn. 472 """ 473 pass
474
475 - def _draw_label(self, cur_drawing):
476 """Add a label to the chromosome segment. 477 478 The label will be applied to the right of the segment. 479 480 This may be overlapped by any sub-feature labels on other segments! 481 """ 482 if self.label is not None: 483 484 label_x = 0.5 * (self.start_x_position + self.end_x_position) + \ 485 (self.chr_percent + 0.05) * (self.end_x_position - 486 self.start_x_position) 487 label_y = ((self.start_y_position - self.end_y_position) / 2 + 488 self.end_y_position) 489 490 label_string = String(label_x, label_y, self.label) 491 label_string.fontName = 'Helvetica' 492 label_string.fontSize = self.label_size 493 494 cur_drawing.add(label_string)
495 496
497 -def _spring_layout(desired, minimum, maximum, gap=0):
498 """Function to try and layout label co-ordinates (or other floats, PRIVATE). 499 500 Originally written for the y-axis vertical positioning of labels on a 501 chromosome diagram (where the minimum gap between y-axis co-ordinates is 502 the label height), it could also potentially be used for x-axis placement, 503 or indeed radial placement for circular chromosomes within GenomeDiagram. 504 505 In essence this is an optimisation problem, balancing the desire to have 506 each label as close as possible to its data point, but also to spread out 507 the labels to avoid overlaps. This could be described with a cost function 508 (modelling the label distance from the desired placement, and the inter- 509 label separations as springs) and solved as a multi-variable minimization 510 problem - perhaps with NumPy or SciPy. 511 512 For now however, the implementation is a somewhat crude ad hoc algorithm. 513 514 NOTE - This expects the input data to have been sorted! 515 """ 516 count = len(desired) 517 if count <= 1: 518 return desired # Easy! 519 if minimum >= maximum: 520 raise ValueError("Bad min/max %f and %f" % (minimum, maximum)) 521 if min(desired) < minimum or max(desired) > maximum: 522 raise ValueError("Data %f to %f out of bounds (%f to %f)" 523 % (min(desired), max(desired), minimum, maximum)) 524 equal_step = float(maximum - minimum) / (count - 1) 525 526 if equal_step < gap: 527 import warnings 528 from Bio import BiopythonWarning 529 warnings.warn("Too many labels to avoid overlap", BiopythonWarning) 530 # Crudest solution 531 return [minimum + i * equal_step for i in range(count)] 532 533 good = True 534 if gap: 535 prev = desired[0] 536 for next in desired[1:]: 537 if prev - next < gap: 538 good = False 539 break 540 if good: 541 return desired 542 543 span = maximum - minimum 544 for split in [0.5 * span, 545 span / 3.0, 546 2 * span / 3.0, 547 0.25 * span, 548 0.75 * span]: 549 midpoint = minimum + split 550 low = [x for x in desired if x <= midpoint - 0.5 * gap] 551 high = [x for x in desired if x > midpoint + 0.5 * gap] 552 if len(low) + len(high) < count: 553 # Bad split point, points right on boundary 554 continue 555 elif not low and len(high) * gap <= (span - split) + 0.5 * gap: 556 # Give a little of the unused low space to the high points 557 return _spring_layout(high, midpoint + 0.5 * gap, maximum, gap) 558 elif not high and len(low) * gap <= split + 0.5 * gap: 559 # Give a little of the unused highspace to the low points 560 return _spring_layout(low, minimum, midpoint - 0.5 * gap, gap) 561 elif (len(low) * gap <= split - 0.5 * gap and 562 len(high) * gap <= (span - split) - 0.5 * gap): 563 return _spring_layout(low, minimum, midpoint - 0.5 * gap, gap) + \ 564 _spring_layout(high, midpoint + 0.5 * gap, maximum, gap) 565 566 # This can be count-productive now we can split out into the telomere or 567 # spacer-segment's vertical space... 568 # Try not to spread out as far as the min/max unless needed 569 low = min(desired) 570 high = max(desired) 571 if (high - low) / (count - 1) >= gap: 572 # Good, we don't need the full range, and can position the 573 # min and max exactly as well :) 574 equal_step = (high - low) / (count - 1) 575 return [low + i * equal_step for i in range(count)] 576 577 low = 0.5 * (minimum + min(desired)) 578 high = 0.5 * (max(desired) + maximum) 579 if (high - low) / (count - 1) >= gap: 580 # Good, we don't need the full range 581 equal_step = (high - low) / (count - 1) 582 return [low + i * equal_step for i in range(count)] 583 584 # Crudest solution 585 return [minimum + i * equal_step for i in range(count)]
586 587 # assert False, _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) 588 # assert _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) == \ 589 # [0.0, 0.125, 0.25, 0.375, 0.5, 0.75, 1.0] 590 # assert _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) == \ 591 # [0.0, 0.16666666666666666, 0.33333333333333331, 0.5, 592 # 0.66666666666666663, 0.83333333333333326, 1.0] 593 594
595 -def _place_labels(desired_etc, minimum, maximum, gap=0):
596 # Want a list of lists/tuples for desired_etc 597 desired_etc.sort() 598 placed = _spring_layout([row[0] for row in desired_etc], 599 minimum, maximum, gap) 600 for old, y2 in zip(desired_etc, placed): 601 # (y1, a, b, c, ..., z) --> (y1, y2, a, b, c, ..., z) 602 yield (old[0], y2) + tuple(old[1:])
603 604
605 -class AnnotatedChromosomeSegment(ChromosomeSegment):
606 - def __init__(self, bp_length, features, 607 default_feature_color=colors.blue, 608 name_qualifiers=('gene', 'label', 'name', 'locus_tag', 'product')):
609 """Like the ChromosomeSegment, but accepts a list of features. 610 611 The features can either be SeqFeature objects, or tuples of values: 612 start (int), end (int), strand (+1, -1, O or None), label (string), 613 ReportLab color (string or object), and optional ReportLab fill color. 614 615 Note we require 0 <= start <= end <= bp_length, and within the vertical 616 space allocated to this segmenet lines will be places according to the 617 start/end coordinates (starting from the top). 618 619 Positive stand features are drawn on the right, negative on the left, 620 otherwise all the way across. 621 622 We recommend using consisent units for all the segment's scale values 623 (e.g. their length in base pairs). 624 625 When providing features as SeqFeature objects, the default color 626 is used, unless the feature's qualifiers include an Artemis colour 627 string (functionality also in GenomeDiagram). The caption also follows 628 the GenomeDiagram approach and takes the first qualifier from the list 629 or tuple specified in name_qualifiers. 630 631 Note additional attribute label_sep_percent controls the percentage of 632 area that the chromosome segment takes up, by default half of the 633 chr_percent attribute (half of 25%, thus 12.5%) 634 635 """ 636 ChromosomeSegment.__init__(self) 637 self.bp_length = bp_length 638 self.features = features 639 self.default_feature_color = default_feature_color 640 self.name_qualifiers = name_qualifiers 641 self.label_sep_percent = self.chr_percent * 0.5
642
643 - def _overdraw_subcomponents(self, cur_drawing):
644 """Draw any annotated features on the chromosome segment. 645 646 Assumes _draw_segment already called to fill out the basic shape, 647 and assmes that uses the same boundaries. 648 """ 649 # set the coordinates of the segment -- it'll take up the MIDDLE part 650 # of the space we have. 651 segment_y = self.end_y_position 652 segment_width = (self.end_x_position - self.start_x_position) \ 653 * self.chr_percent 654 label_sep = (self.end_x_position - self.start_x_position) \ 655 * self.label_sep_percent 656 segment_height = self.start_y_position - self.end_y_position 657 segment_x = self.start_x_position \ 658 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 659 660 left_labels = [] 661 right_labels = [] 662 for f in self.features: 663 try: 664 # Assume SeqFeature objects 665 start = f.location.start 666 end = f.location.end 667 strand = f.strand 668 try: 669 # Handles Artemis colour integers, HTML colors, etc 670 color = _color_trans.translate(f.qualifiers['color'][0]) 671 except Exception: # TODO: ValueError? 672 color = self.default_feature_color 673 fill_color = color 674 name = "" 675 for qualifier in self.name_qualifiers: 676 if qualifier in f.qualifiers: 677 name = f.qualifiers[qualifier][0] 678 break 679 except AttributeError: 680 # Assume tuple of ints, string, and color 681 start, end, strand, name, color = f[:5] 682 color = _color_trans.translate(color) 683 if len(f) > 5: 684 fill_color = _color_trans.translate(f[5]) 685 else: 686 fill_color = color 687 assert 0 <= start <= end <= self.bp_length 688 if strand == +1: 689 # Right side only 690 x = segment_x + segment_width * 0.6 691 w = segment_width * 0.4 692 elif strand == -1: 693 # Left side only 694 x = segment_x 695 w = segment_width * 0.4 696 else: 697 # Both or neither - full width 698 x = segment_x 699 w = segment_width 700 local_scale = segment_height / self.bp_length 701 fill_rectangle = Rect(x, 702 segment_y + segment_height - local_scale * start, 703 w, 704 local_scale * (start - end)) 705 fill_rectangle.fillColor = fill_color 706 fill_rectangle.strokeColor = color 707 cur_drawing.add(fill_rectangle) 708 if name: 709 if fill_color == color: 710 back_color = None 711 else: 712 back_color = fill_color 713 value = (segment_y + segment_height - local_scale * start, 714 color, back_color, name) 715 if strand == -1: 716 self._left_labels.append(value) 717 else: 718 self._right_labels.append(value)
719 720
721 -class TelomereSegment(ChromosomeSegment):
722 """A segment that is located at the end of a linear chromosome. 723 724 This is just like a regular segment, but it draws the end of a chromosome 725 which is represented by a half circle. This just overrides the 726 _draw_segment class of ChromosomeSegment to provide that specialized 727 drawing. 728 """
729 - def __init__(self, inverted=0):
730 """Initialize a segment at the end of a chromosome. 731 732 See ChromosomeSegment for all of the attributes that can be 733 customized in a TelomereSegments. 734 735 Arguments: 736 737 o inverted -- Whether or not the telomere should be inverted 738 (ie. drawn on the bottom of a chromosome) 739 """ 740 ChromosomeSegment.__init__(self) 741 742 self._inverted = inverted
743
744 - def _draw_segment(self, cur_drawing):
745 """Draw a half circle representing the end of a linear chromosome. 746 """ 747 # set the coordinates of the segment -- it'll take up the MIDDLE part 748 # of the space we have. 749 width = (self.end_x_position - self.start_x_position) \ 750 * self.chr_percent 751 height = self.start_y_position - self.end_y_position 752 center_x = 0.5 * (self.end_x_position + self.start_x_position) 753 start_x = center_x - 0.5 * width 754 if self._inverted: 755 center_y = self.start_y_position 756 start_angle = 180 757 end_angle = 360 758 else: 759 center_y = self.end_y_position 760 start_angle = 0 761 end_angle = 180 762 763 cap_wedge = Wedge(center_x, center_y, width / 2, 764 start_angle, end_angle, height) 765 cap_wedge.strokeColor = None 766 cap_wedge.fillColor = self.fill_color 767 cur_drawing.add(cap_wedge) 768 769 # Now draw an arc for the curved edge of the wedge, 770 # omitting the flat end. 771 cap_arc = ArcPath() 772 cap_arc.addArc(center_x, center_y, width / 2, 773 start_angle, end_angle, height) 774 cur_drawing.add(cap_arc)
775 776
777 -class SpacerSegment(ChromosomeSegment):
778 """A segment that is located at the end of a linear chromosome. 779 780 Doesn't draw anything, just empty space which can be helpful 781 for layout purposes (e.g. making room for feature labels). 782 """ 783
784 - def draw(self, cur_diagram):
785 pass
786