Back to index

python-biopython  1.60
_Diagram.py
Go to the documentation of this file.
00001 # Copyright 2003-2008 by Leighton Pritchard.  All rights reserved.
00002 # This code is part of the Biopython distribution and governed by its
00003 # license.  Please see the LICENSE file that should have been included
00004 # as part of this package.
00005 #
00006 # Contact:       Leighton Pritchard, Scottish Crop Research Institute,
00007 #                Invergowrie, Dundee, Scotland, DD2 5DA, UK
00008 #                L.Pritchard@scri.ac.uk
00009 ################################################################################
00010 
00011 """ Diagram module
00012 
00013     Provides:
00014 
00015     o Diagram -   Container for information concerning the tracks to be
00016                     drawn in a diagram, and the interface for defining the
00017                     diagram (possibly split these functions in later version?)
00018 
00019     For drawing capabilities, this module uses reportlab to draw and write
00020     the diagram:
00021 
00022     http://www.reportlab.com
00023 
00024     For dealing with biological information, the package expects BioPython
00025     objects - namely SeqRecord ojbects containing SeqFeature objects.
00026 """
00027 
00028 #------------------------------------------------------------------------------
00029 # IMPORTS
00030 
00031 # ReportLab
00032 from reportlab.graphics import renderPS, renderPDF, renderSVG
00033 try:
00034     from reportlab.graphics import renderPM
00035 except ImportError:
00036     #This is an optional part of ReportLab, so may not be installed.
00037     renderPM=None
00038 from reportlab.lib import pagesizes
00039 
00040 # GenomeDiagram
00041 from _LinearDrawer import LinearDrawer
00042 from _CircularDrawer import CircularDrawer
00043 from _Track import Track
00044 
00045 # Builtins
00046 import sys
00047 
00048 from Bio.Graphics import _write
00049 
00050 #------------------------------------------------------------------------------
00051 # CLASSES
00052 
00053 #------------------------------------------------------------
00054 # Diagram
00055 
00056 class Diagram(object):
00057     """ Diagram
00058 
00059         Provides:
00060 
00061         Attributes:
00062 
00063         o name         String, identifier for the diagram
00064 
00065         o tracks       List of Track objects comprising the diagram 
00066 
00067         o format       String, format of the diagram (circular/linear)
00068 
00069         o pagesize     String, the pagesize of output
00070 
00071         o orientation  String, the page orientation (landscape/portrait)
00072 
00073         o x            Float, the proportion of the page to take up with even 
00074                               X margins
00075 
00076         o y            Float, the proportion of the page to take up with even 
00077                               Y margins
00078 
00079         o xl           Float, the proportion of the page to take up with the 
00080                               left X margin
00081 
00082         o xr           Float, the proportion of the page to take up with the 
00083                               right X margin
00084 
00085         o yt           Float, the proportion of the page to take up with the 
00086                               top Y margin
00087 
00088         o yb           Float, the proportion of the page to take up with the 
00089                               bottom Y margin
00090 
00091         o circle_core  Float, the proportion of the available radius to leave
00092                        empty at the center of a circular diagram (0 to 1).
00093 
00094         o start        Int, the base/aa position to start the diagram at
00095 
00096         o end          Int, the base/aa position to end the diagram at
00097 
00098         o tracklines   Boolean, True if track guidelines are to be drawn
00099 
00100         o fragments    Int, for a linear diagram, the number of equal divisions
00101                                 into which the sequence is divided
00102 
00103         o fragment_size Float, the proportion of the space available to each 
00104                                    fragment that should be used in drawing
00105 
00106         o track_size   Float, the proportion of the space available to each 
00107                                   track that should be used in drawing
00108 
00109         o circular     Boolean, True if the genome/sequence to be drawn is, in 
00110                                 reality, circular.  
00111 
00112         Methods:
00113 
00114         o __init__(self, name=None) Called on instantiation
00115 
00116         o draw(self, format='circular', ...) Instructs the package to draw
00117             the diagram
00118 
00119         o write(self, filename='test1.ps', output='PS') Writes the drawn
00120             diagram to a specified file, in a specified format.
00121 
00122         o add_track(self, track, track_level) Adds a Track object to the
00123             diagram, with instructions to place it at a particular level on
00124             the diagram
00125 
00126         o del_track(self, track_level) Removes the track that is to be drawn
00127             at a particular level on the diagram
00128 
00129         o get_tracks(self) Returns the list of Track objects to be drawn
00130             contained in the diagram
00131 
00132         o renumber_tracks(self, low=1) Renumbers all tracks consecutively,
00133             optionally from a passed lowest number
00134 
00135         o get_levels(self) Returns a list of levels currently occupied by
00136             Track objects
00137 
00138         o get_drawn_levels(self) Returns a list of levels currently occupied
00139             by Track objects that will be shown in the drawn diagram (i.e.
00140             are not hidden)
00141 
00142         o range(self) Returns the lowest- and highest-numbered positions
00143             contained within features in all tracks on the diagram as a tuple.
00144 
00145         o __getitem__(self, key) Returns the track contained at the level of
00146             the passed key
00147 
00148         o __str__(self) Returns a formatted string describing the diagram
00149 
00150     """
00151     def __init__(self, name=None, format='circular', pagesize='A3', 
00152          orientation='landscape', x=0.05, y=0.05, xl=None, 
00153          xr=None, yt=None, yb=None, start=None, end=None, 
00154          tracklines=False, fragments=10, fragment_size=0.9, 
00155          track_size=0.75, circular=True, circle_core=0.0):
00156         """ __init__(self, name=None)
00157 
00158             o name  String describing the diagram
00159 
00160             o format    String: 'circular' or 'linear', depending on the sort of
00161                         diagram required
00162 
00163             o pagesize  String describing the ISO size of the image, or a tuple
00164                         of pixels
00165 
00166             o orientation   String describing the required orientation of the
00167                             final drawing ('landscape' or 'portrait')
00168 
00169             o x         Float (0->1) describing the relative size of the X
00170                         margins to the page
00171 
00172             o y         Float (0->1) describing the relative size of the Y
00173                         margins to the page
00174 
00175             o xl        Float (0->1) describing the relative size of the left X
00176                         margin to the page (overrides x)
00177 
00178             o xl        Float (0->1) describing the relative size of the left X
00179                         margin to the page (overrides x)
00180 
00181             o xr        Float (0->1) describing the relative size of the right X
00182                         margin to the page (overrides x)
00183 
00184             o yt        Float (0->1) describing the relative size of the top Y
00185                         margin to the page (overrides y)
00186 
00187             o yb        Float (0->1) describing the relative size of the lower Y
00188                         margin to the page (overrides y)
00189 
00190             o start     Int, the position to begin drawing the diagram at
00191 
00192 
00193             o end       Int, the position to stop drawing the diagram at
00194 
00195             o tracklines    Boolean flag to show (or not) lines delineating 
00196                         tracks on the diagram
00197 
00198             o fragments Int, for linear diagrams, the number of sections into
00199                         which to break the sequence being drawn
00200 
00201             o fragment_size     Float (0->1), for linear diagrams, describing 
00202                                 the proportion of space in a fragment to take
00203                                 up with tracks
00204 
00205             o track_size        Float (0->1) describing the proportion of space
00206                                 in a track to take up with sigils
00207 
00208             o circular  Boolean flag to indicate whether the sequence being
00209                         drawn is circular
00210                         
00211 
00212         """
00213         self.tracks = {}   # Holds all Track objects, keyed by level
00214         self.name = name    # Description of the diagram
00215         # Diagram page setup attributes
00216         self.format = format
00217         self.pagesize = pagesize
00218         self.orientation = orientation
00219         self.x = x
00220         self.y = y
00221         self.xl = xl
00222         self.xr = xr
00223         self.yt = yt
00224         self.yb = yb
00225         self.start = start
00226         self.end = end
00227         self.tracklines = tracklines
00228         self.fragments = fragments
00229         self.fragment_size = fragment_size
00230         self.track_size = track_size
00231         self.circular = circular
00232         self.circle_core = circle_core
00233         self.cross_track_links = []
00234 
00235     def set_all_tracks(self, attr, value):
00236         """ set_all_tracks(self, attr, value)
00237 
00238             o attr      An attribute of the Track class
00239 
00240             o value     The value to set that attribute
00241 
00242             Set the passed attribute of all tracks in the set to the
00243             passed value
00244         """
00245         for track in self.tracks.values():
00246             if hasattr(track, attr):          # If the feature has the attribute
00247                 if getattr(track, attr) != value:
00248                     setattr(track, attr, value)   # set it to the passed value     
00249 
00250     def draw(self, format=None, pagesize=None, orientation=None,
00251              x=None, y=None, xl=None, xr=None, yt=None, yb=None,
00252              start=None, end=None, tracklines=None, fragments=None,
00253              fragment_size=None, track_size=None, circular=None,
00254              circle_core=None, cross_track_links=None):
00255         """Draw the diagram, with passed parameters overriding existing attributes.
00256         """
00257         # Pass the parameters to the drawer objects that will build the 
00258         # diagrams.  At the moment, we detect overrides with an or in the 
00259         # Instantiation arguments, but I suspect there's a neater way to do 
00260         # this.
00261         if format == 'linear':
00262             drawer = LinearDrawer(self, pagesize or self.pagesize, 
00263                                   orientation or self.orientation, 
00264                                   x or self.x, y or self.y, xl or self.xl, 
00265                                   xr or self.xr, yt or self.yt, 
00266                                   yb or self.yb, start or self.start, 
00267                                   end or self.end, 
00268                                   tracklines or self.tracklines,
00269                                   fragments or self.fragments, 
00270                                   fragment_size or self.fragment_size, 
00271                                   track_size or self.track_size,
00272                                   cross_track_links or self.cross_track_links)
00273         else:
00274             drawer = CircularDrawer(self, pagesize or self.pagesize, 
00275                                     orientation or self.orientation, 
00276                                     x or self.x, y or self.y, xl or self.xl, 
00277                                     xr or self.xr, yt or self.yt, 
00278                                     yb or self.yb, start or self.start, 
00279                                     end or self.end, 
00280                                     tracklines or self.tracklines,
00281                                     track_size or self.track_size,
00282                                     circular or self.circular,
00283                                     circle_core or self.circle_core,
00284                                     cross_track_links or self.cross_track_links)
00285         drawer.draw()   # Tell the drawer to complete the drawing
00286         self.drawing = drawer.drawing  # Get the completed drawing
00287         
00288     def write(self, filename='test1.ps', output='PS', dpi=72):
00289         """ write(self, filename='test1.ps', output='PS', dpi=72)
00290 
00291             o filename      String indicating the name of the output file,
00292                             or a handle to write to.
00293 
00294             o output        String indicating output format, one of PS, PDF,
00295                             SVG, or provided the ReportLab renderPM module is
00296                             installed, one of the bitmap formats JPG, BMP,
00297                             GIF, PNG, TIFF or TIFF.  The format can be given
00298                             in upper or lower case.
00299 
00300             o dpi           Resolution (dots per inch) for bitmap formats.
00301 
00302             Write the completed drawing out to a file in a prescribed format
00303 
00304             No return value.
00305         """
00306         return _write(self.drawing, filename, output, dpi=dpi)
00307         
00308     def write_to_string(self, output='PS', dpi=72):
00309         """ write(self, output='PS')
00310 
00311             o output        String indicating output format, one of PS, PDF,
00312                             SVG, JPG, BMP, GIF, PNG, TIFF or TIFF (as
00313                             specified for the write method).
00314 
00315             o dpi           Resolution (dots per inch) for bitmap formats.
00316 
00317             Return the completed drawing as a string in a prescribed format
00318         """
00319         #The ReportLab drawToString method, which this function used to call,
00320         #just uses a cStringIO or StringIO handle with the drawToFile method.
00321         #In order to put all our complicated file format specific code in one
00322         #place we'll just use a StringIO handle here:
00323         from StringIO import StringIO
00324         handle = StringIO()
00325         self.write(handle, output, dpi)
00326         return handle.getvalue()
00327 
00328     def add_track(self, track, track_level):
00329         """ add_track(self, track, track_level)
00330 
00331             o track         Track object to draw
00332 
00333             o track_level   Int, the level at which the track will be drawn
00334                             (above an arbitrary baseline)
00335 
00336             Add a pre-existing Track to the diagram at a given level
00337         """
00338         if track is None:
00339             raise ValueError("Must specify track")
00340         if track_level not in self.tracks:     # No track at that level
00341             self.tracks[track_level] = track   # so just add it
00342         else:       # Already a track there, so shunt all higher tracks up one
00343             occupied_levels = self.get_levels() # Get list of occupied levels...
00344             occupied_levels.sort()              # ...sort it...
00345             occupied_levels.reverse()           # ...reverse it (highest first)
00346             for val in occupied_levels:
00347                 # If track value >= that to be added
00348                 if val >= track.track_level:
00349                     self.tracks[val+1] = self.tracks[val] # ...increment by 1
00350             self.tracks[track_level] = track   # And put the new track in
00351         self.tracks[track_level].track_level = track_level
00352                 
00353 
00354     def new_track(self, track_level, **args):
00355         """ new_track(self, track_level) -> Track
00356 
00357             o track_level   Int, the level at which the track will be drawn
00358                             (above an arbitrary baseline)
00359 
00360             Add a new Track to the diagram at a given level and returns it for
00361             further user manipulation.
00362         """
00363         newtrack = Track()
00364         for key in args:
00365             setattr(newtrack, key, args[key])
00366         if track_level not in self.tracks:        # No track at that level
00367             self.tracks[track_level] = newtrack   # so just add it
00368         else:       # Already a track there, so shunt all higher tracks up one
00369             occupied_levels = self.get_levels() # Get list of occupied levels...
00370             occupied_levels.sort()              # ...sort it...
00371             occupied_levels.reverse()           # ...reverse (highest first)...
00372             for val in occupied_levels:     
00373                 if val >= track_level:        # Track value >= that to be added
00374                     self.tracks[val+1] = self.tracks[val] # ..increment by 1
00375             self.tracks[track_level] = newtrack   # And put the new track in
00376         self.tracks[track_level].track_level = track_level
00377         return newtrack
00378 
00379             
00380     def del_track(self, track_level):
00381         """ del_track(self, track_level)
00382 
00383             o track_level   Int, the level of the track on the diagram to delete
00384 
00385             Remove the track at the passed level from the diagram
00386         """
00387         del self.tracks[track_level]
00388 
00389 
00390     def get_tracks(self):
00391         """ get_tracks(self) -> list
00392 
00393             Returns a list of the tracks contained in the diagram
00394         """
00395         return self.tracks.values()
00396 
00397 
00398     def move_track(self, from_level, to_level):
00399         """ move_track(self, from_level, to_level)
00400 
00401             o from_level    Int, the level at which the track to be moved is
00402                             found
00403 
00404             o to_level      Int, the level to move the track to
00405 
00406             Moves a track from one level on the diagram to another
00407         """
00408         aux = self.tracks[from_level]
00409         del self.tracks[from_level]
00410         self.add_track(aux, to_level)
00411 
00412 
00413     def renumber_tracks(self, low=1, step=1):
00414         """ renumber_tracks(self, low=1, step=1)
00415 
00416             o low       Int, the track number to start from
00417 
00418             o step      Int, the track interval for separation of tracks
00419 
00420             Reassigns all the tracks to run consecutively from the lowest
00421             value (low)
00422         """
00423         track = low                 # Start numbering from here
00424         levels = self.get_levels()  # 
00425 
00426         conversion = {}             # Holds new set of levels
00427         for level in levels:        # Starting at low...
00428             conversion[track] = self.tracks[level] # Add old tracks to new set
00429             conversion[track].track_level = track
00430             track += step                           # step interval
00431         self.tracks = conversion   # Replace old set of levels with new set
00432 
00433     def get_levels(self):
00434         """ get_levels(self) -> [int, int, ...]
00435 
00436             Return a sorted list of levels occupied by tracks in the diagram
00437         """
00438         levels = self.tracks.keys()
00439         levels.sort()
00440         return levels
00441 
00442 
00443     def get_drawn_levels(self):
00444         """ get_drawn_levels(self) -> [int, int, ...]
00445 
00446             Return a sorted list of levels occupied by tracks that are not
00447             explicitly hidden
00448         """
00449         drawn_levels = [key for key in self.tracks.keys() if \
00450                         not self.tracks[key].hide] # get list of shown levels
00451         drawn_levels.sort()
00452         return drawn_levels
00453 
00454 
00455     def range(self):
00456         """ range(self) -> (int, int)
00457 
00458             Returns the lowest and highest base (or mark) numbers containd in
00459             track features as a tuple
00460         """
00461         lows, highs = [], []
00462         for track in self.tracks.values(): # Get ranges for each track
00463             low, high = track.range()
00464             lows.append(low)
00465             highs.append(high)
00466         return (min(lows), max(highs))      # Return extremes from all tracks
00467 
00468     def __getitem__(self, key):
00469         """ __getitem__(self, key) -> Track
00470 
00471             o key       The id of a track in the diagram
00472 
00473             Return the Track object with the passed id
00474         """
00475         return self.tracks[key]
00476 
00477     def __str__(self):
00478         """ __str__(self) -> ""
00479 
00480             Returns a formatted string with information about the diagram
00481         """
00482         outstr = ["\n<%s: %s>" % (self.__class__, self.name)]
00483         outstr.append("%d tracks" % len(self.tracks))
00484         for level in self.get_levels():
00485             outstr.append("Track %d: %s\n" % (level, self.tracks[level]))
00486         outstr = '\n'.join(outstr)
00487         return outstr       
00488