Package Bio :: Package Graphics :: Package GenomeDiagram :: Module _AbstractDrawer
[hide private]
[frames] | no frames]

Source Code for Module Bio.Graphics.GenomeDiagram._AbstractDrawer

  1  # Copyright 2003-2008 by Leighton Pritchard.  All rights reserved. 
  2  # Revisions copyright 2008-2009 by Peter Cock. 
  3  # This code is part of the Biopython distribution and governed by its 
  4  # license.  Please see the LICENSE file that should have been included 
  5  # as part of this package. 
  6  # 
  7  # Contact:       Leighton Pritchard, Scottish Crop Research Institute, 
  8  #                Invergowrie, Dundee, Scotland, DD2 5DA, UK 
  9  #                L.Pritchard@scri.ac.uk 
 10  ################################################################################ 
 11   
 12  """ AbstractDrawer module (considered to be a private module, the API may change!) 
 13   
 14      Provides: 
 15   
 16      o AbstractDrawer -    Superclass for methods common to *Drawer objects 
 17   
 18      o page_sizes -          Method that returns a ReportLab pagesize when passed 
 19                              a valid ISO size 
 20   
 21      o draw_box -            Method that returns a closed path object when passed 
 22                              the proper co-ordinates.  For HORIZONTAL boxes only. 
 23   
 24      o angle2trig -          Method that returns a tuple of values that are the 
 25                              vector for rotating a point through a passed angle, 
 26                              about an origin 
 27   
 28      o intermediate_points - Method that returns a list of values intermediate 
 29                              between the points in a passed dataset 
 30       
 31      For drawing capabilities, this module uses reportlab to draw and write 
 32      the diagram: 
 33   
 34      http://www.reportlab.com 
 35   
 36      For dealing with biological information, the package expects BioPython 
 37      objects: 
 38   
 39      http://www.biopython.org 
 40  """ 
 41   
 42  # ReportLab imports 
 43  from reportlab.lib import pagesizes 
 44  from reportlab.lib import colors 
 45  from reportlab.graphics.shapes import * 
 46   
 47  from math import pi 
 48   
 49  ################################################################################ 
 50  # METHODS 
 51  ################################################################################ 
 52  # Utility method to translate strings to ISO page sizes 
