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  # standard library 
 34  import os 
 35   
 36  # reportlab 
 37  from reportlab.pdfgen import canvas 
 38  from reportlab.lib.pagesizes import letter 
 39  from reportlab.lib.units import inch 
 40  from reportlab.lib import colors 
 41  from reportlab.pdfbase.pdfmetrics import stringWidth 
 42   
 43  from reportlab.graphics.shapes import Drawing, String, Line, Rect, Wedge, ArcPath 
 44  from reportlab.graphics import renderPDF, renderPS 
 45  from reportlab.graphics.widgetbase import Widget 
 46   
 47  from Bio.Graphics import _write 
 48  from Bio.Graphics.GenomeDiagram._Colors import ColorTranslator as _ColorTranslator 
 49   
 50  _color_trans = _ColorTranslator() 
 51   
 52   
53 -class _ChromosomeComponent(Widget):
54 """Base class specifying the interface for a component of the system. 55 56 This class should not be instantiated directly, but should be used 57 from derived classes. 58 """
59 - def __init__(self):
60 """Initialize a chromosome component. 61 62 Attributes: 63 64 o _sub_components -- Any components which are contained under 65 this parent component. This attribute should be accessed through 66 the add() and remove() functions. 67 """ 68 self._sub_components = []
69
70 - def add(self, component):
71 """Add a sub_component to the list of components under this item. 72 """ 73 assert isinstance(component, _ChromosomeComponent), \ 74 "Expected a _ChromosomeComponent object, got %s" % component 75 76 self._sub_components.append(component)
77
78 - def remove(self, component):
79 """Remove the specified component from the subcomponents. 80 81 Raises a ValueError if the component is not registered as a 82 sub_component. 83 """ 84 try: 85 self._sub_components.remove(component) 86 except ValueError: 87 raise ValueError("Component %s not found in sub_components." % 88 component)
89
90 - def draw(self):
91 """Draw the specified component. 92 """ 93 raise AssertionError("Subclasses must implement.")
94 95
96 -class Organism(_ChromosomeComponent):
97 """Top level class for drawing chromosomes. 98 99 This class holds information about an organism and all of it's 100 chromosomes, and provides the top level object which could be used 101 for drawing a chromosome representation of an organism. 102 103 Chromosomes should be added and removed from the Organism via the 104 add and remove functions. 105 """
106 - def __init__(self, output_format = 'pdf'):
107 _ChromosomeComponent.__init__(self) 108 109 # customizable attributes 110 self.page_size = letter 111 self.title_size = 20 112 113 #Do we need this given we don't draw a legend? 114 #If so, should be a public API... 115 self._legend_height = 0 # 2 * inch 116 117 self.output_format = output_format
118
119 - def draw(self, output_file, title):
120 """Draw out the information for the Organism. 121 122 Arguments: 123 124 o output_file -- The name of a file specifying where the 125 document should be saved, or a handle to be written to. 126 The output format is set when creating the Organism object. 127 Alternatively, output_file=None will return the drawing using 128 the low-level ReportLab objects (for further processing, such 129 as adding additional graphics, before writing). 130 131 o title -- The output title of the produced document. 132 """ 133 width, height = self.page_size 134 cur_drawing = Drawing(width, height) 135 136 self._draw_title(cur_drawing, title, width, height) 137 138 cur_x_pos = inch * .5 139 if len(self._sub_components) > 0: 140 x_pos_change = (width - inch) / len(self._sub_components) 141 # no sub_components 142 else: 143 pass 144 145 for sub_component in self._sub_components: 146 # set the drawing location of the chromosome 147 sub_component.start_x_position = cur_x_pos + 0.05 * x_pos_change 148 sub_component.end_x_position = cur_x_pos + 0.95 * x_pos_change 149 sub_component.start_y_position = height - 1.5 * inch 150 sub_component.end_y_position = self._legend_height + 1 * inch 151 152 # do the drawing 153 sub_component.draw(cur_drawing) 154 155 # update the locations for the next chromosome 156 cur_x_pos += x_pos_change 157 158 self._draw_legend(cur_drawing, self._legend_height + 0.5 * inch, width) 159 160 if output_file is None: 161 #Let the user take care of writing to the file... 162 return cur_drawing 163 164 return _write(cur_drawing, output_file, self.output_format)
165
166 - def _draw_title(self, cur_drawing, title, width, height):
167 """Write out the title of the organism figure. 168 """ 169 title_string = String(width / 2, height - inch, title) 170 title_string.fontName = 'Helvetica-Bold' 171 title_string.fontSize = self.title_size 172 title_string.textAnchor = "middle" 173 174 cur_drawing.add(title_string)
175
176 - def _draw_legend(self, cur_drawing, start_y, width):
177 """Draw a legend for the figure. 178 179 Subclasses should implement this (see also self._legend_height) to 180 provide specialized legends. 181 """ 182 pass
183 184
185 -class Chromosome(_ChromosomeComponent):
186 """Class for drawing a chromosome of an organism. 187 188 This organizes the drawing of a single organisms chromosome. This 189 class can be instantiated directly, but the draw method makes the 190 most sense to be called in the context of an organism. 191 """
192 - def __init__(self, chromosome_name):
193 """Initialize a Chromosome for drawing. 194 195 Arguments: 196 197 o chromosome_name - The label for the chromosome. 198 199 Attributes: 200 201 o start_x_position, end_x_position - The x positions on the page 202 where the chromosome should be drawn. This allows multiple 203 chromosomes to be drawn on a single page. 204 205 o start_y_position, end_y_position - The y positions on the page 206 where the chromosome should be contained. 207 208 Configuration Attributes: 209 210 o title_size - The size of the chromosome title. 211 212 o scale_num - A number of scale the drawing by. This is useful if 213 you want to draw multiple chromosomes of different sizes at the 214 same scale. If this is not set, then the chromosome drawing will 215 be scaled by the number of segements in the chromosome (so each 216 chromosome will be the exact same final size). 217 """ 218 _ChromosomeComponent.__init__(self) 219 220 self._name = chromosome_name 221 222 self.start_x_position = -1 223 self.end_x_position = -1 224 self.start_y_position = -1 225 self.end_y_position = -1 226 227 self.title_size = 20 228 self.scale_num = None 229 230 self.label_size = 6 231 self.chr_percent = 0.25 232 self.label_sep_percent = self.chr_percent * 0.5 233 self._color_labels = False
234
235 - def subcomponent_size(self):
236 """Return the scaled size of all subcomponents of this component. 237 """ 238 total_sub = 0 239 for sub_component in self._sub_components: 240 total_sub += sub_component.scale 241 242 return total_sub
243
244 - def draw(self, cur_drawing):
245 """Draw a chromosome on the specified template. 246 247 Ideally, the x_position and y_*_position attributes should be 248 set prior to drawing -- otherwise we're going to have some problems. 249 """ 250 for position in (self.start_x_position, self.end_x_position, 251 self.start_y_position, self.end_y_position): 252 assert position != -1, "Need to set drawing coordinates." 253 254 # first draw all of the sub-sections of the chromosome -- this 255 # will actually be the picture of the chromosome 256 cur_y_pos = self.start_y_position 257 if self.scale_num: 258 y_pos_change = ((self.start_y_position * .95 - self.end_y_position) 259 / self.scale_num) 260 elif len(self._sub_components) > 0: 261 y_pos_change = ((self.start_y_position * .95 - self.end_y_position) 262 / self.subcomponent_size()) 263 # no sub_components to draw 264 else: 265 pass 266 267 left_labels = [] 268 right_labels = [] 269 for sub_component in self._sub_components: 270 this_y_pos_change = sub_component.scale * y_pos_change 271 272 # set the location of the component to draw 273 sub_component.start_x_position = self.start_x_position 274 sub_component.end_x_position = self.end_x_position 275 sub_component.start_y_position = cur_y_pos 276 sub_component.end_y_position = cur_y_pos - this_y_pos_change 277 278 # draw the sub component 279 sub_component._left_labels = [] 280 sub_component._right_labels = [] 281 sub_component.draw(cur_drawing) 282 left_labels += sub_component._left_labels 283 right_labels += sub_component._right_labels 284 285 # update the position for the next component 286 cur_y_pos -= this_y_pos_change 287 288 self._draw_labels(cur_drawing, left_labels, right_labels) 289 self._draw_label(cur_drawing, self._name)
290
291 - def _draw_label(self, cur_drawing, label_name):
292 """Draw a label for the chromosome. 293 """ 294 x_position = 0.5 * (self.start_x_position + self.end_x_position) 295 y_position = self.end_y_position 296 297 label_string = String(x_position, y_position, label_name) 298 label_string.fontName = 'Times-BoldItalic' 299 label_string.fontSize = self.title_size 300 label_string.textAnchor = 'middle' 301 302 cur_drawing.add(label_string)
303
304 - def _draw_labels(self, cur_drawing, left_labels, right_labels):
305 """Layout and draw sub-feature labels for the chromosome. 306 307 Tries to place each label at the same vertical position as the 308 feature it applies to, but will adjust the positions to avoid or 309 at least reduce label overlap. 310 311 Draws the label text and a coloured line linking it to the 312 location (i.e. feature) it applies to. 313 """ 314 if not self._sub_components: 315 return 316 color_label = self._color_labels 317 318 segment_width = (self.end_x_position - self.start_x_position) \ 319 * self.chr_percent 320 label_sep = (self.end_x_position - self.start_x_position) \ 321 * self.label_sep_percent 322 segment_x = self.start_x_position \ 323 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 324 325 y_limits = [] 326 for sub_component in self._sub_components: 327 y_limits.extend((sub_component.start_y_position, sub_component.end_y_position)) 328 y_min = min(y_limits) 329 y_max = max(y_limits) 330 del y_limits 331 #Now do some label placement magic... 332 #from reportlab.pdfbase import pdfmetrics 333 #font = pdfmetrics.getFont('Helvetica') 334 #h = (font.face.ascent + font.face.descent) * 0.90 335 h = self.label_size 336 for x1, x2, labels, anchor in [ 337 (segment_x, 338 segment_x - label_sep, 339 _place_labels(left_labels, y_min, y_max, h), 340 "end"), 341 (segment_x + segment_width, 342 segment_x + segment_width + label_sep, 343 _place_labels(right_labels, y_min, y_max, h), 344 "start"), 345 ]: 346 for (y1, y2, color, back_color, name) in labels: 347 cur_drawing.add(Line(x1, y1, x2, y2, 348 strokeColor = color, 349 strokeWidth = 0.25)) 350 label_string = String(x2, y2, name, 351 textAnchor=anchor) 352 label_string.fontName = 'Helvetica' 353 label_string.fontSize = h 354 if color_label: 355 label_string.fillColor = color 356 if back_color: 357 w = stringWidth(name, label_string.fontName, 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, span/3.0, 2*span/3.0, 0.25*span, 0.75*span]: 545 midpoint = minimum + split 546 low = [x for x in desired if x <= midpoint - 0.5*gap] 547 high = [x for x in desired if x > midpoint + 0.5*gap] 548 if len(low)+len(high) < count: 549 #Bad split point, points right on boundary 550 continue 551 elif not low and len(high)*gap <= (span-split) + 0.5*gap: 552 #Give a little of the unused low space to the high points 553 return _spring_layout(high, midpoint + 0.5*gap, maximum, gap) 554 elif not high and len(low)*gap <= split + 0.5*gap: 555 #Give a little of the unused highspace to the low points 556 return _spring_layout(low, minimum, midpoint - 0.5*gap, gap) 557 elif len(low)*gap <= split - 0.5*gap \ 558 and len(high)*gap <= (span-split) - 0.5*gap: 559 return _spring_layout(low, minimum, midpoint - 0.5*gap, gap) + \ 560 _spring_layout(high, midpoint+ 0.5*gap, maximum, gap) 561 562 #This can be count-productive now we can split out into the telomere or 563 #spacer-segment's vertical space... 564 #Try not to spread out as far as the min/max unless needed 565 low = min(desired) 566 high = max(desired) 567 if (high-low) / (count-1) >= gap: 568 #Good, we don't need the full range, and can position the 569 #min and max exactly as well :) 570 equal_step = (high-low) / (count-1) 571 return [low+i*equal_step for i in range(count)] 572 573 low = 0.5 * (minimum + min(desired)) 574 high = 0.5 * (max(desired) + maximum) 575 if (high-low) / (count-1) >= gap: 576 #Good, we don't need the full range 577 equal_step = (high-low) / (count-1) 578 return [low+i*equal_step for i in range(count)] 579 580 #Crudest solution 581 return [minimum+i*equal_step for i in range(count)]
582 583 #assert False, _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1) 584 #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] 585 #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] 586 587
588 -def _place_labels(desired_etc, minimum, maximum, gap=0):
589 #Want a list of lists/tuples for desired_etc 590 desired_etc.sort() 591 placed = _spring_layout([row[0] for row in desired_etc], 592 minimum, maximum, gap) 593 for old, y2 in zip(desired_etc, placed): 594 #(y1, a, b, c, ..., z) --> (y1, y2, a, b, c, ..., z) 595 yield (old[0], y2) + tuple(old[1:])
596 597
598 -class AnnotatedChromosomeSegment(ChromosomeSegment):
599 - def __init__(self, bp_length, features, 600 default_feature_color=colors.blue, 601 name_qualifiers = ['gene', 'label', 'name', 'locus_tag', 'product']):
602 """Like the ChromosomeSegment, but accepts a list of features. 603 604 The features can either be SeqFeature objects, or tuples of values: 605 start (int), end (int), strand (+1, -1, O or None), label (string), 606 ReportLab color (string or object), and optional ReportLab fill color. 607 608 Note we require 0 <= start <= end <= bp_length, and within the vertical 609 space allocated to this segmenet lines will be places according to the 610 start/end coordinates (starting from the top). 611 612 Positive stand features are drawn on the right, negative on the left, 613 otherwise all the way across. 614 615 We recommend using consisent units for all the segment's scale values 616 (e.g. their length in base pairs). 617 618 When providing features as SeqFeature objects, the default color 619 is used, unless the feature's qualifiers include an Artemis colour 620 string (functionality also in GenomeDiagram). The caption also follows 621 the GenomeDiagram approach and takes the first qualifier from the list 622 specified in name_qualifiers. 623 624 Note additional attribute label_sep_percent controls the percentage of 625 area that the chromosome segment takes up, by default half of the 626 chr_percent attribute (half of 25%, thus 12.5%) 627 628 """ 629 ChromosomeSegment.__init__(self) 630 self.bp_length = bp_length 631 self.features = features 632 self.default_feature_color = default_feature_color 633 self.name_qualifiers = name_qualifiers 634 self.label_sep_percent = self.chr_percent * 0.5
635
636 - def _overdraw_subcomponents(self, cur_drawing):
637 """Draw any annotated features on the chromosome segment. 638 639 Assumes _draw_segment already called to fill out the basic shape, 640 and assmes that uses the same boundaries. 641 """ 642 # set the coordinates of the segment -- it'll take up the MIDDLE part 643 # of the space we have. 644 segment_y = self.end_y_position 645 segment_width = (self.end_x_position - self.start_x_position) \ 646 * self.chr_percent 647 label_sep = (self.end_x_position - self.start_x_position) \ 648 * self.label_sep_percent 649 segment_height = self.start_y_position - self.end_y_position 650 segment_x = self.start_x_position \ 651 + 0.5 * (self.end_x_position - self.start_x_position - segment_width) 652 653 left_labels = [] 654 right_labels = [] 655 for f in self.features: 656 try: 657 #Assume SeqFeature objects 658 start = f.location.start 659 end = f.location.end 660 strand = f.strand 661 try: 662 #Handles Artemis colour integers, HTML colors, etc 663 color = _color_trans.translate(f.qualifiers['color'][0]) 664 except: 665 color = self.default_feature_color 666 fill_color = color 667 name = "" 668 for qualifier in self.name_qualifiers: 669 if qualifier in f.qualifiers: 670 name = f.qualifiers[qualifier][0] 671 break 672 except AttributeError: 673 #Assume tuple of ints, string, and color 674 start, end, strand, name, color = f[:5] 675 color = _color_trans.translate(color) 676 if len(f) > 5: 677 fill_color = _color_trans.translate(f[5]) 678 else: 679 fill_color = color 680 assert 0 <= start <= end <= self.bp_length 681 if strand == +1 : 682 #Right side only 683 x = segment_x + segment_width * 0.6 684 w = segment_width * 0.4 685 elif strand == -1: 686 #Left side only 687 x = segment_x 688 w = segment_width * 0.4 689 else: 690 #Both or neither - full width 691 x = segment_x 692 w = segment_width 693 local_scale = segment_height / self.bp_length 694 fill_rectangle = Rect(x, segment_y + segment_height - local_scale*start, 695 w, local_scale*(start-end)) 696 fill_rectangle.fillColor = fill_color 697 fill_rectangle.strokeColor = color 698 cur_drawing.add(fill_rectangle) 699 if name: 700 if fill_color == color: 701 back_color = None 702 else: 703 back_color = fill_color 704 value = (segment_y + segment_height - local_scale*start, color, back_color, name) 705 if strand == -1: 706 self._left_labels.append(value) 707 else: 708 self._right_labels.append(value)
709 710
711 -class TelomereSegment(ChromosomeSegment):
712 """A segment that is located at the end of a linear chromosome. 713 714 This is just like a regular segment, but it draws the end of a chromosome 715 which is represented by a half circle. This just overrides the 716 _draw_segment class of ChromosomeSegment to provide that specialized 717 drawing. 718 """
719 - def __init__(self, inverted = 0):
720 """Initialize a segment at the end of a chromosome. 721 722 See ChromosomeSegment for all of the attributes that can be 723 customized in a TelomereSegments. 724 725 Arguments: 726 727 o inverted -- Whether or not the telomere should be inverted 728 (ie. drawn on the bottom of a chromosome) 729 """ 730 ChromosomeSegment.__init__(self) 731 732 self._inverted = inverted
733
734 - def _draw_segment(self, cur_drawing):
735 """Draw a half circle representing the end of a linear chromosome. 736 """ 737 # set the coordinates of the segment -- it'll take up the MIDDLE part 738 # of the space we have. 739 width = (self.end_x_position - self.start_x_position) \ 740 * self.chr_percent 741 height = self.start_y_position - self.end_y_position 742 center_x = 0.5 * (self.end_x_position + self.start_x_position) 743 start_x = center_x - 0.5 * width 744 if self._inverted: 745 center_y = self.start_y_position 746 start_angle = 180 747 end_angle = 360 748 else: 749 center_y = self.end_y_position 750 start_angle = 0 751 end_angle = 180 752 753 cap_wedge = Wedge(center_x, center_y, width / 2, 754 start_angle, end_angle, height) 755 cap_wedge.strokeColor = None 756 cap_wedge.fillColor = self.fill_color 757 cur_drawing.add(cap_wedge) 758 759 #Now draw an arc for the curved edge of the wedge, 760 #omitting the flat end. 761 cap_arc = ArcPath() 762 cap_arc.addArc(center_x, center_y, width / 2, 763 start_angle, end_angle, height) 764 cur_drawing.add(cap_arc)
765 766
767 -class SpacerSegment(ChromosomeSegment):
768 """A segment that is located at the end of a linear chromosome. 769 770 Doesn't draw anything, just empty space which can be helpful 771 for layout purposes (e.g. making room for feature labels). 772 """ 773
774 - def draw(self, cur_diagram):
775 pass
776