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