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