Back to index

python-biopython  1.60
BasicChromosome.py
Go to the documentation of this file.
00001 """Draw representations of organism chromosomes with added information.
00002 
00003 These classes are meant to model the drawing of pictures of chromosomes.
00004 This can be useful for lots of things, including displaying markers on
00005 a chromosome (ie. for genetic mapping) and showing syteny between two
00006 chromosomes.
00007 
00008 The structure of these classes is intended to be a Composite, so that
00009 it will be easy to plug in and switch different parts without
00010 breaking the general drawing capabilities of the system. The
00011 relationship between classes is that everything derives from
00012 _ChromosomeComponent, which specifies the overall interface. The parts
00013 then are related so that an Organism contains Chromosomes, and these
00014 Chromosomes contain ChromosomeSegments. This representation differents
00015 from the canonical composite structure in that we don't really have
00016 'leaf' nodes here -- all components can potentially hold sub-components.
00017 
00018 Most of the time the ChromosomeSegment class is what you'll want to
00019 customize for specific drawing tasks.
00020 
00021 For providing drawing capabilities, these classes use reportlab:
00022 
00023 http://www.reportlab.com
00024 
00025 This provides nice output in PDF, SVG and postscript.  If you have
00026 reportlab's renderPM module installed you can also use PNG etc.
00027 """
00028 # standard library
00029 import os
00030 
00031 # reportlab
00032 from reportlab.pdfgen import canvas
00033 from reportlab.lib.pagesizes import letter
00034 from reportlab.lib.units import inch
00035 from reportlab.lib import colors
00036 
00037 from reportlab.graphics.shapes import Drawing, String, Line, Rect, Wedge, ArcPath
00038 from reportlab.graphics import renderPDF, renderPS
00039 from reportlab.graphics.widgetbase import Widget
00040 
00041 from Bio.Graphics import _write
00042 from Bio.Graphics.GenomeDiagram._Colors import ColorTranslator as _ColorTranslator
00043 
00044 _color_trans = _ColorTranslator()
00045 
00046 class _ChromosomeComponent(Widget):
00047     """Base class specifying the interface for a component of the system.
00048 
00049     This class should not be instantiated directly, but should be used
00050     from derived classes.
00051     """
00052     def __init__(self):
00053         """Initialize a chromosome component.
00054 
00055         Attributes:
00056 
00057         o _sub_components -- Any components which are contained under
00058         this parent component. This attribute should be accessed through
00059         the add() and remove() functions.
00060         """
00061         self._sub_components = []
00062 
00063     def add(self, component):
00064         """Add a sub_component to the list of components under this item.
00065         """
00066         assert isinstance(component, _ChromosomeComponent), \
00067                "Expected a _ChromosomeComponent object, got %s" % component
00068         
00069         self._sub_components.append(component)
00070 
00071     def remove(self, component):
00072         """Remove the specified component from the subcomponents.
00073 
00074         Raises a ValueError if the component is not registered as a
00075         sub_component.
00076         """
00077         try:
00078             self._sub_components.remove(component)
00079         except ValueError:
00080             raise ValueError("Component %s not found in sub_components." %
00081                              component)
00082 
00083     def draw(self):
00084         """Draw the specified component.
00085         """
00086         raise AssertionError("Subclasses must implement.")
00087     
00088 class Organism(_ChromosomeComponent):
00089     """Top level class for drawing chromosomes.
00090 
00091     This class holds information about an organism and all of it's
00092     chromosomes, and provides the top level object which could be used
00093     for drawing a chromosome representation of an organism.
00094 
00095     Chromosomes should be added and removed from the Organism via the
00096     add and remove functions.
00097     """
00098     def __init__(self, output_format = 'pdf'):
00099         _ChromosomeComponent.__init__(self)
00100 
00101         # customizable attributes
00102         self.page_size = letter
00103         self.title_size = 20
00104 
00105         #Do we need this given we don't draw a legend?
00106         #If so, should be a public API...
00107         self._legend_height = 0 # 2 * inch
00108 
00109         self.output_format = output_format
00110 
00111     def draw(self, output_file, title):
00112         """Draw out the information for the Organism.
00113 
00114         Arguments:
00115 
00116         o output_file -- The name of a file specifying where the
00117         document should be saved, or a handle to be written to.
00118         The output format is set when creating the Organism object.
00119 
00120         o title -- The output title of the produced document.
00121         """
00122         width, height = self.page_size
00123         cur_drawing = Drawing(width, height)
00124 
00125         self._draw_title(cur_drawing, title, width, height)
00126 
00127         cur_x_pos = inch * .5
00128         if len(self._sub_components) > 0:
00129             x_pos_change = (width - inch) / len(self._sub_components)
00130         # no sub_components
00131         else:
00132             pass
00133 
00134         for sub_component in self._sub_components:
00135             # set the drawing location of the chromosome
00136             sub_component.start_x_position = cur_x_pos + 0.05 * x_pos_change
00137             sub_component.end_x_position = cur_x_pos + 0.95 * x_pos_change
00138             sub_component.start_y_position = height - 1.5 * inch
00139             sub_component.end_y_position = self._legend_height + 1 * inch
00140 
00141             # do the drawing
00142             sub_component.draw(cur_drawing)
00143 
00144             # update the locations for the next chromosome
00145             cur_x_pos += x_pos_change
00146 
00147         self._draw_legend(cur_drawing, self._legend_height + 0.5 * inch, width)
00148 
00149         return _write(cur_drawing, output_file, self.output_format)
00150 
00151     def _draw_title(self, cur_drawing, title, width, height):
00152         """Write out the title of the organism figure.
00153         """
00154         title_string = String(width / 2, height - inch, title)
00155         title_string.fontName = 'Helvetica-Bold'
00156         title_string.fontSize = self.title_size
00157         title_string.textAnchor = "middle"
00158 
00159         cur_drawing.add(title_string)
00160     
00161     def _draw_legend(self, cur_drawing, start_y, width):
00162         """Draw a legend for the figure.
00163 
00164         Subclasses should implement this (see also self._legend_height) to
00165         provide specialized legends.
00166         """
00167         pass
00168     
00169 class Chromosome(_ChromosomeComponent):
00170     """Class for drawing a chromosome of an organism.
00171 
00172     This organizes the drawing of a single organisms chromosome. This
00173     class can be instantiated directly, but the draw method makes the
00174     most sense to be called in the context of an organism.
00175     """
00176     def __init__(self, chromosome_name):
00177         """Initialize a Chromosome for drawing.
00178 
00179         Arguments:
00180 
00181         o chromosome_name - The label for the chromosome.
00182 
00183         Attributes:
00184 
00185         o start_x_position, end_x_position - The x positions on the page
00186         where the chromosome should be drawn. This allows multiple
00187         chromosomes to be drawn on a single page.
00188 
00189         o start_y_position, end_y_position - The y positions on the page
00190         where the chromosome should be contained.
00191 
00192         Configuration Attributes:
00193 
00194         o title_size - The size of the chromosome title.
00195 
00196         o scale_num - A number of scale the drawing by. This is useful if
00197         you want to draw multiple chromosomes of different sizes at the
00198         same scale. If this is not set, then the chromosome drawing will
00199         be scaled by the number of segements in the chromosome (so each
00200         chromosome will be the exact same final size).
00201         """
00202         _ChromosomeComponent.__init__(self)
00203 
00204         self._name = chromosome_name
00205 
00206         self.start_x_position = -1
00207         self.end_x_position = -1
00208         self.start_y_position = -1
00209         self.end_y_position = -1
00210 
00211         self.title_size = 20
00212         self.scale_num = None
00213 
00214         self.label_size = 6
00215         self.chr_percent = 0.25
00216         self.label_sep_percent = self.chr_percent * 0.5
00217         self._color_labels = False
00218 
00219     def subcomponent_size(self):
00220         """Return the scaled size of all subcomponents of this component.
00221         """
00222         total_sub = 0
00223         for sub_component in self._sub_components:
00224             total_sub += sub_component.scale
00225 
00226         return total_sub
00227 
00228     def draw(self, cur_drawing):
00229         """Draw a chromosome on the specified template.
00230 
00231         Ideally, the x_position and y_*_position attributes should be
00232         set prior to drawing -- otherwise we're going to have some problems.
00233         """
00234         for position in (self.start_x_position, self.end_x_position,
00235                          self.start_y_position, self.end_y_position):
00236             assert position != -1, "Need to set drawing coordinates."
00237 
00238         # first draw all of the sub-sections of the chromosome -- this
00239         # will actually be the picture of the chromosome
00240         cur_y_pos = self.start_y_position
00241         if self.scale_num:
00242             y_pos_change = ((self.start_y_position * .95 - self.end_y_position)
00243                             / self.scale_num)
00244         elif len(self._sub_components) > 0:
00245             y_pos_change = ((self.start_y_position * .95 - self.end_y_position)
00246                             / self.subcomponent_size())
00247         # no sub_components to draw
00248         else:
00249             pass
00250         
00251         left_labels = []
00252         right_labels = []
00253         for sub_component in self._sub_components:
00254             this_y_pos_change = sub_component.scale * y_pos_change
00255             
00256             # set the location of the component to draw
00257             sub_component.start_x_position = self.start_x_position
00258             sub_component.end_x_position = self.end_x_position
00259             sub_component.start_y_position = cur_y_pos
00260             sub_component.end_y_position = cur_y_pos - this_y_pos_change
00261 
00262             # draw the sub component
00263             sub_component._left_labels = []
00264             sub_component._right_labels = []
00265             sub_component.draw(cur_drawing)
00266             left_labels += sub_component._left_labels
00267             right_labels += sub_component._right_labels
00268 
00269             # update the position for the next component
00270             cur_y_pos -= this_y_pos_change
00271 
00272         self._draw_labels(cur_drawing, left_labels, right_labels)
00273         self._draw_label(cur_drawing, self._name)
00274 
00275     def _draw_label(self, cur_drawing, label_name):
00276         """Draw a label for the chromosome.
00277         """
00278         x_position = 0.5 * (self.start_x_position + self.end_x_position)
00279         y_position = self.end_y_position
00280 
00281         label_string = String(x_position, y_position, label_name)
00282         label_string.fontName = 'Times-BoldItalic'
00283         label_string.fontSize = self.title_size
00284         label_string.textAnchor = 'middle'
00285 
00286         cur_drawing.add(label_string)
00287 
00288     def _draw_labels(self, cur_drawing, left_labels, right_labels):
00289         """Layout and draw sub-feature labels for the chromosome.
00290 
00291         Tries to place each label at the same vertical position as the
00292         feature it applies to, but will adjust the positions to avoid or
00293         at least reduce label overlap.
00294 
00295         Draws the label text and a coloured line linking it to the
00296         location (i.e. feature) it applies to.
00297         """
00298         if not self._sub_components:
00299             return
00300         color_label = self._color_labels
00301 
00302         segment_width = (self.end_x_position - self.start_x_position) \
00303                         * self.chr_percent
00304         label_sep = (self.end_x_position - self.start_x_position) \
00305                         * self.label_sep_percent
00306         segment_x = self.start_x_position \
00307                   + 0.5 * (self.end_x_position - self.start_x_position - segment_width)
00308 
00309         y_limits = []
00310         for sub_component in self._sub_components:
00311             y_limits.extend((sub_component.start_y_position, sub_component.end_y_position))
00312         y_min = min(y_limits)
00313         y_max = max(y_limits)
00314         del y_limits
00315         #Now do some label placement magic...
00316         #from reportlab.pdfbase import pdfmetrics
00317         #font = pdfmetrics.getFont('Helvetica')
00318         #h = (font.face.ascent + font.face.descent) * 0.90
00319         h = self.label_size
00320         left_labels = _place_labels(left_labels, y_min, y_max, h)
00321         right_labels = _place_labels(right_labels, y_min, y_max, h)
00322         x1 = segment_x
00323         x2 = segment_x - label_sep
00324         for (y1, y2, color, name) in left_labels:
00325             cur_drawing.add(Line(x1, y1, x2, y2,
00326                                  strokeColor = color,
00327                                  strokeWidth = 0.25))
00328             label_string = String(x2, y2, name,
00329                                   textAnchor="end")
00330             label_string.fontName = 'Helvetica'
00331             label_string.fontSize = self.label_size
00332             if color_label:
00333                 label_string.fillColor = color
00334             cur_drawing.add(label_string)
00335         x1 = segment_x + segment_width
00336         x2 = segment_x + segment_width + label_sep
00337         for (y1, y2, color, name) in right_labels:
00338             cur_drawing.add(Line(x1, y1, x2, y2,
00339                                  strokeColor = color,
00340                                  strokeWidth = 0.25))
00341             label_string = String(x2, y2, name)
00342             label_string.fontName = 'Helvetica'
00343             label_string.fontSize = self.label_size
00344             if color_label:
00345                 label_string.fillColor = color
00346             cur_drawing.add(label_string)
00347 
00348 
00349 
00350 class ChromosomeSegment(_ChromosomeComponent):
00351     """Draw a segment of a chromosome.
00352 
00353     This class provides the important configurable functionality of drawing
00354     a Chromosome. Each segment has some customization available here, or can
00355     be subclassed to define additional functionality. Most of the interesting
00356     drawing stuff is likely to happen at the ChromosomeSegment level.
00357     """
00358     def __init__(self):
00359         """Initialize a ChromosomeSegment.
00360 
00361         Attributes:
00362         o start_x_position, end_x_position - Defines the x range we have
00363         to draw things in.
00364 
00365         o start_y_position, end_y_position - Defines the y range we have
00366         to draw things in.
00367 
00368         Configuration Attributes:
00369 
00370         o scale - A scaling value for the component. By default this is
00371         set at 1 (ie -- has the same scale as everything else). Higher
00372         values give more size to the component, smaller values give less.
00373 
00374         o fill_color - A color to fill in the segment with. Colors are
00375         available in reportlab.lib.colors
00376 
00377         o label - A label to place on the chromosome segment. This should
00378         be a text string specifying what is to be included in the label.
00379 
00380         o label_size - The size of the label.
00381 
00382         o chr_percent - The percentage of area that the chromosome
00383         segment takes up.
00384         """
00385         _ChromosomeComponent.__init__(self)
00386 
00387         self.start_x_position = -1
00388         self.end_x_position = -1
00389         self.start_y_position = -1
00390         self.end_y_position = -1
00391 
00392         # --- attributes for configuration
00393         self.scale = 1
00394         self.fill_color = None
00395         self.label = None
00396         self.label_size = 6
00397         self.chr_percent = .25
00398 
00399     def draw(self, cur_drawing):
00400         """Draw a chromosome segment.
00401 
00402         Before drawing, the range we are drawing in needs to be set.
00403         """
00404         for position in (self.start_x_position, self.end_x_position,
00405                          self.start_y_position, self.end_y_position):
00406             assert position != -1, "Need to set drawing coordinates."
00407 
00408         self._draw_subcomponents(cur_drawing) #Anything behind
00409         self._draw_segment(cur_drawing)
00410         self._overdraw_subcomponents(cur_drawing) #Anything on top
00411         self._draw_label(cur_drawing)
00412 
00413     def _draw_subcomponents(self, cur_drawing):
00414         """Draw any subcomponents of the chromosome segment.
00415 
00416         This should be overridden in derived classes if there are
00417         subcomponents to be drawn.
00418         """
00419         pass
00420 
00421     def _draw_segment(self, cur_drawing):
00422         """Draw the current chromosome segment.
00423         """
00424         # set the coordinates of the segment -- it'll take up the MIDDLE part
00425         # of the space we have.
00426         segment_y = self.end_y_position
00427         segment_width = (self.end_x_position - self.start_x_position) \
00428                         * self.chr_percent
00429         segment_height = self.start_y_position - self.end_y_position
00430         segment_x = self.start_x_position \
00431                   + 0.5 * (self.end_x_position - self.start_x_position - segment_width)
00432         
00433         # first draw the sides of the segment
00434         right_line = Line(segment_x, segment_y,
00435                           segment_x, segment_y + segment_height)
00436         left_line = Line(segment_x + segment_width, segment_y,
00437                          segment_x + segment_width, segment_y + segment_height)
00438         
00439         cur_drawing.add(right_line)
00440         cur_drawing.add(left_line)
00441         
00442         # now draw the box, if it is filled in
00443         if self.fill_color is not None:
00444             fill_rectangle = Rect(segment_x, segment_y,
00445                                   segment_width, segment_height)
00446             fill_rectangle.fillColor = self.fill_color
00447             fill_rectangle.strokeColor = None
00448 
00449             cur_drawing.add(fill_rectangle)
00450 
00451     def _overdraw_subcomponents(self, cur_drawing):
00452         """Draw any subcomponents of the chromosome segment over the main part.
00453 
00454         This should be overridden in derived classes if there are
00455         subcomponents to be drawn.
00456         """
00457         pass
00458 
00459     def _draw_label(self, cur_drawing):
00460         """Add a label to the chromosome segment.
00461 
00462         The label will be applied to the right of the segment.
00463 
00464         This may be overlapped by any sub-feature labels on other segments!
00465         """
00466         if self.label is not None:
00467 
00468             label_x = 0.5 * (self.start_x_position + self.end_x_position) + \
00469                       (self.chr_percent + 0.05) * (self.end_x_position -
00470                                                    self.start_x_position)
00471             label_y = ((self.start_y_position - self.end_y_position) / 2 +
00472                        self.end_y_position)
00473 
00474             label_string = String(label_x, label_y, self.label)
00475             label_string.fontName = 'Helvetica'
00476             label_string.fontSize = self.label_size
00477 
00478             cur_drawing.add(label_string)
00479 
00480 def _spring_layout(desired, minimum, maximum, gap=0):
00481     """Function to try and layout label co-ordinates (or other floats, PRIVATE).
00482     
00483     Originally written for the y-axis vertical positioning of labels on a
00484     chromosome diagram (where the minimum gap between y-axis co-ordinates is
00485     the label height), it could also potentially be used for x-axis placement,
00486     or indeed radial placement for circular chromosomes within GenomeDiagram.
00487     
00488     In essence this is an optimisation problem, balancing the desire to have
00489     each label as close as possible to its data point, but also to spread out
00490     the labels to avoid overlaps. This could be described with a cost function
00491     (modelling the label distance from the desired placement, and the inter-
00492     label separations as springs) and solved as a multi-variable minimization
00493     problem - perhaps with NumPy or SciPy.
00494     
00495     For now however, the implementation is a somewhat crude ad hoc algorithm.
00496     
00497     NOTE - This expects the input data to have been sorted!
00498     """
00499     count = len(desired)
00500     if count <= 1:
00501         return desired #Easy!
00502     if minimum >= maximum:
00503         raise ValueError("Bad min/max %f and %f" % (minimum, maximum))
00504     if min(desired) < minimum or max(desired) > maximum:
00505         raise ValueError("Data %f to %f out of bounds (%f to %f)" \
00506                          % (min(desired), max(desired), minimum, maximum))
00507     equal_step = float(maximum - minimum) / (count - 1)
00508 
00509     if equal_step < gap:
00510         import warnings
00511         warnings.warn("Too many labels to avoid overlap")
00512         #Crudest solution
00513         return [minimum+i*equal_step for i in range(count)]
00514     
00515     good = True
00516     if gap:
00517         prev = desired[0]
00518         for next in desired[1:]:
00519             if prev - next < gap:
00520                 good = False
00521                 break
00522     if good:
00523         return desired
00524 
00525     span = maximum - minimum
00526     for split in [0.5*span, span/3.0, 2*span/3.0, 0.25*span, 0.75*span]:
00527         midpoint = minimum + split
00528         low = [x for x in desired if x <= midpoint - 0.5*gap]
00529         high = [x for x in desired if x > midpoint + 0.5*gap]
00530         if len(low)+len(high) < count:
00531             #Bad split point, points right on boundary
00532             continue
00533         elif not low and len(high)*gap <= (span-split) + 0.5*gap:
00534             #Give a little of the unused low space to the high points
00535             return _spring_layout(high, midpoint + 0.5*gap, maximum, gap)
00536         elif not high and len(low)*gap <= split + 0.5*gap:
00537             #Give a little of the unused highspace to the low points
00538             return _spring_layout(low, minimum, midpoint - 0.5*gap, gap)
00539         elif len(low)*gap <= split - 0.5*gap \
00540         and len(high)*gap <= (span-split) - 0.5*gap:
00541             return _spring_layout(low, minimum, midpoint - 0.5*gap, gap) + \
00542                    _spring_layout(high, midpoint+ 0.5*gap, maximum, gap)
00543     
00544     #This can be count-productive now we can split out into the telomere or
00545     #spacer-segment's vertical space...
00546     #Try not to spread out as far as the min/max unless needed
00547     low = min(desired)
00548     high = max(desired)
00549     if (high-low) / (count-1) >= gap:
00550         #Good, we don't need the full range, and can position the
00551         #min and max exactly as well :)
00552         equal_step = (high-low) / (count-1)
00553         return [low+i*equal_step for i in range(count)]
00554 
00555     low = 0.5 * (minimum + min(desired))
00556     high = 0.5 * (max(desired) + maximum)
00557     if (high-low) / (count-1) >= gap:
00558         #Good, we don't need the full range
00559         equal_step = (high-low) / (count-1)
00560         return [low+i*equal_step for i in range(count)]
00561 
00562     #Crudest solution
00563     return [minimum+i*equal_step for i in range(count)]
00564 
00565 #assert False, _spring_layout([0.10,0.12,0.13,0.14,0.5,0.75, 1.0], 0, 1, 0.1)
00566 #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]
00567 #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]
00568 
00569 def _place_labels(desired_etc, minimum, maximum, gap=0):
00570     desired_etc.sort()
00571     placed = _spring_layout([row[0] for row in desired_etc],
00572                             minimum, maximum, gap)
00573     for old,y2 in zip(desired_etc, placed):
00574         y1, color, name = old
00575         yield (y1, y2, color, name)
00576 
00577 class AnnotatedChromosomeSegment(ChromosomeSegment):
00578     def __init__(self, bp_length, features,
00579                  default_feature_color=colors.blue,
00580                  name_qualifiers = ['gene', 'label', 'name', 'locus_tag', 'product']):
00581         """Like the ChromosomeSegment, but accepts a list of features.
00582         
00583         The features can either be SeqFeature objects, or tuples of five
00584         values: start (int), end (int), strand (+1, -1, O or None), label
00585         (string) and a ReportLab color.
00586         
00587         Note we require 0 <= start <= end <= bp_length, and within the vertical
00588         space allocated to this segmenet lines will be places according to the
00589         start/end coordindates (starting from the top).
00590         
00591         Positive stand features are drawn on the right, negative on the left,
00592         otherwise all the way across.
00593         
00594         We recommend using consisent units for all the segment's scale values
00595         (e.g. their length in base pairs).
00596         
00597         When providing features as SeqFeature objects, the default color
00598         is used, unless the feature's qualifiers include an Artemis colour
00599         string (functionality also in GenomeDiagram). The caption also follows
00600         the GenomeDiagram approach and takes the first qualifier from the list
00601         specified in name_qualifiers.
00602 
00603         Note additional attribute label_sep_percent controls the percentage of
00604         area that the chromosome segment takes up, by default half of the
00605         chr_percent attribute (half of 25%, thus 12.5%)
00606 
00607         """
00608         ChromosomeSegment.__init__(self)
00609         self.bp_length = bp_length
00610         self.features = features
00611         self.default_feature_color = default_feature_color
00612         self.name_qualifiers = name_qualifiers
00613         self.label_sep_percent = self.chr_percent * 0.5
00614 
00615     def _overdraw_subcomponents(self, cur_drawing):
00616         """Draw any annotated features on the chromosome segment.
00617         
00618         Assumes _draw_segment already called to fill out the basic shape,
00619         and assmes that uses the same boundaries.
00620         """
00621         # set the coordinates of the segment -- it'll take up the MIDDLE part
00622         # of the space we have.
00623         segment_y = self.end_y_position
00624         segment_width = (self.end_x_position - self.start_x_position) \
00625                         * self.chr_percent
00626         label_sep = (self.end_x_position - self.start_x_position) \
00627                         * self.label_sep_percent
00628         segment_height = self.start_y_position - self.end_y_position
00629         segment_x = self.start_x_position \
00630                   + 0.5 * (self.end_x_position - self.start_x_position - segment_width)
00631         
00632         left_labels = []
00633         right_labels = []
00634         for f in self.features:
00635             try:
00636                 #Assume SeqFeature objects
00637                 start = f.location.start
00638                 end = f.location.end
00639                 strand = f.strand
00640                 try:
00641                     #Mimic the GenomeDiagram code
00642                     color = _color_trans.artemis_color( \
00643                                              f.qualifiers['color'][0])
00644                 except:
00645                     color = self.default_feature_color
00646                 name = ""
00647                 for qualifier in self.name_qualifiers:            
00648                     if qualifier in f.qualifiers:
00649                         name = f.qualifiers[qualifier][0]
00650                         break
00651             except AttributeError:
00652                 #Assume tuple of ints, string, and color
00653                 start, end, strand, name, color = f
00654             assert 0 <= start <= end <= self.bp_length
00655             if strand == +1 :
00656                 #Right side only
00657                 x = segment_x + segment_width * 0.6
00658                 w = segment_width * 0.4
00659             elif strand == -1:
00660                 #Left side only
00661                 x = segment_x
00662                 w = segment_width * 0.4
00663             else:
00664                 #Both or neighther - full width
00665                 x = segment_x
00666                 w = segment_width
00667             local_scale = segment_height / self.bp_length
00668             fill_rectangle = Rect(x, segment_y + segment_height - local_scale*start,
00669                                   w, local_scale*(start-end))
00670             fill_rectangle.fillColor = color
00671             fill_rectangle.strokeColor = color
00672             cur_drawing.add(fill_rectangle)
00673             if name:
00674                 value = (segment_y + segment_height - local_scale*start, color, name)
00675                 if strand == -1:
00676                     self._left_labels.append(value)
00677                 else:
00678                     self._right_labels.append(value)
00679 
00680             
00681 class TelomereSegment(ChromosomeSegment):
00682     """A segment that is located at the end of a linear chromosome.
00683 
00684     This is just like a regular segment, but it draws the end of a chromosome
00685     which is represented by a half circle. This just overrides the
00686     _draw_segment class of ChromosomeSegment to provide that specialized
00687     drawing.
00688     """
00689     def __init__(self, inverted = 0):
00690         """Initialize a segment at the end of a chromosome.
00691 
00692         See ChromosomeSegment for all of the attributes that can be
00693         customized in a TelomereSegments.
00694 
00695         Arguments:
00696 
00697         o inverted -- Whether or not the telomere should be inverted
00698         (ie. drawn on the bottom of a chromosome)
00699         """
00700         ChromosomeSegment.__init__(self)
00701 
00702         self._inverted = inverted
00703 
00704     def _draw_segment(self, cur_drawing):
00705         """Draw a half circle representing the end of a linear chromosome.
00706         """
00707         # set the coordinates of the segment -- it'll take up the MIDDLE part
00708         # of the space we have.
00709         width = (self.end_x_position - self.start_x_position) \
00710                 * self.chr_percent
00711         height = self.start_y_position - self.end_y_position
00712         center_x = 0.5 * (self.end_x_position + self.start_x_position)
00713         start_x = center_x - 0.5 * width
00714         if self._inverted:
00715             center_y = self.start_y_position
00716             start_angle = 180
00717             end_angle = 360
00718         else:
00719             center_y = self.end_y_position
00720             start_angle = 0
00721             end_angle = 180
00722         
00723         cap_wedge = Wedge(center_x, center_y, width / 2,
00724                           start_angle, end_angle, height)
00725         cap_wedge.strokeColor = None
00726         cap_wedge.fillColor = self.fill_color
00727         cur_drawing.add(cap_wedge)
00728         
00729         #Now draw an arc for the the curved edge of the wedge,
00730         #ommiting the flat end.
00731         cap_arc = ArcPath()
00732         cap_arc.addArc(center_x, center_y, width / 2,
00733                        start_angle, end_angle, height)
00734         cur_drawing.add(cap_arc)
00735 
00736 class SpacerSegment(ChromosomeSegment):
00737     """A segment that is located at the end of a linear chromosome.
00738 
00739     Doesn't draw anything, just empty space which can be helpful
00740     for layout purposes (e.g. making room for feature labels).
00741     """
00742 
00743     def draw(self, cur_diagram):
00744         pass