Back to index

python-biopython  1.60
_LinearDrawer.py
Go to the documentation of this file.
00001 # Copyright 2003-2008 by Leighton Pritchard.  All rights reserved.
00002 # Revisions copyright 2008-2009 by Peter Cock.
00003 # This code is part of the Biopython distribution and governed by its
00004 # license.  Please see the LICENSE file that should have been included
00005 # as part of this package.
00006 #
00007 # Contact:       Leighton Pritchard, Scottish Crop Research Institute,
00008 #                Invergowrie, Dundee, Scotland, DD2 5DA, UK
00009 #                L.Pritchard@scri.ac.uk
00010 ################################################################################
00011 
00012 """ LinearDrawer module
00013 
00014     Provides:
00015 
00016     o LinearDrawer -  Drawing object for linear diagrams
00017 
00018     For drawing capabilities, this module uses reportlab to draw and write
00019     the diagram:
00020 
00021     http://www.reportlab.com
00022 
00023     For dealing with biological information, the package expects BioPython
00024     objects:
00025 
00026     http://www.biopython.org
00027 """
00028 
00029 # ReportLab imports
00030 from reportlab.graphics.shapes import *
00031 from reportlab.lib import colors
00032 
00033 # GenomeDiagram imports
00034 from _AbstractDrawer import AbstractDrawer, draw_box, draw_arrow
00035 from _AbstractDrawer import intermediate_points, angle2trig
00036 from _FeatureSet import FeatureSet
00037 from _GraphSet import GraphSet
00038 
00039 from math import ceil
00040 
00041 class LinearDrawer(AbstractDrawer):
00042     """ LinearDrawer(AbstractDrawer)
00043 
00044         Inherits from:
00045 
00046         o AbstractDrawer
00047 
00048         Provides:
00049 
00050         Methods:
00051 
00052         o __init__(self, ...) Called on instantiation
00053 
00054         o set_page_size(self, pagesize, orientation)    Set the page size to the
00055                                                     passed size and orientation
00056 
00057         o set_margins(self, x, y, xl, xr, yt, yb)   Set the drawable area of the
00058                                                     page
00059 
00060         o set_bounds(self, start, end)  Set the bounds for the elements to be
00061                                         drawn
00062 
00063         o is_in_bounds(self, value)     Returns a boolean for whether the position
00064                                         is actually to be drawn
00065 
00066         o __len__(self)     Returns the length of sequence that will be drawn
00067 
00068 
00069         o draw(self)    Place the drawing elements on the diagram
00070 
00071         o init_fragments(self)  Calculate information
00072                                 about sequence fragment locations on the drawing
00073 
00074         o set_track_heights(self)   Calculate information about the offset of
00075                                     each track from the fragment base
00076                                     
00077         o draw_test_tracks(self)    Add lines demarcating each track to the
00078                                     drawing
00079 
00080         o draw_track(self, track)   Return the contents of the passed track as
00081                                     drawing elements
00082 
00083         o draw_scale(self, track)   Return a scale for the passed track as
00084                                     drawing elements
00085 
00086         o draw_tick(self, tickpos, ctr, ticklen, track, draw_label) Return a
00087                                     tick line and possibly a label
00088 
00089         o draw_greytrack(self, track)   Return a grey background and superposed
00090                                         label for the passed track as drawing
00091                                         elements
00092 
00093         o draw_feature_set(self, set)   Return the features in the passed set as
00094                                         drawing elements
00095 
00096         o draw_feature(self, feature)   Return a single feature as drawing
00097                                         elements
00098 
00099         o get_feature_sigil(self, feature, x0, x1, fragment)    Return a single
00100                                         feature as its sigil in drawing elements
00101 
00102         o draw_graph_set(self, set)     Return the data in a set of graphs as
00103                                         drawing elements
00104 
00105         o draw_line_graph(self, graph)  Return the data in a graph as a line
00106                                         graph in drawing elements
00107 
00108         o draw_heat_graph(self, graph)  Return the data in a graph as a heat
00109                                         graph in drawing elements
00110 
00111         o draw_bar_graph(self, graph)   Return the data in a graph as a bar
00112                                         graph in drawing elements
00113 
00114         o canvas_location(self, base)   Return the fragment, and the offset from
00115                                         the left margin, of a passed position
00116                                         in the sequence, on the diagram.
00117 
00118         Attributes:
00119 
00120         o tracklines    Boolean for whether to draw lines dilineating tracks
00121 
00122         o pagesize      Tuple describing the size of the page in pixels
00123 
00124         o x0            Float X co-ord for leftmost point of drawable area
00125 
00126         o xlim          Float X co-ord for rightmost point of drawable area
00127 
00128         o y0            Float Y co-ord for lowest point of drawable area
00129 
00130         o ylim          Float Y co-ord for topmost point of drawable area
00131 
00132         o pagewidth     Float pixel width of drawable area
00133 
00134         o pageheight    Float pixel height of drawable area
00135 
00136         o xcenter       Float X co-ord of center of drawable area
00137 
00138         o ycenter       Float Y co-ord of center of drawable area
00139 
00140         o start         Int, base to start drawing from
00141 
00142         o end           Int, base to stop drawing at
00143 
00144         o length        Int, size of sequence to be drawn
00145 
00146         o fragments     Int, number of fragments into which to divide the
00147                         drawn sequence
00148 
00149         o fragment_size Float (0->1) the proportion of the fragment height to
00150                         draw in
00151 
00152         o track_size    Float (0->1) the proportion of the track height to
00153                         draw in
00154 
00155         o drawing       Drawing canvas
00156 
00157         o drawn_tracks  List of ints denoting which tracks are to be drawn
00158 
00159         o current_track_level   Int denoting which track is currently being
00160                                 drawn
00161 
00162         o fragment_height   Float total fragment height in pixels
00163 
00164         o fragment_bases    Int total fragment length in bases
00165 
00166         o fragment_lines    Dictionary of top and bottom y-coords of fragment,
00167                             keyed by fragment number
00168 
00169         o fragment_limits   Dictionary of start and end bases of each fragment,
00170                             keyed by fragment number
00171 
00172         o track_offsets     Dictionary of number of pixels that each track top,
00173                             center and bottom is offset from the base of a
00174                             fragment, keyed by track
00175         
00176         o cross_track_links List of tuples each with four entries (track A,
00177                             feature A, track B, feature B) to be linked.
00178 
00179     """
00180     def __init__(self, parent=None, pagesize='A3', orientation='landscape',
00181                  x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
00182                  start=None, end=None, tracklines=0, fragments=10,
00183                  fragment_size=0.9, track_size=0.75, cross_track_links=None):
00184         """ __init__(self, parent, pagesize='A3', orientation='landscape',
00185                      x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
00186                      start=None, end=None, tracklines=0, fragments=10,
00187                      fragment_size=0.9, track_size=0.75)
00188 
00189             o parent    Diagram object containing the data that the drawer
00190                         draws
00191 
00192             o pagesize  String describing the ISO size of the image, or a tuple
00193                         of pixels
00194 
00195             o orientation   String describing the required orientation of the
00196                             final drawing ('landscape' or 'portrait')
00197 
00198             o x         Float (0->1) describing the relative size of the X
00199                         margins to the page
00200 
00201             o y         Float (0->1) describing the relative size of the Y
00202                         margins to the page
00203 
00204             o xl        Float (0->1) describing the relative size of the left X
00205                         margin to the page (overrides x)
00206 
00207             o xl        Float (0->1) describing the relative size of the left X
00208                         margin to the page (overrides x)
00209 
00210             o xr        Float (0->1) describing the relative size of the right X
00211                         margin to the page (overrides x)
00212 
00213             o yt        Float (0->1) describing the relative size of the top Y
00214                         margin to the page (overrides y)
00215 
00216             o yb        Float (0->1) describing the relative size of the lower Y
00217                         margin to the page (overrides y)
00218 
00219             o start     Int, the position to begin drawing the diagram at
00220 
00221             o end       Int, the position to stop drawing the diagram at
00222 
00223             o tracklines    Boolean flag to show (or not) lines delineating tracks
00224                             on the diagram            
00225 
00226             o fragments Int, the number of equal fragments into which the
00227                         sequence should be divided for drawing
00228 
00229             o fragment_size Float(0->1) The proportion of the available height
00230                             for the fragment that should be taken up in drawing
00231                             
00232             o track_size    The proportion of the available track height that
00233                             should be taken up in drawing
00234 
00235             o cross_track_links List of tuples each with four entries (track A,
00236                                 feature A, track B, feature B) to be linked.
00237         """
00238         # Use the superclass' instantiation method
00239         AbstractDrawer.__init__(self, parent, pagesize, orientation,
00240                                   x, y, xl, xr, yt, yb, start, end,
00241                                   tracklines, cross_track_links)
00242 
00243         # Useful measurements on the page
00244         self.fragments = fragments
00245         self.fragment_size = fragment_size
00246         self.track_size = track_size
00247 
00248 
00249     def draw(self):
00250         """ draw(self)
00251 
00252             Draw a linear diagram of the data in the parent Diagram object
00253         """
00254         # Instantiate the drawing canvas
00255         self.drawing = Drawing(self.pagesize[0], self.pagesize[1])
00256 
00257         feature_elements = []           # holds feature elements
00258         feature_labels = []             # holds feature labels
00259         greytrack_bgs = []              # holds track background
00260         greytrack_labels = []           # holds track foreground labels
00261         scale_axes = []                 # holds scale axes
00262         scale_labels = []               # holds scale axis labels
00263 
00264         # Get the tracks to be drawn
00265         self.drawn_tracks = self._parent.get_drawn_levels()
00266 
00267         # Set fragment and track sizes
00268         self.init_fragments()
00269         self.set_track_heights()
00270 
00271         # Go through each track in the parent (if it is to be drawn) one by
00272         # one and collate the data as drawing elements
00273         for track_level in self.drawn_tracks: # only use tracks to be drawn
00274             self.current_track_level = track_level      # establish track level
00275             track = self._parent[track_level]           # get the track at that level
00276             gbgs, glabels = self.draw_greytrack(track)  # get greytrack elements
00277             greytrack_bgs.append(gbgs)
00278             greytrack_labels.append(glabels)
00279             features, flabels = self.draw_track(track)  # get feature and graph elements
00280             feature_elements.append(features)
00281             feature_labels.append(flabels)
00282             if track.scale:
00283                 axes, slabels = self.draw_scale(track)      # get scale elements
00284                 scale_axes.append(axes)
00285                 scale_labels.append(slabels)
00286 
00287         feature_cross_links = []
00288         for cross_link_obj in self.cross_track_links:
00289             cross_link_elements = self.draw_cross_link(cross_link_obj)
00290             if cross_link_elements:
00291                 feature_cross_links.append(cross_link_elements)
00292 
00293         # Groups listed in order of addition to page (from back to front)
00294         # Draw track backgrounds
00295         # Draw feature cross track links
00296         # Draw features and graphs
00297         # Draw scale axes
00298         # Draw scale labels
00299         # Draw feature labels
00300         # Draw track labels
00301         element_groups = [greytrack_bgs, feature_cross_links,
00302                           feature_elements, scale_axes,
00303                           scale_labels, feature_labels, greytrack_labels]
00304         for element_group in element_groups:
00305             for element_list in element_group:
00306                 [self.drawing.add(element) for element in element_list]
00307             
00308         if self.tracklines:             # Draw test tracks over top of diagram
00309             self.draw_test_tracks()
00310 
00311 
00312     def init_fragments(self):
00313         """ init_fragments(self)
00314 
00315             Initialises useful values for calculating the positioning of
00316             diagram elements
00317         """
00318         # Set basic heights, lengths etc
00319         self.fragment_height = 1.*self.pageheight/self.fragments     # total fragment height in pixels
00320         self.fragment_bases = ceil(1.*self.length/self.fragments)    # fragment length in bases
00321 
00322         # Key fragment base and top lines by fragment number
00323         self.fragment_lines = {}    # Holds bottom and top line locations of fragments, keyed by fragment number
00324         fragment_crop = (1-self.fragment_size)/2    # No of pixels to crop the fragment
00325         fragy = self.ylim           # Holder for current absolute fragment base
00326         for fragment in range(self.fragments):
00327             fragtop = fragy-fragment_crop * self.fragment_height     # top - crop
00328             fragbtm = fragy-(1-fragment_crop) * self.fragment_height # bottom + crop
00329             self.fragment_lines[fragment] = (fragbtm, fragtop)
00330             fragy -= self.fragment_height                   # next fragment base
00331 
00332         # Key base starts and ends for each fragment by fragment number
00333         self.fragment_limits = {}   # Holds first and last base positions in a fragment
00334         fragment_step = self.fragment_bases  # bases per fragment
00335         fragment_count = 0
00336         # Add start and end positions for each fragment to dictionary
00337         for marker in range(int(self.start), int(self.end), int(fragment_step)):
00338             self.fragment_limits[fragment_count] = (marker, marker+fragment_step)
00339             fragment_count += 1
00340 
00341 
00342     def set_track_heights(self):
00343         """ set_track_heights(self)
00344 
00345             Since tracks may not be of identical heights, the bottom and top
00346             offsets of each track relative to the fragment top and bottom is
00347             stored in a dictionary - self.track_offsets, keyed by track number
00348         """
00349         bot_track = min(min(self.drawn_tracks), 1)
00350         top_track = max(self.drawn_tracks)     # The 'highest' track number to draw
00351 
00352         trackunit_sum = 0           # Total number of 'units' for the tracks
00353         trackunits = {}             # The start and end units for each track, keyed by track number
00354         heightholder = 0            # placeholder variable
00355         for track in range(bot_track, top_track+1): # for all track numbers to 'draw'
00356             try:
00357                 trackheight = self._parent[track].height    # Get track height
00358             except:
00359                 trackheight = 1                             # ...or default to 1
00360             trackunit_sum += trackheight    # increment total track unit height
00361             trackunits[track] = (heightholder, heightholder+trackheight)
00362             heightholder += trackheight     # move to next height
00363         trackunit_height = 1.*self.fragment_height*self.fragment_size/trackunit_sum
00364 
00365         # Calculate top and bottom offsets for each track, relative to fragment
00366         # base
00367         track_offsets = {}      # The offsets from fragment base for each track
00368         track_crop = trackunit_height*(1-self.track_size)/2.    # 'step back' in pixels
00369         assert track_crop >= 0
00370         for track in trackunits:
00371             top = trackunits[track][1]*trackunit_height - track_crop  # top offset
00372             btm = trackunits[track][0]*trackunit_height + track_crop  # bottom offset
00373             ctr = btm+(top-btm)/2.                          # center offset
00374             track_offsets[track] = (btm, ctr, top)          
00375         self.track_offsets = track_offsets
00376 
00377     def draw_test_tracks(self):
00378         """ draw_test_tracks(self)
00379 
00380             Draw red lines indicating the top and bottom of each fragment,
00381             and blue ones indicating tracks to be drawn.
00382         """
00383         # Add lines for each fragment
00384         for fbtm, ftop in self.fragment_lines.values():
00385             self.drawing.add(Line(self.x0, ftop, self.xlim, ftop,
00386                                   strokeColor=colors.red))  # top line
00387             self.drawing.add(Line(self.x0, fbtm, self.xlim, fbtm,
00388                                   strokeColor=colors.red))  # bottom line
00389 
00390             # Add track lines for this fragment - but only for drawn tracks
00391             for track in self.drawn_tracks:
00392                 trackbtm = fbtm + self.track_offsets[track][0]
00393                 trackctr = fbtm + self.track_offsets[track][1]
00394                 tracktop = fbtm + self.track_offsets[track][2]
00395                 self.drawing.add(Line(self.x0, tracktop, self.xlim, tracktop,
00396                                       strokeColor=colors.blue))  # top line
00397                 self.drawing.add(Line(self.x0, trackctr, self.xlim, trackctr,
00398                                       strokeColor=colors.green))  # center line
00399                 self.drawing.add(Line(self.x0, trackbtm, self.xlim, trackbtm,
00400                                       strokeColor=colors.blue))  # bottom line
00401 
00402     def draw_track(self, track):
00403         """ draw_track(self, track) -> ([element, element,...], [element, element,...])
00404 
00405             o track     Track object
00406 
00407             Returns a tuple (list of elements in the track, list of labels in
00408             the track)
00409         """
00410         track_elements = []     # Holds elements from features and graphs
00411         track_labels = []       # Holds labels from features and graphs
00412         
00413         # Distribution dictionary for dealing with different set types
00414         set_methods = {FeatureSet: self.draw_feature_set,
00415                        GraphSet: self.draw_graph_set
00416                        }
00417         
00418         for set in track.get_sets():        # Draw the feature or graph sets
00419             elements, labels = set_methods[set.__class__](set)
00420             track_elements += elements
00421             track_labels += labels
00422         return track_elements, track_labels
00423         
00424 
00425     def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
00426         """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element)
00427 
00428             o tickpos   Int, position of the tick on the sequence
00429 
00430             o ctr       Float, Y co-ord of the center of the track
00431 
00432             o ticklen   How long to draw the tick
00433 
00434             o track     Track, the track the tick is drawn on
00435 
00436             o draw_label    Boolean, write the tick label?
00437 
00438             Returns a drawing element that is the tick on the scale
00439         """
00440         assert self.start <= tickpos and tickpos <= self.end, \
00441                "Tick at %i, but showing %i to %i" \
00442                % (tickpos, self.start, self.end)
00443         assert (track.start is None or track.start <= tickpos) and \
00444                (track.end is None or tickpos <= track.end), \
00445                "Tick at %i, but showing %r to %r for track" \
00446                % (tickpos, track.start, track.end)
00447         fragment, tickx = self.canvas_location(tickpos) # Tick co-ordinates
00448         assert fragment >=0, \
00449                "Fragment %i, tickpos %i" % (fragment, tickpos)
00450         tctr = ctr + self.fragment_lines[fragment][0]   # Center line of the track
00451         tickx += self.x0                # Tick X co-ord
00452         ticktop = tctr + ticklen        # Y co-ord of tick top
00453         tick = Line(tickx, tctr, tickx, ticktop, strokeColor=track.scale_color)
00454         if draw_label: # Put tick position on as label
00455             if track.scale_format == 'SInt':
00456                 if tickpos >= 1000000:
00457                     tickstring = str(tickpos//1000000) + " Mbp"
00458                 elif tickpos >= 1000:
00459                     tickstring = str(tickpos//1000) + " Kbp"
00460                 else:
00461                     tickstring = str(tickpos)
00462             else:
00463                 tickstring = str(tickpos)
00464             label = String(0, 0, tickstring,  # Make label string
00465                    fontName=track.scale_font,
00466                    fontSize=track.scale_fontsize,
00467                    fillColor=track.scale_color)
00468             labelgroup = Group(label)
00469             rotation = angle2trig(track.scale_fontangle)
00470             labelgroup.transform = (rotation[0], rotation[1], rotation[2],
00471                                     rotation[3], tickx, ticktop)
00472         else:
00473             labelgroup = None
00474         return tick, labelgroup
00475 
00476     def draw_scale(self, track):
00477         """ draw_scale(self, track) -> ([element, element,...], [element, element,...])
00478 
00479             o track     Track object
00480 
00481             Returns a tuple of (list of elements in the scale, list of labels
00482             in the scale)
00483         """
00484         scale_elements = []     # Holds axes and ticks
00485         scale_labels = []       # Holds labels
00486 
00487         if not track.scale:     # No scale required, exit early
00488             return [], []
00489 
00490         # Get track location
00491         btm, ctr, top = self.track_offsets[self.current_track_level]
00492         trackheight = (top-ctr)
00493 
00494         # For each fragment, draw the scale for this track
00495         start, end = self._current_track_start_end()
00496         start_f, start_x = self.canvas_location(start)
00497         end_f, end_x = self.canvas_location(end)
00498 
00499         for fragment in range(start_f, end_f+1):
00500             tbtm = btm + self.fragment_lines[fragment][0]
00501             tctr = ctr + self.fragment_lines[fragment][0]
00502             ttop = top + self.fragment_lines[fragment][0]
00503             # X-axis
00504             if fragment == start_f:
00505                 x_left = start_x
00506             else:
00507                 x_left = 0
00508             if fragment == end_f:
00509                 x_right = end_x
00510                 # Y-axis end marker
00511                 scale_elements.append(Line(self.x0+x_right, tbtm, self.x0+x_right, ttop,
00512                                            strokeColor=track.scale_color))
00513             else:
00514                 x_right = self.xlim - self.x0
00515             scale_elements.append(Line(self.x0+x_left, tctr, self.x0+x_right, tctr,
00516                                    strokeColor=track.scale_color))
00517             # Y-axis start marker
00518             scale_elements.append(Line(self.x0+x_left, tbtm, self.x0+x_left, ttop,
00519                                        strokeColor=track.scale_color))
00520 
00521         start, end = self._current_track_start_end()
00522         if track.scale_ticks:   # Ticks are required on the scale
00523             # Draw large ticks
00524             #I want the ticks to be consistently positioned relative to
00525             #the start of the sequence (position 0), not relative to the
00526             #current viewpoint (self.start and self.end)
00527 
00528             ticklen = track.scale_largeticks * trackheight
00529             tickiterval = int(track.scale_largetick_interval)
00530             #Note that we could just start the list of ticks using
00531             #range(0,self.end,tickinterval) and the filter out the
00532             #ones before self.start - but this seems wasteful.
00533             #Using tickiterval * (self.start//tickiterval) is a shortcut.
00534             for tickpos in range(tickiterval * (self.start//tickiterval),
00535                                  int(self.end), tickiterval):
00536                 if tickpos <= start or end <= tickpos:
00537                     continue
00538                 tick, label = self.draw_tick(tickpos, ctr, ticklen,
00539                                              track,
00540                                              track.scale_largetick_labels)
00541                 scale_elements.append(tick)
00542                 if label is not None:   # If there's a label, add it
00543                     scale_labels.append(label)
00544             # Draw small ticks
00545             ticklen = track.scale_smallticks * trackheight
00546             tickiterval = int(track.scale_smalltick_interval)
00547             for tickpos in range(tickiterval * (self.start//tickiterval),
00548                                  int(self.end), tickiterval):
00549                 if tickpos <= start or end <= tickpos:
00550                     continue
00551                 tick, label = self.draw_tick(tickpos, ctr, ticklen,
00552                                              track,
00553                                              track.scale_smalltick_labels)
00554                 scale_elements.append(tick)
00555                 if label is not None:   # If there's a label, add it
00556                     scale_labels.append(label)
00557 
00558         # Check to see if the track contains a graph - if it does, get the
00559         # minimum and maximum values, and put them on the scale Y-axis
00560         if track.axis_labels:
00561             for set in track.get_sets():            # Check all sets...
00562                 if set.__class__ is GraphSet:     # ...for a graph set
00563                     graph_label_min = []
00564                     graph_label_mid = []                    
00565                     graph_label_max = []
00566                     for graph in set.get_graphs():
00567                         quartiles = graph.quartiles()
00568                         minval, maxval = quartiles[0], quartiles[4]
00569                         if graph.center is None:
00570                             midval = (maxval + minval)/2.
00571                             graph_label_min.append("%.3f" % minval)
00572                             graph_label_max.append("%.3f" % maxval)
00573                         else:
00574                             diff = max((graph.center-minval),
00575                                        (maxval-graph.center))
00576                             minval = graph.center-diff
00577                             maxval = graph.center+diff
00578                             midval = graph.center
00579                             graph_label_mid.append("%.3f" % midval)
00580                             graph_label_min.append("%.3f" % minval)
00581                             graph_label_max.append("%.3f" % maxval)
00582                     for fragment in range(start_f, end_f+1):  # Add to all used fragment axes
00583                         tbtm = btm + self.fragment_lines[fragment][0]
00584                         tctr = ctr + self.fragment_lines[fragment][0]
00585                         ttop = top + self.fragment_lines[fragment][0]
00586                         if fragment == start_f:
00587                             x_left = start_x
00588                         else:
00589                             x_left = 0
00590                         for val, pos in [(";".join(graph_label_min), tbtm),
00591                                          (";".join(graph_label_max), ttop),
00592                                          (";".join(graph_label_mid), tctr)]:
00593                             label = String(0, 0, val,
00594                                            fontName=track.scale_font,
00595                                            fontSize=track.scale_fontsize,
00596                                            fillColor=track.scale_color)
00597                             labelgroup = Group(label)
00598                             rotation = angle2trig(track.scale_fontangle)
00599                             labelgroup.transform = (rotation[0], rotation[1], rotation[2],
00600                                                     rotation[3], self.x0 + x_left, pos)
00601                             scale_labels.append(labelgroup)
00602 
00603         return scale_elements, scale_labels
00604         
00605         
00606 
00607     def draw_greytrack(self, track):
00608         """ draw_greytrack(self) -> ([element, element,...], [element, element,...])
00609 
00610             o track     Track object
00611 
00612             Put in a grey background to the current track in all fragments,
00613             if track specifies that we should
00614         """        
00615         greytrack_bgs = []      # Holds grey track backgrounds
00616         greytrack_labels = []   # Holds grey foreground labels
00617 
00618         if not track.greytrack: # No greytrack required, return early
00619             return [], []
00620 
00621         # Get track location
00622         btm, ctr, top = self.track_offsets[self.current_track_level]
00623 
00624         start, end = self._current_track_start_end()
00625         start_fragment, start_offset = self.canvas_location(start)
00626         end_fragment, end_offset = self.canvas_location(end)
00627 
00628         # Add greytrack to all fragments for this track
00629         for fragment in range(start_fragment, end_fragment+1):
00630             tbtm = btm + self.fragment_lines[fragment][0]
00631             tctr = ctr + self.fragment_lines[fragment][0]
00632             ttop = top + self.fragment_lines[fragment][0]
00633             if fragment == start_fragment:
00634                 x1 = self.x0 + start_offset
00635             else:
00636                 x1 = self.x0
00637             if fragment == end_fragment:
00638                 x2 = self.x0 + end_offset
00639             else:
00640                 x2 = self.xlim
00641             box = draw_box((x1, tbtm), (x2, ttop),  # Grey track bg
00642                            colors.Color(0.96,0.96, 0.96))       # is just a box
00643             greytrack_bgs.append(box)
00644 
00645             if track.greytrack_labels:  # If labels are required
00646                 labelstep = (self.pagewidth)/track.greytrack_labels # how far apart should they be?
00647                 label = String(0, 0, track.name,    # label contents
00648                                fontName=track.greytrack_font,
00649                                fontSize=track.greytrack_fontsize,
00650                                fillColor=track.greytrack_fontcolor)
00651                 # Create a new labelgroup at each position the label is required
00652                 for x in range(int(self.x0), int(self.xlim), int(labelstep)):
00653                     if fragment == start_fragment and x < start_offset:
00654                         continue
00655                     if fragment == end_fragment and end_offset < x + label.getBounds()[2]:
00656                         continue
00657                     labelgroup = Group(label)
00658                     rotation = angle2trig(track.greytrack_font_rotation)
00659                     labelgroup.transform = (rotation[0], rotation[1], rotation[2],
00660                                             rotation[3], x, tbtm)
00661                     if not self.xlim-x <= labelstep:    # Don't overlap the end of the track
00662                         greytrack_labels.append(labelgroup)
00663 
00664         return greytrack_bgs, greytrack_labels
00665 
00666 
00667     def draw_feature_set(self, set):
00668         """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...])
00669 
00670             o set       FeatureSet object
00671 
00672             Returns a tuple (list of elements describing features, list of
00673             labels for elements)
00674         """
00675         #print 'draw feature set'
00676         feature_elements = []   # Holds diagram elements belonging to the features
00677         label_elements = []     # Holds diagram elements belonging to feature labels 
00678 
00679         # Collect all the elements for the feature set
00680         for feature in set.get_features():
00681             if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end):
00682                 features, labels = self.draw_feature(feature)   # get elements and labels
00683                 feature_elements += features
00684                 label_elements += labels
00685 
00686         return feature_elements, label_elements
00687 
00688     def draw_feature(self, feature):
00689         """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...])
00690 
00691             o feature           Feature containing location info
00692 
00693             Returns tuple of (list of elements describing single feature, list
00694             of labels for those elements)
00695         """
00696         if feature.hide:        # Feature hidden, don't draw it...
00697             return [], []
00698         
00699         feature_elements = []   # Holds diagram elements belonging to the feature
00700         label_elements = []     # Holds labels belonging to the feature
00701 
00702         start, end = self._current_track_start_end()
00703         # A single feature may be split into subfeatures, so loop over them
00704         for locstart, locend in feature.locations:
00705             if locend < start:
00706                 continue
00707             locstart = max(locstart, start)
00708             if end < locstart:
00709                 continue
00710             locend = min(locend, end)
00711             feature_boxes = self.draw_feature_location(feature, locstart, locend)
00712             for box, label in feature_boxes:
00713                 feature_elements.append(box)
00714                 if label is not None:
00715                     label_elements.append(label)
00716             
00717         return feature_elements, label_elements
00718 
00719     def draw_feature_location(self, feature, locstart, locend):
00720         feature_boxes = []
00721         # Get start and end positions for feature/subfeatures
00722         start_fragment, start_offset = self.canvas_location(locstart)
00723         end_fragment, end_offset = self.canvas_location(locend)
00724             #print "start_fragment, start_offset", start_fragment, start_offset
00725             #print "end_fragment, end_offset", end_fragment, end_offset
00726             #print "start, end", locstart, locend
00727 
00728         # Note that there is a strange situation where a feature may be in
00729         # several parts, and one or more of those parts may end up being
00730         # drawn on a non-existent fragment.  So we check that the start and
00731         # end fragments do actually exist in terms of the drawing
00732         allowed_fragments = self.fragment_limits.keys()
00733         if start_fragment in allowed_fragments and end_fragment in allowed_fragments:
00734             
00735             #print feature.name, feature.start, feature.end, start_offset, end_offset
00736             if start_fragment == end_fragment:  # Feature is found on one fragment
00737                 feature_box, label = self.get_feature_sigil(feature, start_offset,
00738                                                             end_offset, start_fragment)
00739                 feature_boxes.append((feature_box, label))
00740                 #feature_elements.append(feature_box)
00741                 #if label is not None:   # There is a label for the feature
00742                 #    label_elements.append(label)                        
00743             else: # Feature is split over two or more fragments
00744                 fragment = start_fragment
00745                 start = start_offset
00746                 # The bit that runs up to the end of the first fragment,
00747                 # and any bits that subsequently span whole fragments
00748                 while self.fragment_limits[fragment][1] < locend:
00749                     #print fragment, self.fragment_limits[fragment][1], locend
00750                     feature_box, label = self.get_feature_sigil(feature, start,
00751                                                                 self.pagewidth,
00752                                                                 fragment)
00753                     
00754                     fragment += 1   # move to next fragment
00755                     start = 0       # start next sigil from start of fragment
00756                     feature_boxes.append((feature_box, label))
00757                     #feature_elements.append(feature_box)
00758                     #if label is not None:   # There's a label for the feature
00759                     #    label_elements.append(label)
00760                 # The last bit of the feature
00761                 #print locend, self.end, fragment
00762                 #print self.fragment_bases, self.length
00763                 feature_box, label = self.get_feature_sigil(feature, 0,
00764                                                             end_offset, fragment)
00765                 feature_boxes.append((feature_box, label))
00766         #if locstart > locend:
00767         #    print locstart, locend, feature.strand, feature_boxes, feature.name
00768         return feature_boxes
00769 
00770     def draw_cross_link(self, cross_link):
00771         startA = cross_link.startA
00772         startB = cross_link.startB
00773         endA = cross_link.endA
00774         endB = cross_link.endB
00775            
00776         if not self.is_in_bounds(startA) \
00777         and not self.is_in_bounds(endA):
00778             return None
00779         if not self.is_in_bounds(startB) \
00780         and not self.is_in_bounds(endB):
00781             return None
00782 
00783         if startA < self.start: startA = self.start
00784         if startB < self.start: startB = self.start
00785         if self.end < endA: endA = self.end
00786         if self.end < endB: endB = self.end
00787 
00788         trackobjA = cross_link._trackA(self._parent.tracks.values())
00789         trackobjB = cross_link._trackB(self._parent.tracks.values())
00790         assert trackobjA is not None
00791         assert trackobjB is not None
00792         if trackobjA == trackobjB: raise NotImplementedError()
00793 
00794         if trackobjA.start is not None:
00795             if endA < trackobjA.start:
00796                 return
00797             startA = max(startA, trackobjA.start)
00798         if trackobjA.end is not None:
00799             if trackobjA.end < startA:
00800                 return
00801             endA = min(endA, trackobjA.end)
00802         if trackobjB.start is not None:
00803             if endB < trackobjB.start:
00804                 return
00805             startB = max(startB, trackobjB.start)
00806         if trackobjB.end is not None:
00807             if trackobjB.end < startB:
00808                 return
00809             endB = min(endB, trackobjB.end)
00810 
00811         for track_level in self._parent.get_drawn_levels():
00812             track = self._parent[track_level]
00813             if track == trackobjA:
00814                 trackA = track_level
00815             if track == trackobjB:
00816                 trackB = track_level
00817         if trackA == trackB: raise NotImplementedError()
00818 
00819         if cross_link.color == colors.white and border is None:   # Force black border on 
00820             strokecolor = colors.black                 # white boxes with
00821         elif cross_link.border is None:                           # undefined border, else
00822             strokecolor = cross_link.color                        # use fill color
00823         if cross_link.border:
00824             if not isinstance(cross_link.border, colors.Color):
00825                 raise ValueError("Invalid border color %s" % repr(cross_link.border))
00826             strokecolor = cross_link.border
00827         else:
00828             #e.g. False
00829             strokecolor = None
00830 
00831         allowed_fragments = self.fragment_limits.keys()
00832 
00833         start_fragmentA, start_offsetA = self.canvas_location(startA)
00834         end_fragmentA, end_offsetA = self.canvas_location(endA)
00835         if start_fragmentA not in allowed_fragments \
00836         or end_fragmentA not in allowed_fragments:
00837             return
00838 
00839         start_fragmentB, start_offsetB = self.canvas_location(startB)
00840         end_fragmentB, end_offsetB = self.canvas_location(endB)
00841         if start_fragmentB not in allowed_fragments \
00842         or end_fragmentB not in allowed_fragments:
00843             return
00844 
00845         #TODO - Better drawing of flips when split between fragments
00846 
00847         answer = []
00848         for fragment in range(min(start_fragmentA, start_fragmentB),
00849                               max(end_fragmentA, end_fragmentB)+1):
00850             btmA, ctrA, topA = self.track_offsets[trackA]
00851             btmA += self.fragment_lines[fragment][0]
00852             ctrA += self.fragment_lines[fragment][0]
00853             topA += self.fragment_lines[fragment][0]
00854     
00855             btmB, ctrB, topB = self.track_offsets[trackB]
00856             btmB += self.fragment_lines[fragment][0]
00857             ctrB += self.fragment_lines[fragment][0]
00858             topB += self.fragment_lines[fragment][0]
00859     
00860             if self.fragment_limits[fragment][1] < endA:
00861                 xAe = self.x0 + self.pagewidth
00862                 crop_rightA = True
00863             else:
00864                 xAe = self.x0 + end_offsetA
00865                 crop_rightA = False
00866             if self.fragment_limits[fragment][1] < endB:
00867                 xBe = self.x0 + self.pagewidth
00868                 crop_rightB = True
00869             else:
00870                 xBe = self.x0 + end_offsetB
00871                 crop_rightB = False
00872 
00873             if fragment < start_fragmentA:
00874                 xAs = self.x0 + self.pagewidth
00875                 xAe = xAs
00876                 crop_leftA = False
00877             elif fragment == start_fragmentA:
00878                 xAs = self.x0 + start_offsetA
00879                 crop_leftA = False
00880             else:
00881                 xAs = self.x0
00882                 crop_leftA = True
00883 
00884             if fragment < start_fragmentB:
00885                 xBs = self.x0 + self.pagewidth
00886                 xBe = xBs
00887                 crop_leftB = False
00888             elif fragment == start_fragmentB:
00889                 xBs = self.x0 + start_offsetB
00890                 crop_leftB = False
00891             else:
00892                 xBs = self.x0
00893                 crop_leftB = True
00894 
00895             if ctrA < ctrB:
00896                 yA = topA
00897                 yB = btmB
00898             else:
00899                 yA = btmA
00900                 yB = topB
00901 
00902             if fragment < start_fragmentB or end_fragmentB < fragment:
00903                 if cross_link.flip:
00904                     #Just draw A as a triangle to left/right
00905                     if fragment < start_fragmentB:
00906                         extra = [self.x0 + self.pagewidth, 0.5 * (yA + yB)]
00907                     else:
00908                         extra = [self.x0 , 0.5 * (yA + yB)]
00909                 else:
00910                     if fragment < start_fragmentB:
00911                         extra = [self.x0 + self.pagewidth, 0.7*yA + 0.3*yB,
00912                                  self.x0 + self.pagewidth, 0.3*yA + 0.7*yB]
00913                     else:
00914                         extra = [self.x0 , 0.3*yA + 0.7*yB,
00915                                  self.x0 , 0.7*yA + 0.3*yB]
00916                 answer.append(Polygon([xAs, yA, xAe, yA] + extra,
00917                                strokeColor=strokecolor,
00918                                fillColor=cross_link.color,
00919                                #default is mitre/miter which can stick out too much:
00920                                strokeLineJoin=1, #1=round
00921                                strokewidth=0))
00922             elif fragment < start_fragmentA or end_fragmentA < fragment:
00923                 if cross_link.flip:
00924                     #Just draw B as a triangle to left
00925                     if fragment < start_fragmentA:
00926                         extra = [self.x0 + self.pagewidth, 0.5 * (yA + yB)]
00927                     else:
00928                         extra = [self.x0 , 0.5 * (yA + yB)]
00929                 else:
00930                     if fragment < start_fragmentA:
00931                         extra = [self.x0 + self.pagewidth, 0.3*yA + 0.7*yB,
00932                                  self.x0 + self.pagewidth, 0.7*yA + 0.3*yB]
00933                     else:
00934                         extra = [self.x0 , 0.7*yA + 0.3*yB,
00935                                  self.x0 , 0.3*yA + 0.7*yB]
00936                 answer.append(Polygon([xBs, yB, xBe, yB] + extra,
00937                                strokeColor=strokecolor,
00938                                fillColor=cross_link.color,
00939                                #default is mitre/miter which can stick out too much:
00940                                strokeLineJoin=1, #1=round
00941                                strokewidth=0))
00942             elif cross_link.flip and ((crop_leftA and not crop_rightA) or \
00943                                     (crop_leftB and not crop_rightB)):
00944                 #On left end of fragment... force "crossing" to margin
00945                 answer.append(Polygon([xAs, yA, xAe, yA,
00946                                        self.x0, 0.5 * (yA + yB),
00947                                        xBe, yB, xBs, yB],
00948                                strokeColor=strokecolor,
00949                                fillColor=cross_link.color,
00950                                #default is mitre/miter which can stick out too much:
00951                                strokeLineJoin=1, #1=round
00952                                strokewidth=0))
00953             elif cross_link.flip and ((crop_rightA and not crop_leftA) or \
00954                                       (crop_rightB and not crop_leftB)):
00955                 #On right end... force "crossing" to margin
00956                 answer.append(Polygon([xAs, yA, xAe, yA,
00957                                        xBe, yB, xBs, yB,
00958                                        self.x0 + self.pagewidth, 0.5 * (yA + yB)],
00959                                strokeColor=strokecolor,
00960                                fillColor=cross_link.color,
00961                                #default is mitre/miter which can stick out too much:
00962                                strokeLineJoin=1, #1=round
00963                                strokewidth=0))
00964             elif cross_link.flip:
00965                 answer.append(Polygon([xAs, yA, xAe, yA, xBs, yB, xBe, yB],
00966                                strokeColor=strokecolor,
00967                                fillColor=cross_link.color,
00968                                #default is mitre/miter which can stick out too much:
00969                                strokeLineJoin=1, #1=round
00970                                strokewidth=0))
00971             else:
00972                 answer.append(Polygon([xAs, yA, xAe, yA, xBe, yB, xBs, yB],
00973                                strokeColor=strokecolor,
00974                                fillColor=cross_link.color,
00975                                #default is mitre/miter which can stick out too much:
00976                                strokeLineJoin=1, #1=round
00977                                strokewidth=0))
00978         return answer
00979 
00980     def get_feature_sigil(self, feature, x0, x1, fragment, **kwargs):
00981         """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element, element)
00982 
00983             o feature       Feature object
00984 
00985             o x0            Start X co-ordinate on diagram
00986 
00987             o x1            End X co-ordinate on diagram
00988 
00989             o fragment      The fragment on which the feature appears
00990 
00991             Returns a drawable indicator of the feature, and any required label
00992             for it
00993         """
00994         # Establish co-ordinates for drawing
00995         x0, x1 = self.x0 + x0, self.x0 + x1
00996         btm, ctr, top = self.track_offsets[self.current_track_level]
00997         try:
00998             btm += self.fragment_lines[fragment][0]
00999             ctr += self.fragment_lines[fragment][0]
01000             top += self.fragment_lines[fragment][0]
01001         except:     # Only called if the method screws up big time
01002             print "We've got a screw-up"
01003             print self.start, self.end
01004             print self.fragment_bases
01005             print x0, x1
01006             for locstart, locend in feature.locations:
01007                 print self.canvas_location(locstart)
01008                 print self.canvas_location(locend)
01009             print 'FEATURE\n', feature
01010             1/0
01011 
01012         # Distribution dictionary for various ways of drawing the feature
01013         # Each method takes the corners of a containing box and a color
01014         # as argument
01015         draw_methods = {'BOX': draw_box,
01016                         'ARROW': draw_arrow,
01017                         }
01018         method = draw_methods[feature.sigil]
01019         kwargs['head_length_ratio'] = feature.arrowhead_length
01020         kwargs['shaft_height_ratio'] = feature.arrowshaft_height
01021 
01022         #Support for clickable links... needs ReportLab 2.4 or later
01023         #which added support for links in SVG output.
01024         if hasattr(feature, "url") :
01025             kwargs["hrefURL"] = feature.url
01026             kwargs["hrefTitle"] = feature.name
01027 
01028         strand = feature.strand
01029         
01030         # Get sigil for the feature, location dependent on the feature strand
01031         if strand == 1:
01032             sigil = method((x0, ctr), (x1, top), color=feature.color,
01033                            border=feature.border,
01034                            orientation='right', **kwargs)
01035         elif strand == -1:
01036             sigil = method((x1, btm), (x0, ctr), color=feature.color,
01037                            border=feature.border,
01038                            orientation='left', **kwargs)
01039         else:
01040             sigil = method((x0, btm), (x1, top), color=feature.color,
01041                            border=feature.border,
01042                            **kwargs)
01043         if feature.label:   # Feature requires a label
01044             label = String(0, 0, feature.name,
01045                            fontName=feature.label_font,
01046                            fontSize=feature.label_size,
01047                            fillColor=feature.label_color)
01048             labelgroup = Group(label)
01049             # Feature is on top, or covers both strands (location affects
01050             # the height and rotation of the label)
01051             if feature.strand in (0, 1):    
01052                 rotation = angle2trig(feature.label_angle)
01053                 if feature.label_position in ('start', "5'", 'left'):
01054                     pos = x0
01055                 elif feature.label_position in ('middle', 'center', 'centre'):
01056                     pos = (x1 + x0)/2.
01057                 else:
01058                     pos = x1
01059                 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
01060                                         rotation[3], pos, top)
01061             else:   # Feature on bottom strand
01062                 rotation = angle2trig(feature.label_angle + 180)                        
01063                 if feature.label_position in ('start', "5'", 'left'):
01064                     pos = x1
01065                 elif feature.label_position in ('middle', 'center', 'centre'):
01066                     pos = (x1 + x0)/2.
01067                 else:
01068                     pos = x0
01069                 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
01070                                         rotation[3], pos, btm)
01071         else:
01072             labelgroup = None
01073         return sigil, labelgroup
01074 
01075                 
01076     def draw_graph_set(self, set):
01077         """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...])
01078 
01079             o set       GraphSet object
01080 
01081             Returns tuple (list of graph elements, list of graph labels)
01082         """
01083         #print 'draw graph set'
01084         elements = []   # Holds graph elements
01085 
01086         # Distribution dictionary for how to draw the graph
01087         style_methods = {'line': self.draw_line_graph,
01088                          'heat': self.draw_heat_graph,
01089                          'bar': self.draw_bar_graph
01090                          }
01091 
01092         for graph in set.get_graphs():
01093             elements += style_methods[graph.style](graph)
01094 
01095         return elements, []
01096 
01097 
01098     def draw_line_graph(self, graph):
01099         """ draw_line_graph(self, graph) -> [element, element,...]
01100 
01101             o graph     Graph object
01102 
01103             Returns a line graph as a list of drawable elements 
01104         """
01105         #print '\tdraw_line_graph'
01106         line_elements = []                  # Holds drawable elements
01107 
01108         # Get graph data
01109         data_quartiles = graph.quartiles()
01110         minval, maxval = data_quartiles[0],data_quartiles[4]
01111         btm, ctr, top = self.track_offsets[self.current_track_level]
01112         trackheight = 0.5*(top-btm)
01113         datarange = maxval - minval
01114         if datarange == 0:
01115             datarange = trackheight
01116         
01117         start, end = self._current_track_start_end()
01118         data = graph[start:end]
01119 
01120         # midval is the value at which the x-axis is plotted, and is the
01121         # central ring in the track
01122         if graph.center is None:
01123             midval = (maxval + minval)/2.    
01124         else:
01125             midval = graph.center
01126         # Whichever is the greatest difference: max-midval or min-midval, is
01127         # taken to specify the number of pixel units resolved along the
01128         # y-axis
01129         resolution = max((midval-minval), (maxval-midval))
01130 
01131         # Start from first data point
01132         pos, val = data[0]
01133         lastfrag, lastx = self.canvas_location(pos)
01134         lastx += self.x0        # Start xy co-ords
01135         lasty = trackheight*(val-midval)/resolution + \
01136                 self.fragment_lines[lastfrag][0] + ctr
01137         lastval = val
01138         # Add a series of lines linking consecutive data points
01139         for pos, val in data:   
01140             frag, x = self.canvas_location(pos)
01141             x += self.x0        # next xy co-ords
01142             y = trackheight*(val-midval)/resolution + \
01143                 self.fragment_lines[frag][0] + ctr
01144             if frag == lastfrag:    # Points on the same fragment: draw the line
01145                 line_elements.append(Line(lastx, lasty, x, y,
01146                                           strokeColor = graph.poscolor,
01147                                           strokeWidth = graph.linewidth))
01148             else:   # Points not on the same fragment, so interpolate
01149                 tempval = 1.*(val-lastval)/(x-lastx)
01150                 tempy = trackheight*(val-midval)/resolution + \
01151                         self.fragment_lines[lastfrag][0] + ctr
01152                 line_elements.append(Line(lastx, lasty, self.xlim, tempy,
01153                                           strokeColor = graph.poscolor,
01154                                           strokeWidth = graph.linewidth))
01155                 tempy = trackheight*(val-midval)/resolution + \
01156                         self.fragment_lines[frag][0] + ctr
01157                 line_elements.append(Line(self.x0, tempy, x, y,
01158                                           strokeColor = graph.poscolor,
01159                                           strokeWidth = graph.linewidth))
01160             lastfrag, lastx, lasty, lastval = frag, x, y, val
01161             
01162         return line_elements
01163 
01164 
01165     def draw_heat_graph(self, graph):
01166         """ draw_heat_graph(self, graph) -> [element, element,...]
01167 
01168             o graph     Graph object
01169 
01170             Returns a list of drawable elements for the heat graph
01171         """
01172         #print '\tdraw_heat_graph'
01173         # At each point contained in the graph data, we draw a box that is the
01174         # full height of the track, extending from the midpoint between the
01175         # previous and current data points to the midpoint between the current
01176         # and next data points
01177         heat_elements = []  # Holds drawable elements for the graph
01178 
01179         # Get graph data and information
01180         data_quartiles = graph.quartiles()
01181         minval, maxval = data_quartiles[0],data_quartiles[4]
01182         midval = (maxval + minval)/2.    # mid is the value at the X-axis
01183         btm, ctr, top = self.track_offsets[self.current_track_level]
01184         trackheight = (top-btm)
01185         
01186         start, end = self._current_track_start_end()
01187         data = intermediate_points(start, end, graph[start:end])
01188 
01189         if not data:
01190             return []
01191 
01192         # Create elements on the graph, indicating a large positive value by
01193         # the graph's poscolor, and a large negative value by the graph's
01194         # negcolor attributes
01195         for pos0, pos1, val in data:
01196             #assert start <= pos0 <= pos1 <= end
01197             fragment0, x0 = self.canvas_location(pos0)
01198             fragment1, x1 = self.canvas_location(pos1)
01199             x0, x1 = self.x0 + x0, self.x0 + x1     # account for margin
01200             #print 'x1 before:', x1
01201             
01202             # Calculate the heat color, based on the differential between
01203             # the value and the median value
01204             heat = colors.linearlyInterpolatedColor(graph.poscolor,
01205                                                     graph.negcolor,
01206                                                     maxval, minval, val)
01207             
01208             # Draw heat box
01209             if fragment0 == fragment1:  # Box is contiguous on one fragment
01210                 if pos1 >= self.fragment_limits[fragment0][1]:
01211                     x1 = self.xlim
01212                 ttop = top + self.fragment_lines[fragment0][0]
01213                 tbtm = btm + self.fragment_lines[fragment0][0]
01214                 #print 'equal', pos0, pos1, val
01215                 #print pos0, pos1, fragment0, fragment1
01216                 heat_elements.append(draw_box((x0, tbtm), (x1, ttop),
01217                                               color=heat, border=None))
01218             else:   # box is split over two or more fragments
01219                 #if pos0 >= self.fragment_limits[fragment0][0]:
01220                 #    fragment0 += 1
01221                 fragment = fragment0
01222                 start_x = x0
01223                 while self.fragment_limits[fragment][1] <= pos1:
01224                     #print pos0, self.fragment_limits[fragment][1], pos1
01225                     ttop = top + self.fragment_lines[fragment][0]
01226                     tbtm = btm + self.fragment_lines[fragment][0]
01227                     heat_elements.append(draw_box((start_x, tbtm),
01228                                                   (self.xlim, ttop),
01229                                                   color=heat,
01230                                                   border=None))
01231                     fragment += 1
01232                     start_x = self.x0
01233                 ttop = top + self.fragment_lines[fragment][0]
01234                 tbtm = btm + self.fragment_lines[fragment][0]
01235                 # Add the last part of the bar
01236                 #print 'x1 after:', x1, '\n'
01237                 heat_elements.append(draw_box((self.x0, tbtm), (x1, ttop),
01238                                               color=heat, border=None))                    
01239 
01240         return heat_elements
01241 
01242 
01243     def draw_bar_graph(self, graph):
01244         """ draw_bar_graph(self, graph) -> [element, element,...]
01245 
01246             o graph     Graph object
01247 
01248             Returns a list of drawable elements for a bar graph of the passed
01249             Graph object
01250         """
01251         #print '\tdraw_bar_graph'
01252         # At each point contained in the graph data, we draw a vertical bar
01253         # from the track center to the height of the datapoint value (positive
01254         # values go up in one color, negative go down in the alternative
01255         # color).
01256         bar_elements = []   # Holds drawable elements for the graph
01257         
01258         # Set the number of pixels per unit for the data
01259         data_quartiles = graph.quartiles()
01260         minval, maxval = data_quartiles[0],data_quartiles[4]
01261         btm, ctr, top = self.track_offsets[self.current_track_level]
01262         trackheight = 0.5*(top-btm)
01263         datarange = maxval - minval
01264         if datarange == 0:
01265             datarange = trackheight
01266         data = graph[self.start:self.end]
01267         # midval is the value at which the x-axis is plotted, and is the
01268         # central ring in the track
01269         if graph.center is None:
01270             midval = (maxval + minval)/2.    
01271         else:
01272             midval = graph.center
01273 
01274         # Convert data into 'binned' blocks, covering half the distance to the
01275         # next data point on either side, accounting for the ends of fragments
01276         # and tracks
01277         start, end = self._current_track_start_end()
01278         data = intermediate_points(start, end, graph[start:end])
01279 
01280         if not data:
01281             return []
01282 
01283         # Whichever is the greatest difference: max-midval or min-midval, is
01284         # taken to specify the number of pixel units resolved along the
01285         # y-axis
01286         resolution = max((midval-minval), (maxval-midval))
01287         if resolution == 0:
01288             resolution = trackheight
01289         
01290         # Create elements for the bar graph based on newdata
01291         for pos0, pos1, val in data:
01292             fragment0, x0 = self.canvas_location(pos0)
01293             fragment1, x1 = self.canvas_location(pos1)
01294             x0, x1 = self.x0 + x0, self.x0 + x1     # account for margin
01295             barval = trackheight*(val-midval)/resolution
01296             if barval >=0:  # Different colors for bars that extend above...
01297                 barcolor = graph.poscolor
01298             else:           # ...or below the axis
01299                 barcolor = graph.negcolor
01300 
01301             # Draw bar
01302             if fragment0 == fragment1:  # Box is contiguous
01303                 if pos1 >= self.fragment_limits[fragment0][1]:
01304                     x1 = self.xlim
01305                 tctr = ctr + self.fragment_lines[fragment0][0]
01306                 barval += tctr                
01307                 bar_elements.append(draw_box((x0, tctr), (x1, barval),
01308                                              color=barcolor))
01309             else:   # Box is split over two or more fragments                       
01310                 fragment = fragment0
01311                 #if pos0 >= self.fragment_limits[fragment0][0]:
01312                 #    fragment += 1         
01313                 start = x0
01314                 while self.fragment_limits[fragment][1] < pos1:
01315                     tctr = ctr + self.fragment_lines[fragment][0]
01316                     thisbarval = barval + tctr
01317                     bar_elements.append(draw_box((start, tctr),
01318                                                  (self.xlim, thisbarval),
01319                                                  color=barcolor))
01320                     fragment += 1
01321                     start = self.x0
01322                 tctr = ctr + self.fragment_lines[fragment1][0]
01323                 barval += tctr
01324                 # Add the last part of the bar
01325                 bar_elements.append(draw_box((self.x0, tctr), (x1, barval),
01326                                              color=barcolor))
01327 
01328         return bar_elements
01329 
01330       
01331     def canvas_location(self, base):
01332         """ canvas_location(self, base) -> (int, float)
01333 
01334             o base      The base number on the genome sequence
01335 
01336             Returns the x-coordinate and fragment number of a base on the
01337             genome sequence, in the context of the current drawing setup
01338         """
01339         base = int(base - self.start)   # number of bases we are from the start
01340         fragment = int(base / self.fragment_bases)
01341         if fragment < 1:    # First fragment
01342             base_offset = base
01343             fragment = 0
01344         elif fragment >= self.fragments:
01345             fragment = self.fragments-1
01346             base_offset = self.fragment_bases
01347         else:               # Calculate number of bases from start of fragment
01348             base_offset = base % self.fragment_bases
01349         assert fragment < self.fragments, (base, self.start, self.end, self.length, self.fragment_bases)
01350         # Calculate number of pixels from start of fragment
01351         x_offset = 1. * self.pagewidth * base_offset / self.fragment_bases
01352         return fragment, x_offset
01353