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