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

Source Code for Module Bio.Graphics.GenomeDiagram._LinearDrawer

   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  """ LinearDrawer module 
  13   
  14      Provides: 
  15   
  16      o LinearDrawer -  Drawing object for linear diagrams 
  17   
  18      For drawing capabilities, this module uses reportlab to draw and write 
  19      the diagram: 
  20   
  21      http://www.reportlab.com 
  22   
  23      For dealing with biological information, the package expects BioPython 
  24      objects: 
  25   
  26      http://www.biopython.org 
  27  """ 
  28   
  29  # ReportLab imports 
  30  from reportlab.graphics.shapes import * 
  31  from reportlab.lib import colors 
  32   
  33  # GenomeDiagram imports 
  34  from _AbstractDrawer import AbstractDrawer, draw_box, draw_arrow 
  35  from _AbstractDrawer import intermediate_points, angle2trig 
  36  from _FeatureSet import FeatureSet 
  37  from _GraphSet import GraphSet 
  38   
  39  from math import ceil 
  40   
41 -class LinearDrawer(AbstractDrawer):
42 """ LinearDrawer(AbstractDrawer) 43 44 Inherits from: 45 46 o AbstractDrawer 47 48 Provides: 49 50 Methods: 51 52 o __init__(self, parent=None, pagesize='A3', orientation='landscape', 53 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 54 start=None, end=None, tracklines=0, fragments=10, 55 fragment_size=0.9, track_size=0.75) Called on instantiation 56 57 o set_page_size(self, pagesize, orientation) Set the page size to the 58 passed size and orientation 59 60 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the 61 page 62 63 o set_bounds(self, start, end) Set the bounds for the elements to be 64 drawn 65 66 o is_in_bounds(self, value) Returns a boolean for whether the position 67 is actually to be drawn 68 69 o __len__(self) Returns the length of sequence that will be drawn 70 71 72 o draw(self) Place the drawing elements on the diagram 73 74 o init_fragments(self) Calculate information 75 about sequence fragment locations on the drawing 76 77 o set_track_heights(self) Calculate information about the offset of 78 each track from the fragment base 79 80 o draw_test_tracks(self) Add lines demarcating each track to the 81 drawing 82 83 o draw_track(self, track) Return the contents of the passed track as 84 drawing elements 85 86 o draw_scale(self, track) Return a scale for the passed track as 87 drawing elements 88 89 o draw_tick(self, tickpos, ctr, ticklen, track, draw_label) Return a 90 tick line and possibly a label 91 92 o draw_greytrack(self, track) Return a grey background and superposed 93 label for the passed track as drawing 94 elements 95 96 o draw_feature_set(self, set) Return the features in the passed set as 97 drawing elements 98 99 o draw_feature(self, feature) Return a single feature as drawing 100 elements 101 102 o get_feature_sigil(self, feature, x0, x1, fragment) Return a single 103 feature as its sigil in drawing elements 104 105 o draw_graph_set(self, set) Return the data in a set of graphs as 106 drawing elements 107 108 o draw_line_graph(self, graph) Return the data in a graph as a line 109 graph in drawing elements 110 111 o draw_heat_graph(self, graph) Return the data in a graph as a heat 112 graph in drawing elements 113 114 o draw_bar_graph(self, graph) Return the data in a graph as a bar 115 graph in drawing elements 116 117 o canvas_location(self, base) Return the fragment, and the offset from 118 the left margin, of a passed position 119 in the sequence, on the diagram. 120 121 Attributes: 122 123 o tracklines Boolean for whether to draw lines dilineating tracks 124 125 o pagesize Tuple describing the size of the page in pixels 126 127 o x0 Float X co-ord for leftmost point of drawable area 128 129 o xlim Float X co-ord for rightmost point of drawable area 130 131 o y0 Float Y co-ord for lowest point of drawable area 132 133 o ylim Float Y co-ord for topmost point of drawable area 134 135 o pagewidth Float pixel width of drawable area 136 137 o pageheight Float pixel height of drawable area 138 139 o xcenter Float X co-ord of center of drawable area 140 141 o ycenter Float Y co-ord of center of drawable area 142 143 o start Int, base to start drawing from 144 145 o end Int, base to stop drawing at 146 147 o length Int, size of sequence to be drawn 148 149 o fragments Int, number of fragments into which to divide the 150 drawn sequence 151 152 o fragment_size Float (0->1) the proportion of the fragment height to 153 draw in 154 155 o track_size Float (0->1) the proportion of the track height to 156 draw in 157 158 o drawing Drawing canvas 159 160 o drawn_tracks List of ints denoting which tracks are to be drawn 161 162 o current_track_level Int denoting which track is currently being 163 drawn 164 165 o fragment_height Float total fragment height in pixels 166 167 o fragment_bases Int total fragment length in bases 168 169 o fragment_lines Dictionary of top and bottom y-coords of fragment, 170 keyed by fragment number 171 172 o fragment_limits Dictionary of start and end bases of each fragment, 173 keyed by fragment number 174 175 o track_offsets Dictionary of number of pixels that each track top, 176 center and bottom is offset from the base of a 177 fragment, keyed by track 178 179 """
180 - def __init__(self, parent=None, pagesize='A3', orientation='landscape', 181 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 182 start=None, end=None, tracklines=0, fragments=10, 183 fragment_size=0.9, track_size=0.75):
184 """ __init__(self, parent, pagesize='A3', orientation='landscape', 185 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 186 start=None, end=None, tracklines=0, fragments=10, 187 fragment_size=0.9, track_size=0.75) 188 189 o parent Diagram object containing the data that the drawer 190 draws 191 192 o pagesize String describing the ISO size of the image, or a tuple 193 of pixels 194 195 o orientation String describing the required orientation of the 196 final drawing ('landscape' or 'portrait') 197 198 o x Float (0->1) describing the relative size of the X 199 margins to the page 200 201 o y Float (0->1) describing the relative size of the Y 202 margins to the page 203 204 o xl Float (0->1) describing the relative size of the left X 205 margin to the page (overrides x) 206 207 o xl Float (0->1) describing the relative size of the left X 208 margin to the page (overrides x) 209 210 o xr Float (0->1) describing the relative size of the right X 211 margin to the page (overrides x) 212 213 o yt Float (0->1) describing the relative size of the top Y 214 margin to the page (overrides y) 215 216 o yb Float (0->1) describing the relative size of the lower Y 217 margin to the page (overrides y) 218 219 o start Int, the position to begin drawing the diagram at 220 221 o end Int, the position to stop drawing the diagram at 222 223 o tracklines Boolean flag to show (or not) lines delineating tracks 224 on the diagram 225 226 o fragments Int, the number of equal fragments into which the 227 sequence should be divided for drawing 228 229 o fragment_size Float(0->1) The proportion of the available height 230 for the fragment that should be taken up in drawing 231 232 o track_size The proportion of the available track height that 233 should be taken up in drawing 234 235 """ 236 # Use the superclass' instantiation method 237 AbstractDrawer.__init__(self, parent, pagesize, orientation, 238 x, y, xl, xr, yt, yb, start, end, 239 tracklines) 240 241 # Useful measurements on the page 242 self.fragments = fragments 243 self.fragment_size = fragment_size 244 self.track_size = track_size
245 246
247 - def draw(self):
248 """ draw(self) 249 250 Draw a linear diagram of the data in the parent Diagram object 251 """ 252 # Instantiate the drawing canvas 253 self.drawing = Drawing(self.pagesize[0], self.pagesize[1]) 254 255 feature_elements = [] # holds feature elements 256 feature_labels = [] # holds feature labels 257 greytrack_bgs = [] # holds track background 258 greytrack_labels = [] # holds track foreground labels 259 scale_axes = [] # holds scale axes 260 scale_labels = [] # holds scale axis labels 261 262 # Get the tracks to be drawn 263 self.drawn_tracks = self._parent.get_drawn_levels() 264 265 # Set fragment and track sizes 266 self.init_fragments() 267 self.set_track_heights() 268 269 # Go through each track in the parent (if it is to be drawn) one by 270 # one and collate the data as drawing elements 271 for track_level in self.drawn_tracks: # only use tracks to be drawn 272 self.current_track_level = track_level # establish track level 273 track = self._parent[track_level] # get the track at that level 274 gbgs, glabels = self.draw_greytrack(track) # get greytrack elements 275 greytrack_bgs.append(gbgs) 276 greytrack_labels.append(glabels) 277 features, flabels = self.draw_track(track) # get feature and graph elements 278 feature_elements.append(features) 279 feature_labels.append(flabels) 280 if track.scale: 281 axes, slabels = self.draw_scale(track) # get scale elements 282 scale_axes.append(axes) 283 scale_labels.append(slabels) 284 285 # Groups listed in order of addition to page (from back to front) 286 # Draw track backgrounds 287 # Draw features and graphs 288 # Draw scale axes 289 # Draw scale labels 290 # Draw feature labels 291 # Draw track labels 292 element_groups = [greytrack_bgs, feature_elements, scale_axes, 293 scale_labels, feature_labels, greytrack_labels] 294 for element_group in element_groups: 295 for element_list in element_group: 296 [self.drawing.add(element) for element in element_list] 297 298 if self.tracklines: # Draw test tracks over top of diagram 299 self.draw_test_tracks()
300 301
302 - def init_fragments(self):
303 """ init_fragments(self) 304 305 Initialises useful values for calculating the positioning of 306 diagram elements 307 """ 308 # Set basic heights, lengths etc 309 self.fragment_height = 1.*self.pageheight/self.fragments # total fragment height in pixels 310 self.fragment_bases = ceil(1.*self.length/self.fragments) # fragment length in bases 311 312 # Key fragment base and top lines by fragment number 313 self.fragment_lines = {} # Holds bottom and top line locations of fragments, keyed by fragment number 314 fragment_crop = (1-self.fragment_size)/2 # No of pixels to crop the fragment 315 fragy = self.ylim # Holder for current absolute fragment base 316 for fragment in range(self.fragments): 317 fragtop = fragy-fragment_crop * self.fragment_height # top - crop 318 fragbtm = fragy-(1-fragment_crop) * self.fragment_height # bottom + crop 319 self.fragment_lines[fragment] = (fragbtm, fragtop) 320 fragy -= self.fragment_height # next fragment base 321 322 # Key base starts and ends for each fragment by fragment number 323 self.fragment_limits = {} # Holds first and last base positions in a fragment 324 fragment_step = self.fragment_bases # bases per fragment 325 fragment_count = 0 326 # Add start and end positions for each fragment to dictionary 327 for marker in range(int(self.start), int(self.end), int(fragment_step)): 328 self.fragment_limits[fragment_count] = (marker, marker+fragment_step) 329 fragment_count += 1
330 331
332 - def set_track_heights(self):
333 """ set_track_heights(self) 334 335 Since tracks may not be of identical heights, the bottom and top 336 offsets of each track relative to the fragment top and bottom is 337 stored in a dictionary - self.track_offsets, keyed by track number 338 """ 339 top_track = max(self.drawn_tracks) # The 'highest' track number to draw 340 341 trackunit_sum = 0 # Total number of 'units' for the tracks 342 trackunits = {} # The start and end units for each track, keyed by track number 343 heightholder = 0 # placeholder variable 344 for track in range(1, top_track+1): # for all track numbers to 'draw' 345 try: 346 trackheight = self._parent[track].height # Get track height 347 except: 348 trackheight = 1 # ...or default to 1 349 trackunit_sum += trackheight # increment total track unit height 350 trackunits[track] = (heightholder, heightholder+trackheight) 351 heightholder += trackheight # move to next height 352 trackunit_height = 1.*self.fragment_height*self.fragment_size/trackunit_sum 353 354 # Calculate top and bottom offsets for each track, relative to fragment 355 # base 356 track_offsets = {} # The offsets from fragment base for each track 357 track_crop = trackunit_height*(1-self.track_size)/2. # 'step back' in pixels 358 assert track_crop >= 0 359 for track in trackunits: 360 top = trackunits[track][1]*trackunit_height-track_crop # top offset 361 btm = trackunits[track][0]*trackunit_height+track_crop # bottom offset 362 ctr = btm+(top-btm)/2. # center offset 363 track_offsets[track] = (btm, ctr, top) 364 self.track_offsets = track_offsets
365 366
367 - def draw_test_tracks(self):
368 """ draw_test_tracks(self) 369 370 Draw red lines indicating the top and bottom of each fragment, 371 and blue ones indicating tracks to be drawn. 372 """ 373 # Add lines for each fragment 374 for fbtm, ftop in self.fragment_lines.values(): 375 self.drawing.add(Line(self.x0, ftop, self.xlim, ftop, 376 strokeColor=colors.red)) # top line 377 self.drawing.add(Line(self.x0, fbtm, self.xlim, fbtm, 378 strokeColor=colors.red)) # bottom line 379 380 # Add track lines for this fragment - but only for drawn tracks 381 for track in self.drawn_tracks: 382 trackbtm = fbtm + self.track_offsets[track][0] 383 trackctr = fbtm + self.track_offsets[track][1] 384 tracktop = fbtm + self.track_offsets[track][2] 385 self.drawing.add(Line(self.x0, tracktop, self.xlim, tracktop, 386 strokeColor=colors.blue)) # top line 387 self.drawing.add(Line(self.x0, trackctr, self.xlim, trackctr, 388 strokeColor=colors.green)) # center line 389 self.drawing.add(Line(self.x0, trackbtm, self.xlim, trackbtm, 390 strokeColor=colors.blue)) # bottom line
391 392
393 - def draw_track(self, track):
394 """ draw_track(self, track) -> ([element, element,...], [element, element,...]) 395 396 o track Track object 397 398 Returns a tuple (list of elements in the track, list of labels in 399 the track) 400 """ 401 track_elements = [] # Holds elements from features and graphs 402 track_labels = [] # Holds labels from features and graphs 403 404 # Distribution dictionary for dealing with different set types 405 set_methods = {FeatureSet: self.draw_feature_set, 406 GraphSet: self.draw_graph_set 407 } 408 409 for set in track.get_sets(): # Draw the feature or graph sets 410 elements, labels = set_methods[set.__class__](set) 411 track_elements += elements 412 track_labels += labels 413 return track_elements, track_labels
414 415
416 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
417 """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element) 418 419 o tickpos Int, position of the tick on the sequence 420 421 o ctr Float, Y co-ord of the center of the track 422 423 o ticklen How long to draw the tick 424 425 o track Track, the track the tick is drawn on 426 427 o draw_label Boolean, write the tick label? 428 429 Returns a drawing element that is the tick on the scale 430 """ 431 assert self.start <= tickpos and tickpos <= self.end, \ 432 "Tick at %i, but showing %i to %i" \ 433 % (tickpos, self.start, self.end) 434 fragment, tickx = self.canvas_location(tickpos) # Tick co-ordinates 435 assert fragment >=0, \ 436 "Fragment %i, tickpos %i" % (fragment, tickpos) 437 tctr = ctr + self.fragment_lines[fragment][0] # Center line of the track 438 tickx += self.x0 # Tick X co-ord 439 ticktop = tctr + ticklen # Y co-ord of tick top 440 tick = Line(tickx, tctr, tickx, ticktop, strokeColor=track.scale_color) 441 if draw_label: # Put tick position on as label 442 if track.scale_format == 'SInt': 443 if tickpos >= 1000000: 444 tickstring = str(tickpos//1000000) + " Mbp" 445 elif tickpos >= 1000: 446 tickstring = str(tickpos//1000) + " Kbp" 447 else: 448 tickstring = str(tickpos) 449 else: 450 tickstring = str(tickpos) 451 label = String(0, 0, tickstring, # Make label string 452 fontName=track.scale_font, 453 fontSize=track.scale_fontsize, 454 fillColor=track.scale_color) 455 labelgroup = Group(label) 456 rotation = angle2trig(track.scale_fontangle) 457 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 458 rotation[3], tickx, ticktop) 459 else: 460 labelgroup = None 461 return tick, labelgroup
462
463 - def draw_scale(self, track):
464 """ draw_scale(self, track) -> ([element, element,...], [element, element,...]) 465 466 o track Track object 467 468 Returns a tuple of (list of elements in the scale, list of labels 469 in the scale) 470 """ 471 scale_elements = [] # Holds axes and ticks 472 scale_labels = [] # Holds labels 473 474 if not track.scale: # No scale required, exit early 475 return [], [] 476 477 # Get track location 478 btm, ctr, top = self.track_offsets[self.current_track_level] 479 trackheight = (top-ctr) 480 481 # For each fragment, draw the scale for this track 482 for fragment in range(self.fragments): 483 tbtm = btm + self.fragment_lines[fragment][0] 484 tctr = ctr + self.fragment_lines[fragment][0] 485 ttop = top + self.fragment_lines[fragment][0] 486 # X-axis 487 if fragment == self.fragments - 1: 488 frag, x = self.canvas_location(self.end) 489 scale_elements.append(Line(self.x0, tctr, self.x0 + x, tctr, 490 strokeColor=track.scale_color)) 491 else: 492 scale_elements.append(Line(self.x0, tctr, self.xlim, tctr, 493 strokeColor=track.scale_color)) 494 # Y-axis 495 scale_elements.append(Line(self.x0, tbtm, self.x0, ttop, 496 strokeColor=track.scale_color)) 497 if track.scale_ticks: # Ticks are required on the scale 498 # Draw large ticks 499 #I want the ticks to be consistently positioned relative to 500 #the start of the sequence (position 0), not relative to the 501 #current viewpoint (self.start and self.end) 502 503 ticklen = track.scale_largeticks * trackheight 504 tickiterval = int(track.scale_largetick_interval) 505 #Note that we could just start the list of ticks using 506 #range(0,self.end,tickinterval) and the filter out the 507 #ones before self.start - but this seems wasteful. 508 #Using tickiterval * (self.start//tickiterval) is a shortcut. 509 largeticks = [pos for pos \ 510 in range(tickiterval * (self.start//tickiterval), 511 int(self.end), 512 tickiterval) \ 513 if pos >= self.start] 514 for tickpos in largeticks: 515 tick, label = self.draw_tick(tickpos, ctr, ticklen, 516 track, 517 track.scale_largetick_labels) 518 scale_elements.append(tick) 519 if label is not None: # If there's a label, add it 520 scale_labels.append(label) 521 # Draw small ticks 522 ticklen = track.scale_smallticks * trackheight 523 tickiterval = int(track.scale_smalltick_interval) 524 smallticks = [pos for pos \ 525 in range(tickiterval * (self.start//tickiterval), 526 int(self.end), 527 tickiterval) \ 528 if pos >= self.start] 529 for tickpos in smallticks: 530 tick, label = self.draw_tick(tickpos, ctr, ticklen, 531 track, 532 track.scale_smalltick_labels) 533 scale_elements.append(tick) 534 if label is not None: # If there's a label, add it 535 scale_labels.append(label) 536 537 # Check to see if the track contains a graph - if it does, get the 538 # minimum and maximum values, and put them on the scale Y-axis 539 if track.axis_labels: 540 for set in track.get_sets(): # Check all sets... 541 if set.__class__ is GraphSet: # ...for a graph set 542 graph_label_min = [] 543 graph_label_mid = [] 544 graph_label_max = [] 545 for graph in set.get_graphs(): 546 quartiles = graph.quartiles() 547 minval, maxval = quartiles[0], quartiles[4] 548 if graph.center is None: 549 midval = (maxval + minval)/2. 550 graph_label_min.append("%.3f" % minval) 551 graph_label_max.append("%.3f" % maxval) 552 else: 553 diff = max((graph.center-minval), 554 (maxval-graph.center)) 555 minval = graph.center-diff 556 maxval = graph.center+diff 557 midval = graph.center 558 graph_label_mid.append("%.3f" % midval) 559 graph_label_min.append("%.3f" % minval) 560 graph_label_max.append("%.3f" % maxval) 561 for fragment in range(self.fragments): # Add to all fragment axes 562 tbtm = btm + self.fragment_lines[fragment][0] 563 tctr = ctr + self.fragment_lines[fragment][0] 564 ttop = top + self.fragment_lines[fragment][0] 565 for val, pos in [(";".join(graph_label_min), tbtm), 566 (";".join(graph_label_max), ttop), 567 (";".join(graph_label_mid), tctr)]: 568 label = String(0, 0, val, 569 fontName=track.scale_font, 570 fontSize=track.scale_fontsize, 571 fillColor=track.scale_color) 572 labelgroup = Group(label) 573 rotation = angle2trig(track.scale_fontangle) 574 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 575 rotation[3], self.x0, pos) 576 scale_labels.append(labelgroup) 577 578 return scale_elements, scale_labels
579 580 581
582 - def draw_greytrack(self, track):
583 """ draw_greytrack(self) -> ([element, element,...], [element, element,...]) 584 585 o track Track object 586 587 Put in a grey background to the current track in all fragments, 588 if track specifies that we should 589 """ 590 greytrack_bgs = [] # Holds grey track backgrounds 591 greytrack_labels = [] # Holds grey foreground labels 592 593 if not track.greytrack: # No greytrack required, return early 594 return [], [] 595 596 # Get track location 597 btm, ctr, top = self.track_offsets[self.current_track_level] 598 599 # Add greytrack to all fragments for this track 600 for fragment in range(self.fragments): 601 tbtm = btm + self.fragment_lines[fragment][0] 602 tctr = ctr + self.fragment_lines[fragment][0] 603 ttop = top + self.fragment_lines[fragment][0] 604 box = draw_box((self.x0, tbtm), (self.xlim, ttop), # Grey track bg 605 colors.Color(0.96,0.96, 0.96)) # is just a box 606 greytrack_bgs.append(box) 607 608 if track.greytrack_labels: # If labels are required 609 labelstep = (self.pagewidth)/track.greytrack_labels # how far apart should they be? 610 label = String(0, 0, track.name, # label contents 611 fontName=track.greytrack_font, 612 fontSize=track.greytrack_fontsize, 613 fillColor=track.greytrack_fontcolor) 614 # Create a new labelgroup at each position the label is required 615 for x in range(int(self.x0), int(self.xlim), int(labelstep)): 616 labelgroup = Group(label) 617 rotation = angle2trig(track.greytrack_font_rotation) 618 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 619 rotation[3], x, tbtm) 620 if not self.xlim-x <= labelstep: # Don't overlap the end of the track 621 greytrack_labels.append(labelgroup) 622 623 return greytrack_bgs, greytrack_labels
624 625
626 - def draw_feature_set(self, set):
627 """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...]) 628 629 o set FeatureSet object 630 631 Returns a tuple (list of elements describing features, list of 632 labels for elements) 633 """ 634 #print 'draw feature set' 635 feature_elements = [] # Holds diagram elements belonging to the features 636 label_elements = [] # Holds diagram elements belonging to feature labels 637 638 # Collect all the elements for the feature set 639 for feature in set.get_features(): 640 if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end): 641 features, labels = self.draw_feature(feature) # get elements and labels 642 feature_elements += features 643 label_elements += labels 644 645 return feature_elements, label_elements
646 647
648 - def draw_feature(self, feature):
649 """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...]) 650 651 o feature Feature containing location info 652 653 Returns tuple of (list of elements describing single feature, list 654 of labels for those elements) 655 """ 656 if feature.hide: # Feature hidden, don't draw it... 657 return [], [] 658 659 feature_elements = [] # Holds diagram elements belonging to the feature 660 label_elements = [] # Holds labels belonging to the feature 661 662 # A single feature may be split into subfeatures, so loop over them 663 for start, end in feature.locations: 664 #print start, end, feature.name 665 # Forward strand, start > end as it overlaps zero 666 if start > end: 667 locs = [(start, self.end), (self.start, end)] 668 else: 669 locs = [(start, end)] 670 #print locs 671 for locstart, locend in locs: 672 #print locstart, locend, feature.name 673 # Correct locations in case the feature overruns the drawn sequence 674 if locstart < self.start: 675 locstart = self.start 676 if locend > self.end: 677 locend = self.end 678 679 feature_boxes = self.draw_feature_location(feature, locstart, locend) 680 for box, label in feature_boxes: 681 feature_elements.append(box) 682 if label is not None: 683 label_elements.append(label) 684 685 return feature_elements, label_elements
686
687 - def draw_feature_location(self, feature, locstart, locend):
688 feature_boxes = [] 689 # Get start and end positions for feature/subfeatures 690 start_fragment, start_offset = self.canvas_location(locstart) 691 end_fragment, end_offset = self.canvas_location(locend) 692 #print "start_fragment, start_offset", start_fragment, start_offset 693 #print "end_fragment, end_offset", end_fragment, end_offset 694 #print "start, end", locstart, locend 695 696 # Note that there is a strange situation where a feature may be in 697 # several parts, and one or more of those parts may end up being 698 # drawn on a non-existent fragment. So we check that the start and 699 # end fragments do actually exist in terms of the drawing 700 allowed_fragments = self.fragment_limits.keys() 701 if start_fragment in allowed_fragments and end_fragment in allowed_fragments: 702 703 #print feature.name, feature.start, feature.end, start_offset, end_offset 704 if start_fragment == end_fragment: # Feature is found on one fragment 705 feature_box, label = self.get_feature_sigil(feature, start_offset, 706 end_offset, start_fragment) 707 feature_boxes.append((feature_box, label)) 708 #feature_elements.append(feature_box) 709 #if label is not None: # There is a label for the feature 710 # label_elements.append(label) 711 else: # Feature is split over two or more fragments 712 fragment = start_fragment 713 start = start_offset 714 # The bit that runs up to the end of the first fragment, 715 # and any bits that subsequently span whole fragments 716 while self.fragment_limits[fragment][1] < locend: 717 #print fragment, self.fragment_limits[fragment][1], locend 718 feature_box, label = self.get_feature_sigil(feature, start, 719 self.pagewidth, 720 fragment) 721 722 fragment += 1 # move to next fragment 723 start = 0 # start next sigil from start of fragment 724 feature_boxes.append((feature_box, label)) 725 #feature_elements.append(feature_box) 726 #if label is not None: # There's a label for the feature 727 # label_elements.append(label) 728 # The last bit of the feature 729 #print locend, self.end, fragment 730 #print self.fragment_bases, self.length 731 feature_box, label = self.get_feature_sigil(feature, 0, 732 end_offset, fragment) 733 feature_boxes.append((feature_box, label)) 734 #if locstart > locend: 735 # print locstart, locend, feature.strand, feature_boxes, feature.name 736 return feature_boxes
737 738 739
740 - def get_feature_sigil(self, feature, x0, x1, fragment, **kwargs):
741 """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element, element) 742 743 o feature Feature object 744 745 o x0 Start X co-ordinate on diagram 746 747 o x1 End X co-ordinate on diagram 748 749 o fragment The fragment on which the feature appears 750 751 Returns a drawable indicator of the feature, and any required label 752 for it 753 """ 754 # Establish co-ordinates for drawing 755 x0, x1 = self.x0 + x0, self.x0 + x1 756 btm, ctr, top = self.track_offsets[self.current_track_level] 757 try: 758 btm += self.fragment_lines[fragment][0] 759 ctr += self.fragment_lines[fragment][0] 760 top += self.fragment_lines[fragment][0] 761 except: # Only called if the method screws up big time 762 print "We've got a screw-up" 763 print self.start, self.end 764 print self.fragment_bases 765 print x0, x1 766 for locstart, locend in feature.locations: 767 print self.canvas_location(locstart) 768 print self.canvas_location(locend) 769 print 'FEATURE\n', feature 770 1/0 771 772 # Distribution dictionary for various ways of drawing the feature 773 # Each method takes the corners of a containing box and a color 774 # as argument 775 draw_methods = {'BOX': draw_box, 776 'ARROW': draw_arrow, 777 } 778 method = draw_methods[feature.sigil] 779 kwargs['head_length_ratio'] = feature.arrowhead_length 780 kwargs['shaft_height_ratio'] = feature.arrowshaft_height 781 782 #Support for clickable links... needs ReportLab 2.4 or later 783 #which added support for links in SVG output. 784 if hasattr(feature, "url") : 785 kwargs["hrefURL"] = feature.url 786 kwargs["hrefTitle"] = feature.name 787 788 strand = feature.strand 789 790 # Get sigil for the feature, location dependent on the feature strand 791 if strand == 1: 792 sigil = method((x0, ctr), (x1, top), color=feature.color, 793 orientation='right', **kwargs) 794 elif strand == -1: 795 sigil = method((x1, btm), (x0, ctr), color=feature.color, 796 orientation='left', **kwargs) 797 else: 798 sigil = method((x0, btm), (x1, top), color=feature.color, 799 **kwargs) 800 if feature.label: # Feature requires a label 801 label = String(0, 0, feature.name, 802 fontName=feature.label_font, 803 fontSize=feature.label_size, 804 fillColor=feature.label_color) 805 labelgroup = Group(label) 806 # Feature is on top, or covers both strands (location affects 807 # the height and rotation of the label) 808 if feature.strand in (0, 1): 809 rotation = angle2trig(feature.label_angle) 810 if feature.label_position in ('start', "5'", 'left'): 811 pos = x0 812 elif feature.label_position in ('middle', 'center', 'centre'): 813 pos = (x1 + x0)/2. 814 else: 815 pos = x1 816 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 817 rotation[3], pos, top) 818 else: # Feature on bottom strand 819 rotation = angle2trig(feature.label_angle + 180) 820 if feature.label_position in ('start', "5'", 'left'): 821 pos = x1 822 elif feature.label_position in ('middle', 'center', 'centre'): 823 pos = (x1 + x0)/2. 824 else: 825 pos = x0 826 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 827 rotation[3], pos, btm) 828 else: 829 labelgroup = None 830 return sigil, labelgroup
831 832
833 - def draw_graph_set(self, set):
834 """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...]) 835 836 o set GraphSet object 837 838 Returns tuple (list of graph elements, list of graph labels) 839 """ 840 #print 'draw graph set' 841 elements = [] # Holds graph elements 842 843 # Distribution dictionary for how to draw the graph 844 style_methods = {'line': self.draw_line_graph, 845 'heat': self.draw_heat_graph, 846 'bar': self.draw_bar_graph 847 } 848 849 for graph in set.get_graphs(): 850 elements += style_methods[graph.style](graph) 851 852 return elements, []
853 854
855 - def draw_line_graph(self, graph):
856 """ draw_line_graph(self, graph) -> [element, element,...] 857 858 o graph Graph object 859 860 Returns a line graph as a list of drawable elements 861 """ 862 #print '\tdraw_line_graph' 863 line_elements = [] # Holds drawable elements 864 865 # Get graph data 866 data_quartiles = graph.quartiles() 867 minval, maxval = data_quartiles[0],data_quartiles[4] 868 btm, ctr, top = self.track_offsets[self.current_track_level] 869 trackheight = 0.5*(top-btm) 870 datarange = maxval - minval 871 if datarange == 0: 872 datarange = trackheight 873 data = graph[self.start:self.end] 874 875 # midval is the value at which the x-axis is plotted, and is the 876 # central ring in the track 877 if graph.center is None: 878 midval = (maxval + minval)/2. 879 else: 880 midval = graph.center 881 # Whichever is the greatest difference: max-midval or min-midval, is 882 # taken to specify the number of pixel units resolved along the 883 # y-axis 884 resolution = max((midval-minval), (maxval-midval)) 885 886 # Start from first data point 887 pos, val = data[0] 888 lastfrag, lastx = self.canvas_location(pos) 889 lastx += self.x0 # Start xy co-ords 890 lasty = trackheight*(val-midval)/resolution + \ 891 self.fragment_lines[lastfrag][0] + ctr 892 lastval = val 893 # Add a series of lines linking consecutive data points 894 for pos, val in data: 895 frag, x = self.canvas_location(pos) 896 x += self.x0 # next xy co-ords 897 y = trackheight*(val-midval)/resolution + \ 898 self.fragment_lines[frag][0] + ctr 899 if frag == lastfrag: # Points on the same fragment: draw the line 900 line_elements.append(Line(lastx, lasty, x, y, 901 strokeColor = graph.poscolor, 902 strokeWidth = graph.linewidth)) 903 else: # Points not on the same fragment, so interpolate 904 tempval = 1.*(val-lastval)/(x-lastx) 905 tempy = trackheight*(val-midval)/resolution + \ 906 self.fragment_lines[lastfrag][0] + ctr 907 line_elements.append(Line(lastx, lasty, self.xlim, tempy, 908 strokeColor = graph.poscolor, 909 strokeWidth = graph.linewidth)) 910 tempy = trackheight*(val-midval)/resolution + \ 911 self.fragment_lines[frag][0] + ctr 912 line_elements.append(Line(self.x0, tempy, x, y, 913 strokeColor = graph.poscolor, 914 strokeWidth = graph.linewidth)) 915 lastfrag, lastx, lasty, lastval = frag, x, y, val 916 917 return line_elements
918 919
920 - def draw_heat_graph(self, graph):
921 """ draw_heat_graph(self, graph) -> [element, element,...] 922 923 o graph Graph object 924 925 Returns a list of drawable elements for the heat graph 926 """ 927 #print '\tdraw_heat_graph' 928 # At each point contained in the graph data, we draw a box that is the 929 # full height of the track, extending from the midpoint between the 930 # previous and current data points to the midpoint between the current 931 # and next data points 932 heat_elements = [] # Holds drawable elements for the graph 933 934 # Get graph data and information 935 data_quartiles = graph.quartiles() 936 minval, maxval = data_quartiles[0],data_quartiles[4] 937 midval = (maxval + minval)/2. # mid is the value at the X-axis 938 btm, ctr, top = self.track_offsets[self.current_track_level] 939 trackheight = (top-btm) 940 #print self.start, self.end 941 newdata = intermediate_points(self.start, self.end, 942 graph[self.start:self.end]) 943 #print newdata 944 945 # Create elements on the graph, indicating a large positive value by 946 # the graph's poscolor, and a large negative value by the graph's 947 # negcolor attributes 948 for pos0, pos1, val in newdata: 949 fragment0, x0 = self.canvas_location(pos0) 950 fragment1, x1 = self.canvas_location(pos1) 951 x0, x1 = self.x0 + x0, self.x0 + x1 # account for margin 952 #print 'x1 before:', x1 953 954 # Calculate the heat color, based on the differential between 955 # the value and the median value 956 heat = colors.linearlyInterpolatedColor(graph.poscolor, 957 graph.negcolor, 958 maxval, minval, val) 959 960 # Draw heat box 961 if fragment0 == fragment1: # Box is contiguous on one fragment 962 if pos1 >= self.fragment_limits[fragment0][1]: 963 x1 = self.xlim 964 ttop = top + self.fragment_lines[fragment0][0] 965 tbtm = btm + self.fragment_lines[fragment0][0] 966 #print 'equal', pos0, pos1, val 967 #print pos0, pos1, fragment0, fragment1 968 heat_elements.append(draw_box((x0, tbtm), (x1, ttop), 969 color=heat, border=None)) 970 else: # box is split over two or more fragments 971 #if pos0 >= self.fragment_limits[fragment0][0]: 972 # fragment0 += 1 973 fragment = fragment0 974 start = x0 975 while self.fragment_limits[fragment][1] <= pos1: 976 #print pos0, self.fragment_limits[fragment][1], pos1 977 ttop = top + self.fragment_lines[fragment][0] 978 tbtm = btm + self.fragment_lines[fragment][0] 979 heat_elements.append(draw_box((start, tbtm), 980 (self.xlim, ttop), 981 color=heat, 982 border=None)) 983 fragment += 1 984 start = self.x0 985 ttop = top + self.fragment_lines[fragment][0] 986 tbtm = btm + self.fragment_lines[fragment][0] 987 # Add the last part of the bar 988 #print 'x1 after:', x1, '\n' 989 heat_elements.append(draw_box((self.x0, tbtm), (x1, ttop), 990 color=heat, border=None)) 991 992 return heat_elements
993 994
995 - def draw_bar_graph(self, graph):
996 """ draw_bar_graph(self, graph) -> [element, element,...] 997 998 o graph Graph object 999 1000 Returns a list of drawable elements for a bar graph of the passed 1001 Graph object 1002 """ 1003 #print '\tdraw_bar_graph' 1004 # At each point contained in the graph data, we draw a vertical bar 1005 # from the track center to the height of the datapoint value (positive 1006 # values go up in one color, negative go down in the alternative 1007 # color). 1008 bar_elements = [] # Holds drawable elements for the graph 1009 1010 # Set the number of pixels per unit for the data 1011 data_quartiles = graph.quartiles() 1012 minval, maxval = data_quartiles[0],data_quartiles[4] 1013 btm, ctr, top = self.track_offsets[self.current_track_level] 1014 trackheight = 0.5*(top-btm) 1015 datarange = maxval - minval 1016 if datarange == 0: 1017 datarange = trackheight 1018 data = graph[self.start:self.end] 1019 # midval is the value at which the x-axis is plotted, and is the 1020 # central ring in the track 1021 if graph.center is None: 1022 midval = (maxval + minval)/2. 1023 else: 1024 midval = graph.center 1025 1026 # Convert data into 'binned' blocks, covering half the distance to the 1027 # next data point on either side, accounting for the ends of fragments 1028 # and tracks 1029 newdata = intermediate_points(self.start, self.end, 1030 graph[self.start:self.end]) 1031 1032 # Whichever is the greatest difference: max-midval or min-midval, is 1033 # taken to specify the number of pixel units resolved along the 1034 # y-axis 1035 resolution = max((midval-minval), (maxval-midval)) 1036 if resolution == 0: 1037 resolution = trackheight 1038 1039 # Create elements for the bar graph based on newdata 1040 for pos0, pos1, val in newdata: 1041 fragment0, x0 = self.canvas_location(pos0) 1042 fragment1, x1 = self.canvas_location(pos1) 1043 x0, x1 = self.x0 + x0, self.x0 + x1 # account for margin 1044 barval = trackheight*(val-midval)/resolution 1045 if barval >=0: # Different colors for bars that extend above... 1046 barcolor = graph.poscolor 1047 else: # ...or below the axis 1048 barcolor = graph.negcolor 1049 1050 # Draw bar 1051 if fragment0 == fragment1: # Box is contiguous 1052 if pos1 >= self.fragment_limits[fragment0][1]: 1053 x1 = self.xlim 1054 tctr = ctr + self.fragment_lines[fragment0][0] 1055 barval += tctr 1056 bar_elements.append(draw_box((x0, tctr), (x1, barval), 1057 color=barcolor)) 1058 else: # Box is split over two or more fragments 1059 fragment = fragment0 1060 #if pos0 >= self.fragment_limits[fragment0][0]: 1061 # fragment += 1 1062 start = x0 1063 while self.fragment_limits[fragment][1] < pos1: 1064 tctr = ctr + self.fragment_lines[fragment][0] 1065 thisbarval = barval + tctr 1066 bar_elements.append(draw_box((start, tctr), 1067 (self.xlim, thisbarval), 1068 color=barcolor)) 1069 fragment += 1 1070 start = self.x0 1071 tctr = ctr + self.fragment_lines[fragment1][0] 1072 barval += tctr 1073 # Add the last part of the bar 1074 bar_elements.append(draw_box((self.x0, tctr), (x1, barval), 1075 color=barcolor)) 1076 1077 return bar_elements
1078 1079
1080 - def canvas_location(self, base):
1081 """ canvas_location(self, base) -> (int, float) 1082 1083 o base The base number on the genome sequence 1084 1085 Returns the x-coordinate and fragment number of a base on the 1086 genome sequence, in the context of the current drawing setup 1087 """ 1088 base = int(base - self.start) # number of bases we are from the start 1089 fragment = int(base / self.fragment_bases) 1090 if fragment < 1: # First fragment 1091 base_offset = base 1092 fragment = 0 1093 else: # Calculate number of bases from start of fragment 1094 base_offset = base % self.fragment_bases 1095 # Calculate number of pixels from start of fragment 1096 x_offset = 1. * self.pagewidth * base_offset / self.fragment_bases 1097 return fragment, x_offset
1098