53 -def page_sizes(size):
54 """ page_sizes(size) 55 56 o size A string representing a standard page size 57 58 Returns a ReportLab pagesize when passed a valid size string 59 """ 60 sizes = {'A0': pagesizes.A0, # ReportLab pagesizes, keyed by ISO string 61 'A1': pagesizes.A1, 62 'A2': pagesizes.A2, 63 'A3': pagesizes.A3, 64 'A4': pagesizes.A4, 65 'A5': pagesizes.A5, 66 'A6': pagesizes.A6, 67 'B0': pagesizes.B0, 68 'B1': pagesizes.B1, 69 'B2': pagesizes.B2, 70 'B3': pagesizes.B3, 71 'B4': pagesizes.B4, 72 'B5': pagesizes.B5, 73 'B6': pagesizes.B6, 74 'ELEVENSEVENTEEN': pagesizes.ELEVENSEVENTEEN, 75 'LEGAL': pagesizes.LEGAL, 76 'LETTER': pagesizes.LETTER 77 } 78 try: 79 return sizes[size] 80 except: 81 raise ValueError, "%s not in list of page sizes" % size
82 83
84 -def draw_box((x1, y1), (x2, y2), 85 color=colors.lightgreen, border=None, colour=None, 86 **kwargs):
87 """ draw_box(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4), 88 color=colors.lightgreen) 89 90 o (x1,y1) and (x2,y2) Co-ordinates for opposite corners of the box 91 92 o color /colour The color for the box 93 (colour takes priority over color) 94 95 o border Border color for the box 96 97 Returns a closed path object, beginning at (x1,y1) going round 98 the four points in order, and filling with the passed color. 99 """ 100 #Let the UK spelling (colour) override the USA spelling (color) 101 if colour is not None: 102 color = colour 103 del colour 104 105 if not isinstance(color, colors.Color): 106 raise ValueError("Invalid color %s" % repr(color)) 107 108 if color == colors.white and border is None: # Force black border on 109 strokecolor = colors.black # white boxes with 110 elif border is None: # undefined border, else 111 strokecolor = color # use fill color 112 elif border is not None: 113 if not isinstance(border, colors.Color): 114 raise ValueError("Invalid border color %s" % repr(border)) 115 strokecolor = border 116 117 x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2) 118 return Polygon([x1, y1, x2, y1, x2, y2, x1, y2], 119 strokeColor=strokecolor, 120 fillColor=color, 121 strokewidth=0)
122
123 -def draw_polygon(list_of_points, 124 color=colors.lightgreen, border=None, colour=None, 125 **kwargs):
126 """ draw_polygon(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4) 127 colour=colors.lightgreen) 128 129 o list_of_point = list of (x,y) tuples for the corner coordinates 130 131 o colour The colour for the box 132 133 Returns a closed path object, beginning at (x1,y1) going round 134 the four points in order, and filling with the passed colour. 135 """ 136 #Let the UK spelling (colour) override the USA spelling (color) 137 if colour is not None: 138 color = colour 139 del colour 140 141 if color == colors.white and border is None: # Force black border on 142 strokecolor = colors.black # white boxes with 143 elif border is None: # undefined border, else 144 strokecolor = color # use fill colour 145 elif border is not None: 146 strokecolor = border 147 148 xy_list = [] 149 for (x,y) in list_of_points: 150 xy_list.append(x) 151 xy_list.append(y) 152 153 return Polygon(xy_list, 154 strokeColor=strokecolor, 155 fillColor=color, 156 strokewidth=0)
157 158
159 -def draw_arrow((x1, y1), (x2, y2), color=colors.lightgreen, border=None, 160 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right', 161 colour=None, **kwargs):
162 """ Returns a closed path object representing an arrow enclosed by the 163 box with corners at {(x1,y1),(x2,y2)}, a shaft height 164 given by shaft_height_ratio (relative to box height), a head length 165 given by head_length_ratio (also relative to box height), and 166 an orientation that may be 'left' or 'right'. 167 """ 168 if shaft_height_ratio < 0 or 1 < shaft_height_ratio: 169 raise ValueError("Arrow shaft height ratio should be in range 0 to 1") 170 if head_length_ratio < 0: 171 raise ValueError("Arrow head length ratio should be positive") 172 173 #Let the UK spelling (colour) override the USA spelling (color) 174 if colour is not None: 175 color = colour 176 del colour 177 178 if color == colors.white and border is None: # Force black border on 179 strokecolor = colors.black # white boxes with 180 elif border is None: # undefined border, else 181 strokecolor = color # use fill colour 182 elif border is not None: 183 strokecolor = border 184 185 # Depending on the orientation, we define the bottom left (x1, y1) and 186 # top right (x2, y2) coordinates differently, but still draw the box 187 # using the same relative co-ordinates: 188 xmin, ymin = min(x1, x2), min(y1, y2) 189 xmax, ymax = max(x1, x2), max(y1, y2) 190 if orientation == 'right': 191 x1, x2, y1, y2 = xmin, xmax, ymin, ymax 192 elif orientation == 'left': 193 x1, x2, y1, y2 = xmax, xmin, ymin, ymax 194 else: 195 raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \ 196 % repr(orientation)) 197 198 # We define boxheight and boxwidth accordingly, and calculate the shaft 199 # height from these. We also ensure that the maximum head length is 200 # the width of the box enclosure 201 boxheight = y2-y1 202 boxwidth = x2-x1 203 shaftheight = boxheight*shaft_height_ratio 204 headlength = min(abs(boxheight)*head_length_ratio, abs(boxwidth)) 205 if boxwidth < 0: 206 headlength *= -1 #reverse it 207 208 209 shafttop = 0.5*(boxheight+shaftheight) 210 shaftbase = boxheight-shafttop 211 headbase = boxwidth-headlength 212 midheight = 0.5*boxheight 213 return Polygon([x1, y1+shafttop, 214 x1+headbase, y1+shafttop, 215 x1+headbase, y2, 216 x2, y1+midheight, 217 x1+headbase, y1, 218 x1+headbase, y1+shaftbase, 219 x1, y1+shaftbase], 220 strokeColor=strokecolor, 221 #strokeWidth=max(1, int(boxheight/40.)), 222 strokeWidth=1, 223 #default is mitre/miter which can stick out too much: 224 strokeLineJoin=1, #1=round 225 fillColor=color)
226
227 -def angle2trig(theta):
228 """ angle2trig(angle) 229 230 o theta Angle in degrees, counter clockwise from horizontal 231 232 Returns a representation of the passed angle in a format suitable 233 for ReportLab rotations (i.e. cos(theta), sin(theta), -sin(theta), 234 cos(theta) tuple) 235 """ 236 c = cos(theta * pi / 180) 237 s = sin(theta * pi / 180) 238 return(c, s, -s, c) # Vector for rotating point around an origin
239 240
241 -def intermediate_points(start, end, graph_data):
242 """ intermediate_points(start, end, graph_data) 243 244 o graph_data 245 246 o start 247 248 o end 249 250 Returns a list of (start, end, value) tuples describing the passed 251 graph data as 'bins' between position midpoints. 252 """ 253 #print start, end, len(graph_data) 254 newdata = [] # data in form (X0, X1, val) 255 # add first block 256 newdata.append((start, graph_data[0][0]+(graph_data[1][0]-graph_data[0][0])/2., 257 graph_data[0][1])) 258 # add middle set 259 for index in xrange(1, len(graph_data)-1): 260 lastxval, lastyval = graph_data[index-1] 261 xval, yval = graph_data[index] 262 nextxval, nextyval = graph_data[index+1] 263 newdata.append( (lastxval+(xval-lastxval)/2., 264 xval+(nextxval-xval)/2., yval) ) 265 # add last block 266 newdata.append( (xval+(nextxval-xval)/2., 267 end, graph_data[-1][1]) ) 268 #print newdata[-1] 269 #print newdata 270 return newdata
271 272 ################################################################################ 273 # CLASSES 274 ################################################################################ 275
276 -class AbstractDrawer:
277 """ AbstractDrawer 278 279 Provides: 280 281 Methods: 282 283 o __init__(self, parent, pagesize='A3', orientation='landscape', 284 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 285 start=None, end=None, tracklines=0) Called on instantiation 286 287 o set_page_size(self, pagesize, orientation) Set the page size to the 288 passed size and orientation 289 290 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the 291 page 292 293 o set_bounds(self, start, end) Set the bounds for the elements to be 294 drawn 295 296 o is_in_bounds(self, value) Returns a boolean for whether the position 297 is actually to be drawn 298 299 o __len__(self) Returns the length of sequence that will be drawn 300 301 Attributes: 302 303 o tracklines Boolean for whether to draw lines dilineating tracks 304 305 o pagesize Tuple describing the size of the page in pixels 306 307 o x0 Float X co-ord for leftmost point of drawable area 308 309 o xlim Float X co-ord for rightmost point of drawable area 310 311 o y0 Float Y co-ord for lowest point of drawable area 312 313 o ylim Float Y co-ord for topmost point of drawable area 314 315 o pagewidth Float pixel width of drawable area 316 317 o pageheight Float pixel height of drawable area 318 319 o xcenter Float X co-ord of center of drawable area 320 321 o ycenter Float Y co-ord of center of drawable area 322 323 o start Int, base to start drawing from 324 325 o end Int, base to stop drawing at 326 327 o length Size of sequence to be drawn 328 329 """
330 - def __init__(self, parent, pagesize='A3', orientation='landscape', 331 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 332 start=None, end=None, tracklines=0):
333 """ __init__(self, parent, pagesize='A3', orientation='landscape', 334 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 335 start=None, end=None, tracklines=0) 336 337 o parent Diagram object containing the data that the drawer 338 draws 339 340 o pagesize String describing the ISO size of the image, or a tuple 341 of pixels 342 343 o orientation String describing the required orientation of the 344 final drawing ('landscape' or 'portrait') 345 346 o x Float (0->1) describing the relative size of the X 347 margins to the page 348 349 o y Float (0->1) describing the relative size of the Y 350 margins to the page 351 352 o xl Float (0->1) describing the relative size of the left X 353 margin to the page (overrides x) 354 355 o xl Float (0->1) describing the relative size of the left X 356 margin to the page (overrides x) 357 358 o xr Float (0->1) describing the relative size of the right X 359 margin to the page (overrides x) 360 361 o yt Float (0->1) describing the relative size of the top Y 362 margin to the page (overrides y) 363 364 o yb Float (0->1) describing the relative size of the lower Y 365 margin to the page (overrides y) 366 367 o start Int, the position to begin drawing the diagram at 368 369 o end Int, the position to stop drawing the diagram at 370 371 o tracklines Boolean flag to show (or not) lines delineating tracks 372 on the diagram 373 """ 374 self._parent = parent # The calling Diagram object 375 376 # Perform 'administrative' tasks of setting up the page 377 self.set_page_size(pagesize, orientation) # Set drawing size 378 self.set_margins(x, y, xl, xr, yt, yb) # Set page margins 379 self.set_bounds(start, end) # Set limits on what will be drawn 380 self.tracklines = tracklines # Set flags
381
382 - def _set_xcentre(self, value):
383 self.xcenter = value
384 xcentre = property(fget = lambda self : self.xcenter, 385 fset = _set_xcentre, 386 doc="Backwards compatible alias for xcenter (OBSOLETE)") 387
388 - def _set_ycentre(self, value):
389 self.ycenter = value
390 ycentre = property(fget = lambda self : self.ycenter, 391 fset = _set_ycentre, 392 doc="Backwards compatible alias for ycenter (OBSOLETE)") 393
394 - def set_page_size(self, pagesize, orientation):
395 """ set_page_size(self, pagesize, orientation) 396 397 o pagesize Size of the output image, a tuple of pixels (width, 398 height, or a string in the reportlab.lib.pagesizes 399 set of ISO sizes. 400 401 o orientation String: 'landscape' or 'portrait' 402 403 Set the size of the drawing 404 """ 405 if type(pagesize) == type('a'): # A string, so translate 406 pagesize = page_sizes(pagesize) 407 elif type(pagesize) == type((1,2)): # A tuple, so don't translate 408 pagesize = pagesize 409 else: 410 raise ValueError, "Page size %s not recognised" % pagesize 411 shortside, longside = min(pagesize), max(pagesize) 412 413 orientation = orientation.lower() 414 if orientation not in ('landscape', 'portrait'): 415 raise ValueError, "Orientation %s not recognised" % orientation 416 if orientation == 'landscape': 417 self.pagesize = (longside, shortside) 418 else: 419 self.pagesize = (shortside, longside)
420 421
422 - def set_margins(self, x, y, xl, xr, yt, yb):
423 """ set_margins(self, x, y, xl, xr, yt, yb) 424 425 o x Float(0->1), Absolute X margin as % of page 426 427 o y Float(0->1), Absolute Y margin as % of page 428 429 o xl Float(0->1), Left X margin as % of page 430 431 o xr Float(0->1), Right X margin as % of page 432 433 o yt Float(0->1), Top Y margin as % of page 434 435 o yb Float(0->1), Bottom Y margin as % of page 436 437 Set the page margins as proportions of the page 0->1, and also 438 set the page limits x0, y0 and xlim, ylim, and page center 439 xorigin, yorigin, as well as overall page width and height 440 """ 441 # Set left, right, top and bottom margins 442 xmargin_l = xl or x 443 xmargin_r = xr or x 444 ymargin_top = yt or y 445 ymargin_btm = yb or y 446 447 # Set page limits, center and height/width 448 self.x0, self.y0 = self.pagesize[0]*xmargin_l, self.pagesize[1]*ymargin_btm 449 self.xlim, self.ylim = self.pagesize[0]*(1-xmargin_r), self.pagesize[1]*(1-ymargin_top) 450 self.pagewidth = self.xlim-self.x0 451 self.pageheight = self.ylim-self.y0 452 self.xcenter, self.ycenter = self.x0+self.pagewidth/2., self.y0+self.pageheight/2.
453 454
455 - def set_bounds(self, start, end):
456 """ set_bounds(self, start, end) 457 458 o start The first base (or feature mark) to draw from 459 460 o end The last base (or feature mark) to draw to 461 462 Sets start and end points for the drawing as a whole 463 """ 464 low, high = self._parent.range() # Extent of tracks 465 466 if start > end: 467 start, end = end, start 468 469 if start is None or start < 1: # Check validity of passed args and 470 start = 1 # default to 1 471 if end is None or end < 1: 472 end = high + 1 # default to track range top limit 473 474 self.start, self.end = int(start), int(end) 475 self.length = self.end - self.start + 1
476 477
478 - def is_in_bounds(self, value):
479 """ is_in_bounds(self, value) 480 481 o value A base position 482 483 Returns 1 if the value is within the region selected for drawing 484 """ 485 if value >= self.start and value <= self.end: 486 return 1 487 return 0
488 489
490 - def __len__(self):
491 """ __len__(self) 492 493 Returns the length of the region to be drawn 494 """ 495 return self.length
496