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(point1, point2, 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 point1, point2 Co-ordinates for opposite corners of the box 91 (x,y tuples) 92 93 o color /colour The color for the box 94 (colour takes priority over color) 95 96 o border Border color for the box 97 98 Returns a closed path object, beginning at (x1,y1) going round 99 the four points in order, and filling with the passed color. 100 """ 101 x1, y1 = point1 102 x2, y2 = point2 103 104 #Let the UK spelling (colour) override the USA spelling (color) 105 if colour is not None: 106 color = colour 107 del colour 108 109 if not isinstance(color, colors.Color): 110 raise ValueError("Invalid color %s" % repr(color)) 111 112 if color == colors.white and border is None: # Force black border on 113 strokecolor = colors.black # white boxes with 114 elif border is None: # undefined border, else 115 strokecolor = color # use fill color 116 elif border is not None: 117 if not isinstance(border, colors.Color): 118 raise ValueError("Invalid border color %s" % repr(border)) 119 strokecolor = border 120 121 x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2) 122 return Polygon([x1, y1, x2, y1, x2, y2, x1, y2], 123 strokeColor=strokecolor, 124 fillColor=color, 125 strokewidth=0, 126 **kwargs)
127 128
129 -def draw_polygon(list_of_points, 130 color=colors.lightgreen, border=None, colour=None, 131 **kwargs):
132 """ draw_polygon(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4) 133 colour=colors.lightgreen) 134 135 o list_of_point = list of (x,y) tuples for the corner coordinates 136 137 o colour The colour for the box 138 139 Returns a closed path object, beginning at (x1,y1) going round 140 the four points in order, and filling with the passed colour. 141 """ 142 #Let the UK spelling (colour) override the USA spelling (color) 143 if colour is not None: 144 color = colour 145 del colour 146 147 if color == colors.white and border is None: # Force black border on 148 strokecolor = colors.black # white boxes with 149 elif border is None: # undefined border, else 150 strokecolor = color # use fill colour 151 elif border is not None: 152 strokecolor = border 153 154 xy_list = [] 155 for (x,y) in list_of_points: 156 xy_list.append(x) 157 xy_list.append(y) 158 159 return Polygon(xy_list, 160 strokeColor=strokecolor, 161 fillColor=color, 162 strokewidth=0, 163 **kwargs)
164 165
166 -def draw_arrow(point1, point2, color=colors.lightgreen, border=None, 167 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right', 168 colour=None, **kwargs):
169 """ Returns a closed path object representing an arrow enclosed by the 170 box with corners at {point1=(x1,y1), point2=(x2,y2)}, a shaft height 171 given by shaft_height_ratio (relative to box height), a head length 172 given by head_length_ratio (also relative to box height), and 173 an orientation that may be 'left' or 'right'. 174 """ 175 x1, y1 = point1 176 x2, y2 = point2 177 178 if shaft_height_ratio < 0 or 1 < shaft_height_ratio: 179 raise ValueError("Arrow shaft height ratio should be in range 0 to 1") 180 if head_length_ratio < 0: 181 raise ValueError("Arrow head length ratio should be positive") 182 183 #Let the UK spelling (colour) override the USA spelling (color) 184 if colour is not None: 185 color = colour 186 del colour 187 188 if color == colors.white and border is None: # Force black border on 189 strokecolor = colors.black # white boxes with 190 elif border is None: # undefined border, else 191 strokecolor = color # use fill colour 192 elif border is not None: 193 strokecolor = border 194 195 # Depending on the orientation, we define the bottom left (x1, y1) and 196 # top right (x2, y2) coordinates differently, but still draw the box 197 # using the same relative co-ordinates: 198 xmin, ymin = min(x1, x2), min(y1, y2) 199 xmax, ymax = max(x1, x2), max(y1, y2) 200 if orientation == 'right': 201 x1, x2, y1, y2 = xmin, xmax, ymin, ymax 202 elif orientation == 'left': 203 x1, x2, y1, y2 = xmax, xmin, ymin, ymax 204 else: 205 raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \ 206 % repr(orientation)) 207 208 # We define boxheight and boxwidth accordingly, and calculate the shaft 209 # height from these. We also ensure that the maximum head length is 210 # the width of the box enclosure 211 boxheight = y2-y1 212 boxwidth = x2-x1 213 shaftheight = boxheight*shaft_height_ratio 214 headlength = min(abs(boxheight)*head_length_ratio, abs(boxwidth)) 215 if boxwidth < 0: 216 headlength *= -1 #reverse it 217 218 219 shafttop = 0.5*(boxheight+shaftheight) 220 shaftbase = boxheight-shafttop 221 headbase = boxwidth-headlength 222 midheight = 0.5*boxheight 223 return Polygon([x1, y1+shafttop, 224 x1+headbase, y1+shafttop, 225 x1+headbase, y2, 226 x2, y1+midheight, 227 x1+headbase, y1, 228 x1+headbase, y1+shaftbase, 229 x1, y1+shaftbase], 230 strokeColor=strokecolor, 231 #strokeWidth=max(1, int(boxheight/40.)), 232 strokeWidth=1, 233 #default is mitre/miter which can stick out too much: 234 strokeLineJoin=1, #1=round 235 fillColor=color, 236 **kwargs)
237
238 -def angle2trig(theta):
239 """ angle2trig(angle) 240 241 o theta Angle in degrees, counter clockwise from horizontal 242 243 Returns a representation of the passed angle in a format suitable 244 for ReportLab rotations (i.e. cos(theta), sin(theta), -sin(theta), 245 cos(theta) tuple) 246 """ 247 c = cos(theta * pi / 180) 248 s = sin(theta * pi / 180) 249 return(c, s, -s, c) # Vector for rotating point around an origin
250 251
252 -def intermediate_points(start, end, graph_data):
253 """ intermediate_points(start, end, graph_data) 254 255 o graph_data 256 257 o start 258 259 o end 260 261 Returns a list of (start, end, value) tuples describing the passed 262 graph data as 'bins' between position midpoints. 263 """ 264 #print start, end, len(graph_data) 265 newdata = [] # data in form (X0, X1, val) 266 # add first block 267 newdata.append((start, graph_data[0][0]+(graph_data[1][0]-graph_data[0][0])/2., 268 graph_data[0][1])) 269 # add middle set 270 for index in xrange(1, len(graph_data)-1): 271 lastxval, lastyval = graph_data[index-1] 272 xval, yval = graph_data[index] 273 nextxval, nextyval = graph_data[index+1] 274 newdata.append( (lastxval+(xval-lastxval)/2., 275 xval+(nextxval-xval)/2., yval) ) 276 # add last block 277 newdata.append( (xval+(nextxval-xval)/2., 278 end, graph_data[-1][1]) ) 279 #print newdata[-1] 280 #print newdata 281 return newdata
282 283 ################################################################################ 284 # CLASSES 285 ################################################################################ 286
287 -class AbstractDrawer:
288 """ AbstractDrawer 289 290 Provides: 291 292 Methods: 293 294 o __init__(self, parent, pagesize='A3', orientation='landscape', 295 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 296 start=None, end=None, tracklines=0) Called on instantiation 297 298 o set_page_size(self, pagesize, orientation) Set the page size to the 299 passed size and orientation 300 301 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the 302 page 303 304 o set_bounds(self, start, end) Set the bounds for the elements to be 305 drawn 306 307 o is_in_bounds(self, value) Returns a boolean for whether the position 308 is actually to be drawn 309 310 o __len__(self) Returns the length of sequence that will be drawn 311 312 Attributes: 313 314 o tracklines Boolean for whether to draw lines dilineating tracks 315 316 o pagesize Tuple describing the size of the page in pixels 317 318 o x0 Float X co-ord for leftmost point of drawable area 319 320 o xlim Float X co-ord for rightmost point of drawable area 321 322 o y0 Float Y co-ord for lowest point of drawable area 323 324 o ylim Float Y co-ord for topmost point of drawable area 325 326 o pagewidth Float pixel width of drawable area 327 328 o pageheight Float pixel height of drawable area 329 330 o xcenter Float X co-ord of center of drawable area 331 332 o ycenter Float Y co-ord of center of drawable area 333 334 o start Int, base to start drawing from 335 336 o end Int, base to stop drawing at 337 338 o length Size of sequence to be drawn 339 340 """
341 - def __init__(self, parent, pagesize='A3', orientation='landscape', 342 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 343 start=None, end=None, tracklines=0):
344 """ __init__(self, parent, pagesize='A3', orientation='landscape', 345 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 346 start=None, end=None, tracklines=0) 347 348 o parent Diagram object containing the data that the drawer 349 draws 350 351 o pagesize String describing the ISO size of the image, or a tuple 352 of pixels 353 354 o orientation String describing the required orientation of the 355 final drawing ('landscape' or 'portrait') 356 357 o x Float (0->1) describing the relative size of the X 358 margins to the page 359 360 o y Float (0->1) describing the relative size of the Y 361 margins to the page 362 363 o xl Float (0->1) describing the relative size of the left X 364 margin to the page (overrides x) 365 366 o xl Float (0->1) describing the relative size of the left X 367 margin to the page (overrides x) 368 369 o xr Float (0->1) describing the relative size of the right X 370 margin to the page (overrides x) 371 372 o yt Float (0->1) describing the relative size of the top Y 373 margin to the page (overrides y) 374 375 o yb Float (0->1) describing the relative size of the lower Y 376 margin to the page (overrides y) 377 378 o start Int, the position to begin drawing the diagram at 379 380 o end Int, the position to stop drawing the diagram at 381 382 o tracklines Boolean flag to show (or not) lines delineating tracks 383 on the diagram 384 """ 385 self._parent = parent # The calling Diagram object 386 387 # Perform 'administrative' tasks of setting up the page 388 self.set_page_size(pagesize, orientation) # Set drawing size 389 self.set_margins(x, y, xl, xr, yt, yb) # Set page margins 390 self.set_bounds(start, end) # Set limits on what will be drawn 391 self.tracklines = tracklines # Set flags
392
393 - def _set_xcentre(self, value):
394 import warnings 395 import Bio 396 warnings.warn("The _set_xcentre method and .xcentre attribute are deprecated; please use the .xcenter attribute instead", Bio.BiopythonDeprecationWarning) 397 self.xcenter = value
398 xcentre = property(fget = lambda self : self.xcenter, 399 fset = _set_xcentre, 400 doc="Backwards compatible alias for xcenter (DEPRECATED)") 401
402 - def _set_ycentre(self, value):
403 import warnings 404 import Bio 405 warnings.warn("The _set_ycentre method and .xcentre attribute are deprecated; please use the .ycenter attribute instead", Bio.BiopythonDeprecationWarning) 406 self.ycenter = value
407 ycentre = property(fget = lambda self : self.ycenter, 408 fset = _set_ycentre, 409 doc="Backwards compatible alias for ycenter (DEPRECATED)") 410
411 - def set_page_size(self, pagesize, orientation):
412 """ set_page_size(self, pagesize, orientation) 413 414 o pagesize Size of the output image, a tuple of pixels (width, 415 height, or a string in the reportlab.lib.pagesizes 416 set of ISO sizes. 417 418 o orientation String: 'landscape' or 'portrait' 419 420 Set the size of the drawing 421 """ 422 if type(pagesize) == type('a'): # A string, so translate 423 pagesize = page_sizes(pagesize) 424 elif type(pagesize) == type((1,2)): # A tuple, so don't translate 425 pagesize = pagesize 426 else: 427 raise ValueError, "Page size %s not recognised" % pagesize 428 shortside, longside = min(pagesize), max(pagesize) 429 430 orientation = orientation.lower() 431 if orientation not in ('landscape', 'portrait'): 432 raise ValueError, "Orientation %s not recognised" % orientation 433 if orientation == 'landscape': 434 self.pagesize = (longside, shortside) 435 else: 436 self.pagesize = (shortside, longside)
437 438
439 - def set_margins(self, x, y, xl, xr, yt, yb):
440 """ set_margins(self, x, y, xl, xr, yt, yb) 441 442 o x Float(0->1), Absolute X margin as % of page 443 444 o y Float(0->1), Absolute Y margin as % of page 445 446 o xl Float(0->1), Left X margin as % of page 447 448 o xr Float(0->1), Right X margin as % of page 449 450 o yt Float(0->1), Top Y margin as % of page 451 452 o yb Float(0->1), Bottom Y margin as % of page 453 454 Set the page margins as proportions of the page 0->1, and also 455 set the page limits x0, y0 and xlim, ylim, and page center 456 xorigin, yorigin, as well as overall page width and height 457 """ 458 # Set left, right, top and bottom margins 459 xmargin_l = xl or x 460 xmargin_r = xr or x 461 ymargin_top = yt or y 462 ymargin_btm = yb or y 463 464 # Set page limits, center and height/width 465 self.x0, self.y0 = self.pagesize[0]*xmargin_l, self.pagesize[1]*ymargin_btm 466 self.xlim, self.ylim = self.pagesize[0]*(1-xmargin_r), self.pagesize[1]*(1-ymargin_top) 467 self.pagewidth = self.xlim-self.x0 468 self.pageheight = self.ylim-self.y0 469 self.xcenter, self.ycenter = self.x0+self.pagewidth/2., self.y0+self.pageheight/2.
470 471
472 - def set_bounds(self, start, end):
473 """ set_bounds(self, start, end) 474 475 o start The first base (or feature mark) to draw from 476 477 o end The last base (or feature mark) to draw to 478 479 Sets start and end points for the drawing as a whole 480 """ 481 low, high = self._parent.range() # Extent of tracks 482 483 if start is not None and end is not None and start > end: 484 start, end = end, start 485 486 if start is None or start < 1: # Check validity of passed args and 487 start = 1 # default to 1 488 if end is None or end < 1: 489 end = high + 1 # default to track range top limit 490 491 self.start, self.end = int(start), int(end) 492 self.length = self.end - self.start + 1
493 494
495 - def is_in_bounds(self, value):
496 """ is_in_bounds(self, value) 497 498 o value A base position 499 500 Returns 1 if the value is within the region selected for drawing 501 """ 502 if value >= self.start and value <= self.end: 503 return 1 504 return 0
505 506
507 - def __len__(self):
508 """ __len__(self) 509 510 Returns the length of the region to be drawn 511 """ 512 return self.length
513