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 draw_cut_corner_box, _stroke_and_fill_colors 
  36  from _AbstractDrawer import intermediate_points, angle2trig 
  37  from _FeatureSet import FeatureSet 
  38  from _GraphSet import GraphSet 
  39   
  40  from math import ceil 
  41   
  42   
43 -class LinearDrawer(AbstractDrawer):
44 """ LinearDrawer(AbstractDrawer) 45 46 Inherits from: 47 48 o AbstractDrawer 49 50 Provides: 51 52 Methods: 53 54 o __init__(self, ...) Called on instantiation 55 56 o set_page_size(self, pagesize, orientation) Set the page size to the 57 passed size and orientation 58 59 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the 60 page 61 62 o set_bounds(self, start, end) Set the bounds for the elements to be 63 drawn 64 65 o is_in_bounds(self, value) Returns a boolean for whether the position 66 is actually to be drawn 67 68 o __len__(self) Returns the length of sequence that will be drawn 69 70 71 o draw(self) Place the drawing elements on the diagram 72 73 o init_fragments(self) Calculate information 74 about sequence fragment locations on the drawing 75 76 o set_track_heights(self) Calculate information about the offset of 77 each track from the fragment base 78 79 o draw_test_tracks(self) Add lines demarcating each track to the 80 drawing 81 82 o draw_track(self, track) Return the contents of the passed track as 83 drawing elements 84 85 o draw_scale(self, track) Return a scale for the passed track as 86 drawing elements 87 88 o draw_tick(self, tickpos, ctr, ticklen, track, draw_label) Return a 89 tick line and possibly a label 90 91 o draw_greytrack(self, track) Return a grey background and superposed 92 label for the passed track as drawing 93 elements 94 95 o draw_feature_set(self, set) Return the features in the passed set as 96 drawing elements 97 98 o draw_feature(self, feature) Return a single feature as drawing 99 elements 100 101 o get_feature_sigil(self, feature, x0, x1, fragment) Return a single 102 feature as its sigil in drawing elements 103 104 o draw_graph_set(self, set) Return the data in a set of graphs as 105 drawing elements 106 107 o draw_line_graph(self, graph) Return the data in a graph as a line 108 graph in drawing elements 109 110 o draw_heat_graph(self, graph) Return the data in a graph as a heat 111 graph in drawing elements 112 113 o draw_bar_graph(self, graph) Return the data in a graph as a bar 114 graph in drawing elements 115 116 o canvas_location(self, base) Return the fragment, and the offset from 117 the left margin, of a passed position 118 in the sequence, on the diagram. 119 120 Attributes: 121 122 o tracklines Boolean for whether to draw lines dilineating tracks 123 124 o pagesize Tuple describing the size of the page in pixels 125 126 o x0 Float X co-ord for leftmost point of drawable area 127 128 o xlim Float X co-ord for rightmost point of drawable area 129 130 o y0 Float Y co-ord for lowest point of drawable area 131 132 o ylim Float Y co-ord for topmost point of drawable area 133 134 o pagewidth Float pixel width of drawable area 135 136 o pageheight Float pixel height of drawable area 137 138 o xcenter Float X co-ord of center of drawable area 139 140 o ycenter Float Y co-ord of center of drawable area 141 142 o start Int, base to start drawing from 143 144 o end Int, base to stop drawing at 145 146 o length Int, size of sequence to be drawn 147 148 o fragments Int, number of fragments into which to divide the 149 drawn sequence 150 151 o fragment_size Float (0->1) the proportion of the fragment height to 152 draw in 153 154 o track_size Float (0->1) the proportion of the track height to 155 draw in 156 157 o drawing Drawing canvas 158 159 o drawn_tracks List of ints denoting which tracks are to be drawn 160 161 o current_track_level Int denoting which track is currently being 162 drawn 163 164 o fragment_height Float total fragment height in pixels 165 166 o fragment_bases Int total fragment length in bases 167 168 o fragment_lines Dictionary of top and bottom y-coords of fragment, 169 keyed by fragment number 170 171 o fragment_limits Dictionary of start and end bases of each fragment, 172 keyed by fragment number 173 174 o track_offsets Dictionary of number of pixels that each track top, 175 center and bottom is offset from the base of a 176 fragment, keyed by track 177 178 o cross_track_links List of tuples each with four entries (track A, 179 feature A, track B, feature B) to be linked. 180 181 """
182 - def __init__(self, parent=None, pagesize='A3', orientation='landscape', 183 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 184 start=None, end=None, tracklines=0, fragments=10, 185 fragment_size=0.9, track_size=0.75, cross_track_links=None):
186 """ __init__(self, parent, pagesize='A3', orientation='landscape', 187 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 188 start=None, end=None, tracklines=0, fragments=10, 189 fragment_size=0.9, track_size=0.75) 190 191 o parent Diagram object containing the data that the drawer 192 draws 193 194 o pagesize String describing the ISO size of the image, or a tuple 195 of pixels 196 197 o orientation String describing the required orientation of the 198 final drawing ('landscape' or 'portrait') 199 200 o x Float (0->1) describing the relative size of the X 201 margins to the page 202 203 o y Float (0->1) describing the relative size of the Y 204 margins to the page 205 206 o xl Float (0->1) describing the relative size of the left X 207 margin to the page (overrides x) 208 209 o xl Float (0->1) describing the relative size of the left X 210 margin to the page (overrides x) 211 212 o xr Float (0->1) describing the relative size of the right X 213 margin to the page (overrides x) 214 215 o yt Float (0->1) describing the relative size of the top Y 216 margin to the page (overrides y) 217 218 o yb Float (0->1) describing the relative size of the lower Y 219 margin to the page (overrides y) 220 221 o start Int, the position to begin drawing the diagram at 222 223 o end Int, the position to stop drawing the diagram at 224 225 o tracklines Boolean flag to show (or not) lines delineating tracks 226 on the diagram 227 228 o fragments Int, the number of equal fragments into which the 229 sequence should be divided for drawing 230 231 o fragment_size Float(0->1) The proportion of the available height 232 for the fragment that should be taken up in drawing 233 234 o track_size The proportion of the available track height that 235 should be taken up in drawing 236 237 o cross_track_links List of tuples each with four entries (track A, 238 feature A, track B, feature B) to be linked. 239 """ 240 # Use the superclass' instantiation method 241 AbstractDrawer.__init__(self, parent, pagesize, orientation, 242 x, y, xl, xr, yt, yb, start, end, 243 tracklines, cross_track_links) 244 245 # Useful measurements on the page 246 self.fragments = fragments 247 self.fragment_size = fragment_size 248 self.track_size = track_size
249
250 - def draw(self):
251 """ draw(self) 252 253 Draw a linear diagram of the data in the parent Diagram object 254 """ 255 # Instantiate the drawing canvas 256 self.drawing = Drawing(self.pagesize[0], self.pagesize[1]) 257 258 feature_elements = [] # holds feature elements 259 feature_labels = [] # holds feature labels 260 greytrack_bgs = [] # holds track background 261 greytrack_labels = [] # holds track foreground labels 262 scale_axes = [] # holds scale axes 263 scale_labels = [] # holds scale axis labels 264 265 # Get the tracks to be drawn 266 self.drawn_tracks = self._parent.get_drawn_levels() 267 268 # Set fragment and track sizes 269 self.init_fragments() 270 self.set_track_heights() 271 272 # Go through each track in the parent (if it is to be drawn) one by 273 # one and collate the data as drawing elements 274 for track_level in self.drawn_tracks: # only use tracks to be drawn 275 self.current_track_level = track_level # establish track level 276 track = self._parent[track_level] # get the track at that level 277 gbgs, glabels = self.draw_greytrack(track) # get greytrack elements 278 greytrack_bgs.append(gbgs) 279 greytrack_labels.append(glabels) 280 features, flabels = self.draw_track(track) # get feature and graph elements 281 feature_elements.append(features) 282 feature_labels.append(flabels) 283 if track.scale: 284 axes, slabels = self.draw_scale(track) # get scale elements 285 scale_axes.append(axes) 286 scale_labels.append(slabels) 287 288 feature_cross_links = [] 289 for cross_link_obj in self.cross_track_links: 290 cross_link_elements = self.draw_cross_link(cross_link_obj) 291 if cross_link_elements: 292 feature_cross_links.append(cross_link_elements) 293 294 # Groups listed in order of addition to page (from back to front) 295 # Draw track backgrounds 296 # Draw feature cross track links 297 # Draw features and graphs 298 # Draw scale axes 299 # Draw scale labels 300 # Draw feature labels 301 # Draw track labels 302 element_groups = [greytrack_bgs, feature_cross_links, 303 feature_elements, scale_axes, 304 scale_labels, feature_labels, greytrack_labels] 305 for element_group in element_groups: 306 for element_list in element_group: 307 [self.drawing.add(element) for element in element_list] 308 309 if self.tracklines: # Draw test tracks over top of diagram 310 self.draw_test_tracks()
311
312 - def init_fragments(self):
313 """ init_fragments(self) 314 315 Initialises useful values for calculating the positioning of 316 diagram elements 317 """ 318 # Set basic heights, lengths etc 319 self.fragment_height = 1.*self.pageheight/self.fragments # total fragment height in pixels 320 self.fragment_bases = ceil(1.*self.length/self.fragments) # fragment length in bases 321 322 # Key fragment base and top lines by fragment number 323 self.fragment_lines = {} # Holds bottom and top line locations of fragments, keyed by fragment number 324 fragment_crop = (1-self.fragment_size)/2 # No of pixels to crop the fragment 325 fragy = self.ylim # Holder for current absolute fragment base 326 for fragment in range(self.fragments): 327 fragtop = fragy-fragment_crop * self.fragment_height # top - crop 328 fragbtm = fragy-(1-fragment_crop) * self.fragment_height # bottom + crop 329 self.fragment_lines[fragment] = (fragbtm, fragtop) 330 fragy -= self.fragment_height # next fragment base 331 332 # Key base starts and ends for each fragment by fragment number 333 self.fragment_limits = {} # Holds first and last base positions in a fragment 334 fragment_step = self.fragment_bases # bases per fragment 335 fragment_count = 0 336 # Add start and end positions for each fragment to dictionary 337 for marker in range(int(self.start), int(self.end), int(fragment_step)): 338 self.fragment_limits[fragment_count] = (marker, marker+fragment_step) 339 fragment_count += 1
340
341 - def set_track_heights(self):
342 """ set_track_heights(self) 343 344 Since tracks may not be of identical heights, the bottom and top 345 offsets of each track relative to the fragment top and bottom is 346 stored in a dictionary - self.track_offsets, keyed by track number 347 """ 348 bot_track = min(min(self.drawn_tracks), 1) 349 top_track = max(self.drawn_tracks) # The 'highest' track number to draw 350 351 trackunit_sum = 0 # Total number of 'units' for the tracks 352 trackunits = {} # The start and end units for each track, keyed by track number 353 heightholder = 0 # placeholder variable 354 for track in range(bot_track, top_track+1): # for all track numbers to 'draw' 355 try: 356 trackheight = self._parent[track].height # Get track height 357 except: 358 trackheight = 1 # ...or default to 1 359 trackunit_sum += trackheight # increment total track unit height 360 trackunits[track] = (heightholder, heightholder+trackheight) 361 heightholder += trackheight # move to next height 362 trackunit_height = 1.*self.fragment_height*self.fragment_size/trackunit_sum 363 364 # Calculate top and bottom offsets for each track, relative to fragment 365 # base 366 track_offsets = {} # The offsets from fragment base for each track 367 track_crop = trackunit_height*(1-self.track_size)/2. # 'step back' in pixels 368 assert track_crop >= 0 369 for track in trackunits: 370 top = trackunits[track][1]*trackunit_height - track_crop # top offset 371 btm = trackunits[track][0]*trackunit_height + track_crop # bottom offset 372 ctr = btm+(top-btm)/2. # center offset 373 track_offsets[track] = (btm, ctr, top) 374 self.track_offsets = track_offsets
375
376 - def draw_test_tracks(self):
377 """ draw_test_tracks(self) 378 379 Draw red lines indicating the top and bottom of each fragment, 380 and blue ones indicating tracks to be drawn. 381 """ 382 # Add lines for each fragment 383 for fbtm, ftop in self.fragment_lines.values(): 384 self.drawing.add(Line(self.x0, ftop, self.xlim, ftop, 385 strokeColor=colors.red)) # top line 386 self.drawing.add(Line(self.x0, fbtm, self.xlim, fbtm, 387 strokeColor=colors.red)) # bottom line 388 389 # Add track lines for this fragment - but only for drawn tracks 390 for track in self.drawn_tracks: 391 trackbtm = fbtm + self.track_offsets[track][0] 392 trackctr = fbtm + self.track_offsets[track][1] 393 tracktop = fbtm + self.track_offsets[track][2] 394 self.drawing.add(Line(self.x0, tracktop, self.xlim, tracktop, 395 strokeColor=colors.blue)) # top line 396 self.drawing.add(Line(self.x0, trackctr, self.xlim, trackctr, 397 strokeColor=colors.green)) # center line 398 self.drawing.add(Line(self.x0, trackbtm, self.xlim, trackbtm, 399 strokeColor=colors.blue)) # bottom line
400
401 - def draw_track(self, track):
402 """ draw_track(self, track) -> ([element, element,...], [element, element,...]) 403 404 o track Track object 405 406 Returns a tuple (list of elements in the track, list of labels in 407 the track) 408 """ 409 track_elements = [] # Holds elements from features and graphs 410 track_labels = [] # Holds labels from features and graphs 411 412 # Distribution dictionary for dealing with different set types 413 set_methods = {FeatureSet: self.draw_feature_set, 414 GraphSet: self.draw_graph_set 415 } 416 417 for set in track.get_sets(): # Draw the feature or graph sets 418 elements, labels = set_methods[set.__class__](set) 419 track_elements += elements 420 track_labels += labels 421 return track_elements, track_labels
422
423 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
424 """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element) 425 426 o tickpos Int, position of the tick on the sequence 427 428 o ctr Float, Y co-ord of the center of the track 429 430 o ticklen How long to draw the tick 431 432 o track Track, the track the tick is drawn on 433 434 o draw_label Boolean, write the tick label? 435 436 Returns a drawing element that is the tick on the scale 437 """ 438 assert self.start <= tickpos and tickpos <= self.end, \ 439 "Tick at %i, but showing %i to %i" \ 440 % (tickpos, self.start, self.end) 441 assert (track.start is None or track.start <= tickpos) and \ 442 (track.end is None or tickpos <= track.end), \ 443 "Tick at %i, but showing %r to %r for track" \ 444 % (tickpos, track.start, track.end) 445 fragment, tickx = self.canvas_location(tickpos) # Tick co-ordinates 446 assert fragment >=0, \ 447 "Fragment %i, tickpos %i" % (fragment, tickpos) 448 tctr = ctr + self.fragment_lines[fragment][0] # Center line of the track 449 tickx += self.x0 # Tick X co-ord 450 ticktop = tctr + ticklen # Y co-ord of tick top 451 tick = Line(tickx, tctr, tickx, ticktop, strokeColor=track.scale_color) 452 if draw_label: # Put tick position on as label 453 if track.scale_format == 'SInt': 454 if tickpos >= 1000000: 455 tickstring = str(tickpos//1000000) + " Mbp" 456 elif tickpos >= 1000: 457 tickstring = str(tickpos//1000) + " Kbp" 458 else: 459 tickstring = str(tickpos) 460 else: 461 tickstring = str(tickpos) 462 label = String(0, 0, tickstring, # Make label string 463 fontName=track.scale_font, 464 fontSize=track.scale_fontsize, 465 fillColor=track.scale_color) 466 labelgroup = Group(label) 467 rotation = angle2trig(track.scale_fontangle) 468 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 469 rotation[3], tickx, ticktop) 470 else: 471 labelgroup = None 472 return tick, labelgroup
473
474 - def draw_scale(self, track):
475 """ draw_scale(self, track) -> ([element, element,...], [element, element,...]) 476 477 o track Track object 478 479 Returns a tuple of (list of elements in the scale, list of labels 480 in the scale) 481 """ 482 scale_elements = [] # Holds axes and ticks 483 scale_labels = [] # Holds labels 484 485 if not track.scale: # No scale required, exit early 486 return [], [] 487 488 # Get track location 489 btm, ctr, top = self.track_offsets[self.current_track_level] 490 trackheight = (top-ctr) 491 492 # For each fragment, draw the scale for this track 493 start, end = self._current_track_start_end() 494 start_f, start_x = self.canvas_location(start) 495 end_f, end_x = self.canvas_location(end) 496 497 for fragment in range(start_f, end_f+1): 498 tbtm = btm + self.fragment_lines[fragment][0] 499 tctr = ctr + self.fragment_lines[fragment][0] 500 ttop = top + self.fragment_lines[fragment][0] 501 # X-axis 502 if fragment == start_f: 503 x_left = start_x 504 else: 505 x_left = 0 506 if fragment == end_f: 507 x_right = end_x 508 # Y-axis end marker 509 scale_elements.append(Line(self.x0+x_right, tbtm, self.x0+x_right, ttop, 510 strokeColor=track.scale_color)) 511 else: 512 x_right = self.xlim - self.x0 513 scale_elements.append(Line(self.x0+x_left, tctr, self.x0+x_right, tctr, 514 strokeColor=track.scale_color)) 515 # Y-axis start marker 516 scale_elements.append(Line(self.x0+x_left, tbtm, self.x0+x_left, ttop, 517 strokeColor=track.scale_color)) 518 519 start, end = self._current_track_start_end() 520 if track.scale_ticks: # Ticks are required on the scale 521 # Draw large ticks 522 #I want the ticks to be consistently positioned relative to 523 #the start of the sequence (position 0), not relative to the 524 #current viewpoint (self.start and self.end) 525 526 ticklen = track.scale_largeticks * trackheight 527 tickiterval = int(track.scale_largetick_interval) 528 #Note that we could just start the list of ticks using 529 #range(0,self.end,tickinterval) and the filter out the 530 #ones before self.start - but this seems wasteful. 531 #Using tickiterval * (self.start//tickiterval) is a shortcut. 532 for tickpos in range(tickiterval * (self.start//tickiterval), 533 int(self.end), tickiterval): 534 if tickpos <= start or end <= tickpos: 535 continue 536 tick, label = self.draw_tick(tickpos, ctr, ticklen, 537 track, 538 track.scale_largetick_labels) 539 scale_elements.append(tick) 540 if label is not None: # If there's a label, add it 541 scale_labels.append(label) 542 # Draw small ticks 543 ticklen = track.scale_smallticks * trackheight 544 tickiterval = int(track.scale_smalltick_interval) 545 for tickpos in range(tickiterval * (self.start//tickiterval), 546 int(self.end), tickiterval): 547 if tickpos <= start or end <= tickpos: 548 continue 549 tick, label = self.draw_tick(tickpos, ctr, ticklen, 550 track, 551 track.scale_smalltick_labels) 552 scale_elements.append(tick) 553 if label is not None: # If there's a label, add it 554 scale_labels.append(label) 555 556 # Check to see if the track contains a graph - if it does, get the 557 # minimum and maximum values, and put them on the scale Y-axis 558 if track.axis_labels: 559 for set in track.get_sets(): # Check all sets... 560 if set.__class__ is GraphSet: # ...for a graph set 561 graph_label_min = [] 562 graph_label_mid = [] 563 graph_label_max = [] 564 for graph in set.get_graphs(): 565 quartiles = graph.quartiles() 566 minval, maxval = quartiles[0], quartiles[4] 567 if graph.center is None: 568 midval = (maxval + minval)/2. 569 graph_label_min.append("%.3f" % minval) 570 graph_label_max.append("%.3f" % maxval) 571 else: 572 diff = max((graph.center-minval), 573 (maxval-graph.center)) 574 minval = graph.center-diff 575 maxval = graph.center+diff 576 midval = graph.center 577 graph_label_mid.append("%.3f" % midval) 578 graph_label_min.append("%.3f" % minval) 579 graph_label_max.append("%.3f" % maxval) 580 for fragment in range(start_f, end_f+1): # Add to all used fragment axes 581 tbtm = btm + self.fragment_lines[fragment][0] 582 tctr = ctr + self.fragment_lines[fragment][0] 583 ttop = top + self.fragment_lines[fragment][0] 584 if fragment == start_f: 585 x_left = start_x 586 else: 587 x_left = 0 588 for val, pos in [(";".join(graph_label_min), tbtm), 589 (";".join(graph_label_max), ttop), 590 (";".join(graph_label_mid), tctr)]: 591 label = String(0, 0, val, 592 fontName=track.scale_font, 593 fontSize=track.scale_fontsize, 594 fillColor=track.scale_color) 595 labelgroup = Group(label) 596 rotation = angle2trig(track.scale_fontangle) 597 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 598 rotation[3], self.x0 + x_left, pos) 599 scale_labels.append(labelgroup) 600 601 return scale_elements, scale_labels
602
603 - def draw_greytrack(self, track):
604 """ draw_greytrack(self) -> ([element, element,...], [element, element,...]) 605 606 o track Track object 607 608 Put in a grey background to the current track in all fragments, 609 if track specifies that we should 610 """ 611 greytrack_bgs = [] # Holds grey track backgrounds 612 greytrack_labels = [] # Holds grey foreground labels 613 614 if not track.greytrack: # No greytrack required, return early 615 return [], [] 616 617 # Get track location 618 btm, ctr, top = self.track_offsets[self.current_track_level] 619 620 start, end = self._current_track_start_end() 621 start_fragment, start_offset = self.canvas_location(start) 622 end_fragment, end_offset = self.canvas_location(end) 623 624 # Add greytrack to all fragments for this track 625 for fragment in range(start_fragment, end_fragment+1): 626 tbtm = btm + self.fragment_lines[fragment][0] 627 tctr = ctr + self.fragment_lines[fragment][0] 628 ttop = top + self.fragment_lines[fragment][0] 629 if fragment == start_fragment: 630 x1 = self.x0 + start_offset 631 else: 632 x1 = self.x0 633 if fragment == end_fragment: 634 x2 = self.x0 + end_offset 635 else: 636 x2 = self.xlim 637 box = draw_box((x1, tbtm), (x2, ttop), # Grey track bg 638 colors.Color(0.96,0.96, 0.96)) # is just a box 639 greytrack_bgs.append(box) 640 641 if track.greytrack_labels: # If labels are required 642 labelstep = (self.pagewidth)/track.greytrack_labels # how far apart should they be? 643 label = String(0, 0, track.name, # label contents 644 fontName=track.greytrack_font, 645 fontSize=track.greytrack_fontsize, 646 fillColor=track.greytrack_fontcolor) 647 # Create a new labelgroup at each position the label is required 648 for x in range(int(self.x0), int(self.xlim), int(labelstep)): 649 if fragment == start_fragment and x < start_offset: 650 continue 651 if fragment == end_fragment and end_offset < x + label.getBounds()[2]: 652 continue 653 labelgroup = Group(label) 654 rotation = angle2trig(track.greytrack_font_rotation) 655 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 656 rotation[3], x, tbtm) 657 if not self.xlim-x <= labelstep: # Don't overlap the end of the track 658 greytrack_labels.append(labelgroup) 659 660 return greytrack_bgs, greytrack_labels
661
662 - def draw_feature_set(self, set):
663 """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...]) 664 665 o set FeatureSet object 666 667 Returns a tuple (list of elements describing features, list of 668 labels for elements) 669 """ 670 #print 'draw feature set' 671 feature_elements = [] # Holds diagram elements belonging to the features 672 label_elements = [] # Holds diagram elements belonging to feature labels 673 674 # Collect all the elements for the feature set 675 for feature in set.get_features(): 676 if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end): 677 features, labels = self.draw_feature(feature) # get elements and labels 678 feature_elements += features 679 label_elements += labels 680 681 return feature_elements, label_elements
682
683 - def draw_feature(self, feature):
684 """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...]) 685 686 o feature Feature containing location info 687 688 Returns tuple of (list of elements describing single feature, list 689 of labels for those elements) 690 """ 691 if feature.hide: # Feature hidden, don't draw it... 692 return [], [] 693 694 feature_elements = [] # Holds diagram elements belonging to the feature 695 label_elements = [] # Holds labels belonging to the feature 696 697 start, end = self._current_track_start_end() 698 # A single feature may be split into subfeatures, so loop over them 699 for locstart, locend in feature.locations: 700 if locend < start: 701 continue 702 locstart = max(locstart, start) 703 if end < locstart: 704 continue 705 locend = min(locend, end) 706 feature_boxes = self.draw_feature_location(feature, locstart, locend) 707 for box, label in feature_boxes: 708 feature_elements.append(box) 709 if label is not None: 710 label_elements.append(label) 711 712 return feature_elements, label_elements
713
714 - def draw_feature_location(self, feature, locstart, locend):
715 feature_boxes = [] 716 # Get start and end positions for feature/subfeatures 717 start_fragment, start_offset = self.canvas_location(locstart) 718 end_fragment, end_offset = self.canvas_location(locend) 719 #print "start_fragment, start_offset", start_fragment, start_offset 720 #print "end_fragment, end_offset", end_fragment, end_offset 721 #print "start, end", locstart, locend 722 723 # Note that there is a strange situation where a feature may be in 724 # several parts, and one or more of those parts may end up being 725 # drawn on a non-existent fragment. So we check that the start and 726 # end fragments do actually exist in terms of the drawing 727 allowed_fragments = self.fragment_limits.keys() 728 if start_fragment in allowed_fragments and end_fragment in allowed_fragments: 729 #print feature.name, feature.start, feature.end, start_offset, end_offset 730 if start_fragment == end_fragment: # Feature is found on one fragment 731 feature_box, label = self.get_feature_sigil(feature, start_offset, 732 end_offset, start_fragment) 733 feature_boxes.append((feature_box, label)) 734 #feature_elements.append(feature_box) 735 #if label is not None: # There is a label for the feature 736 # label_elements.append(label) 737 else: # Feature is split over two or more fragments 738 fragment = start_fragment 739 start = start_offset 740 # The bit that runs up to the end of the first fragment, 741 # and any bits that subsequently span whole fragments 742 while self.fragment_limits[fragment][1] < locend: 743 #print fragment, self.fragment_limits[fragment][1], locend 744 feature_box, label = self.get_feature_sigil(feature, start, 745 self.pagewidth, 746 fragment) 747 748 fragment += 1 # move to next fragment 749 start = 0 # start next sigil from start of fragment 750 feature_boxes.append((feature_box, label)) 751 #feature_elements.append(feature_box) 752 #if label is not None: # There's a label for the feature 753 # label_elements.append(label) 754 # The last bit of the feature 755 #print locend, self.end, fragment 756 #print self.fragment_bases, self.length 757 feature_box, label = self.get_feature_sigil(feature, 0, 758 end_offset, fragment) 759 feature_boxes.append((feature_box, label)) 760 #if locstart > locend: 761 # print locstart, locend, feature.strand, feature_boxes, feature.name 762 return feature_boxes
763 969
970 - def get_feature_sigil(self, feature, x0, x1, fragment, **kwargs):
971 """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element, element) 972 973 o feature Feature object 974 975 o x0 Start X co-ordinate on diagram 976 977 o x1 End X co-ordinate on diagram 978 979 o fragment The fragment on which the feature appears 980 981 Returns a drawable indicator of the feature, and any required label 982 for it 983 """ 984 # Establish co-ordinates for drawing 985 x0, x1 = self.x0 + x0, self.x0 + x1 986 btm, ctr, top = self.track_offsets[self.current_track_level] 987 try: 988 btm += self.fragment_lines[fragment][0] 989 ctr += self.fragment_lines[fragment][0] 990 top += self.fragment_lines[fragment][0] 991 except: # Only called if the method screws up big time 992 print "We've got a screw-up" 993 print self.start, self.end 994 print self.fragment_bases 995 print x0, x1 996 for locstart, locend in feature.locations: 997 print self.canvas_location(locstart) 998 print self.canvas_location(locend) 999 print 'FEATURE\n', feature 1000 1/0 1001 1002 # Distribution dictionary for various ways of drawing the feature 1003 draw_methods = {'BOX': self._draw_sigil_box, 1004 'ARROW': self._draw_sigil_arrow, 1005 'BIGARROW': self._draw_sigil_big_arrow, 1006 'OCTO': self._draw_sigil_octo, 1007 'JAGGY': self._draw_sigil_jaggy, 1008 } 1009 1010 method = draw_methods[feature.sigil] 1011 kwargs['head_length_ratio'] = feature.arrowhead_length 1012 kwargs['shaft_height_ratio'] = feature.arrowshaft_height 1013 1014 #Support for clickable links... needs ReportLab 2.4 or later 1015 #which added support for links in SVG output. 1016 if hasattr(feature, "url") : 1017 kwargs["hrefURL"] = feature.url 1018 kwargs["hrefTitle"] = feature.name 1019 1020 # Get sigil for the feature, give it the bounding box straddling 1021 # the axis (it decides strand specific placement) 1022 sigil = method(btm, ctr, top, x0, x1, strand=feature.strand, 1023 color=feature.color, border=feature.border, 1024 **kwargs) 1025 if feature.label: # Feature requires a label 1026 label = String(0, 0, feature.name, 1027 fontName=feature.label_font, 1028 fontSize=feature.label_size, 1029 fillColor=feature.label_color) 1030 labelgroup = Group(label) 1031 # Feature is on top, or covers both strands (location affects 1032 # the height and rotation of the label) 1033 if feature.strand in (0, 1): 1034 rotation = angle2trig(feature.label_angle) 1035 if feature.label_position in ('start', "5'", 'left'): 1036 pos = x0 1037 elif feature.label_position in ('middle', 'center', 'centre'): 1038 pos = (x1 + x0)/2. 1039 else: 1040 pos = x1 1041 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 1042 rotation[3], pos, top) 1043 else: # Feature on bottom strand 1044 rotation = angle2trig(feature.label_angle + 180) 1045 if feature.label_position in ('start', "5'", 'left'): 1046 pos = x1 1047 elif feature.label_position in ('middle', 'center', 'centre'): 1048 pos = (x1 + x0)/2. 1049 else: 1050 pos = x0 1051 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 1052 rotation[3], pos, btm) 1053 else: 1054 labelgroup = None 1055 return sigil, labelgroup
1056
1057 - def draw_graph_set(self, set):
1058 """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...]) 1059 1060 o set GraphSet object 1061 1062 Returns tuple (list of graph elements, list of graph labels) 1063 """ 1064 #print 'draw graph set' 1065 elements = [] # Holds graph elements 1066 1067 # Distribution dictionary for how to draw the graph 1068 style_methods = {'line': self.draw_line_graph, 1069 'heat': self.draw_heat_graph, 1070 'bar': self.draw_bar_graph 1071 } 1072 1073 for graph in set.get_graphs(): 1074 elements += style_methods[graph.style](graph) 1075 1076 return elements, []
1077
1078 - def draw_line_graph(self, graph):
1079 """ draw_line_graph(self, graph) -> [element, element,...] 1080 1081 o graph Graph object 1082 1083 Returns a line graph as a list of drawable elements 1084 """ 1085 #print '\tdraw_line_graph' 1086 line_elements = [] # Holds drawable elements 1087 1088 # Get graph data 1089 data_quartiles = graph.quartiles() 1090 minval, maxval = data_quartiles[0],data_quartiles[4] 1091 btm, ctr, top = self.track_offsets[self.current_track_level] 1092 trackheight = 0.5*(top-btm) 1093 datarange = maxval - minval 1094 if datarange == 0: 1095 datarange = trackheight 1096 1097 start, end = self._current_track_start_end() 1098 data = graph[start:end] 1099 1100 # midval is the value at which the x-axis is plotted, and is the 1101 # central ring in the track 1102 if graph.center is None: 1103 midval = (maxval + minval)/2. 1104 else: 1105 midval = graph.center 1106 # Whichever is the greatest difference: max-midval or min-midval, is 1107 # taken to specify the number of pixel units resolved along the 1108 # y-axis 1109 resolution = max((midval-minval), (maxval-midval)) 1110 1111 # Start from first data point 1112 pos, val = data[0] 1113 lastfrag, lastx = self.canvas_location(pos) 1114 lastx += self.x0 # Start xy co-ords 1115 lasty = trackheight*(val-midval)/resolution + \ 1116 self.fragment_lines[lastfrag][0] + ctr 1117 lastval = val 1118 # Add a series of lines linking consecutive data points 1119 for pos, val in data: 1120 frag, x = self.canvas_location(pos) 1121 x += self.x0 # next xy co-ords 1122 y = trackheight*(val-midval)/resolution + \ 1123 self.fragment_lines[frag][0] + ctr 1124 if frag == lastfrag: # Points on the same fragment: draw the line 1125 line_elements.append(Line(lastx, lasty, x, y, 1126 strokeColor = graph.poscolor, 1127 strokeWidth = graph.linewidth)) 1128 else: # Points not on the same fragment, so interpolate 1129 tempval = 1.*(val-lastval)/(x-lastx) 1130 tempy = trackheight*(val-midval)/resolution + \ 1131 self.fragment_lines[lastfrag][0] + ctr 1132 line_elements.append(Line(lastx, lasty, self.xlim, tempy, 1133 strokeColor = graph.poscolor, 1134 strokeWidth = graph.linewidth)) 1135 tempy = trackheight*(val-midval)/resolution + \ 1136 self.fragment_lines[frag][0] + ctr 1137 line_elements.append(Line(self.x0, tempy, x, y, 1138 strokeColor = graph.poscolor, 1139 strokeWidth = graph.linewidth)) 1140 lastfrag, lastx, lasty, lastval = frag, x, y, val 1141 1142 return line_elements
1143
1144 - def draw_heat_graph(self, graph):
1145 """ draw_heat_graph(self, graph) -> [element, element,...] 1146 1147 o graph Graph object 1148 1149 Returns a list of drawable elements for the heat graph 1150 """ 1151 #print '\tdraw_heat_graph' 1152 # At each point contained in the graph data, we draw a box that is the 1153 # full height of the track, extending from the midpoint between the 1154 # previous and current data points to the midpoint between the current 1155 # and next data points 1156 heat_elements = [] # Holds drawable elements for the graph 1157 1158 # Get graph data and information 1159 data_quartiles = graph.quartiles() 1160 minval, maxval = data_quartiles[0],data_quartiles[4] 1161 midval = (maxval + minval)/2. # mid is the value at the X-axis 1162 btm, ctr, top = self.track_offsets[self.current_track_level] 1163 trackheight = (top-btm) 1164 1165 start, end = self._current_track_start_end() 1166 data = intermediate_points(start, end, graph[start:end]) 1167 1168 if not data: 1169 return [] 1170 1171 # Create elements on the graph, indicating a large positive value by 1172 # the graph's poscolor, and a large negative value by the graph's 1173 # negcolor attributes 1174 for pos0, pos1, val in data: 1175 #assert start <= pos0 <= pos1 <= end 1176 fragment0, x0 = self.canvas_location(pos0) 1177 fragment1, x1 = self.canvas_location(pos1) 1178 x0, x1 = self.x0 + x0, self.x0 + x1 # account for margin 1179 #print 'x1 before:', x1 1180 1181 # Calculate the heat color, based on the differential between 1182 # the value and the median value 1183 heat = colors.linearlyInterpolatedColor(graph.poscolor, 1184 graph.negcolor, 1185 maxval, minval, val) 1186 1187 # Draw heat box 1188 if fragment0 == fragment1: # Box is contiguous on one fragment 1189 if pos1 >= self.fragment_limits[fragment0][1]: 1190 x1 = self.xlim 1191 ttop = top + self.fragment_lines[fragment0][0] 1192 tbtm = btm + self.fragment_lines[fragment0][0] 1193 #print 'equal', pos0, pos1, val 1194 #print pos0, pos1, fragment0, fragment1 1195 heat_elements.append(draw_box((x0, tbtm), (x1, ttop), 1196 color=heat, border=None)) 1197 else: # box is split over two or more fragments 1198 #if pos0 >= self.fragment_limits[fragment0][0]: 1199 # fragment0 += 1 1200 fragment = fragment0 1201 start_x = x0 1202 while self.fragment_limits[fragment][1] <= pos1: 1203 #print pos0, self.fragment_limits[fragment][1], pos1 1204 ttop = top + self.fragment_lines[fragment][0] 1205 tbtm = btm + self.fragment_lines[fragment][0] 1206 heat_elements.append(draw_box((start_x, tbtm), 1207 (self.xlim, ttop), 1208 color=heat, 1209 border=None)) 1210 fragment += 1 1211 start_x = self.x0 1212 ttop = top + self.fragment_lines[fragment][0] 1213 tbtm = btm + self.fragment_lines[fragment][0] 1214 # Add the last part of the bar 1215 #print 'x1 after:', x1, '\n' 1216 heat_elements.append(draw_box((self.x0, tbtm), (x1, ttop), 1217 color=heat, border=None)) 1218 1219 return heat_elements
1220
1221 - def draw_bar_graph(self, graph):
1222 """ draw_bar_graph(self, graph) -> [element, element,...] 1223 1224 o graph Graph object 1225 1226 Returns a list of drawable elements for a bar graph of the passed 1227 Graph object 1228 """ 1229 #print '\tdraw_bar_graph' 1230 # At each point contained in the graph data, we draw a vertical bar 1231 # from the track center to the height of the datapoint value (positive 1232 # values go up in one color, negative go down in the alternative 1233 # color). 1234 bar_elements = [] # Holds drawable elements for the graph 1235 1236 # Set the number of pixels per unit for the data 1237 data_quartiles = graph.quartiles() 1238 minval, maxval = data_quartiles[0],data_quartiles[4] 1239 btm, ctr, top = self.track_offsets[self.current_track_level] 1240 trackheight = 0.5*(top-btm) 1241 datarange = maxval - minval 1242 if datarange == 0: 1243 datarange = trackheight 1244 data = graph[self.start:self.end] 1245 # midval is the value at which the x-axis is plotted, and is the 1246 # central ring in the track 1247 if graph.center is None: 1248 midval = (maxval + minval)/2. 1249 else: 1250 midval = graph.center 1251 1252 # Convert data into 'binned' blocks, covering half the distance to the 1253 # next data point on either side, accounting for the ends of fragments 1254 # and tracks 1255 start, end = self._current_track_start_end() 1256 data = intermediate_points(start, end, graph[start:end]) 1257 1258 if not data: 1259 return [] 1260 1261 # Whichever is the greatest difference: max-midval or min-midval, is 1262 # taken to specify the number of pixel units resolved along the 1263 # y-axis 1264 resolution = max((midval-minval), (maxval-midval)) 1265 if resolution == 0: 1266 resolution = trackheight 1267 1268 # Create elements for the bar graph based on newdata 1269 for pos0, pos1, val in data: 1270 fragment0, x0 = self.canvas_location(pos0) 1271 fragment1, x1 = self.canvas_location(pos1) 1272 x0, x1 = self.x0 + x0, self.x0 + x1 # account for margin 1273 barval = trackheight*(val-midval)/resolution 1274 if barval >=0: # Different colors for bars that extend above... 1275 barcolor = graph.poscolor 1276 else: # ...or below the axis 1277 barcolor = graph.negcolor 1278 1279 # Draw bar 1280 if fragment0 == fragment1: # Box is contiguous 1281 if pos1 >= self.fragment_limits[fragment0][1]: 1282 x1 = self.xlim 1283 tctr = ctr + self.fragment_lines[fragment0][0] 1284 barval += tctr 1285 bar_elements.append(draw_box((x0, tctr), (x1, barval), 1286 color=barcolor)) 1287 else: # Box is split over two or more fragments 1288 fragment = fragment0 1289 #if pos0 >= self.fragment_limits[fragment0][0]: 1290 # fragment += 1 1291 start = x0 1292 while self.fragment_limits[fragment][1] < pos1: 1293 tctr = ctr + self.fragment_lines[fragment][0] 1294 thisbarval = barval + tctr 1295 bar_elements.append(draw_box((start, tctr), 1296 (self.xlim, thisbarval), 1297 color=barcolor)) 1298 fragment += 1 1299 start = self.x0 1300 tctr = ctr + self.fragment_lines[fragment1][0] 1301 barval += tctr 1302 # Add the last part of the bar 1303 bar_elements.append(draw_box((self.x0, tctr), (x1, barval), 1304 color=barcolor)) 1305 1306 return bar_elements
1307
1308 - def canvas_location(self, base):
1309 """ canvas_location(self, base) -> (int, float) 1310 1311 o base The base number on the genome sequence 1312 1313 Returns the x-coordinate and fragment number of a base on the 1314 genome sequence, in the context of the current drawing setup 1315 """ 1316 base = int(base - self.start) # number of bases we are from the start 1317 fragment = int(base / self.fragment_bases) 1318 if fragment < 1: # First fragment 1319 base_offset = base 1320 fragment = 0 1321 elif fragment >= self.fragments: 1322 fragment = self.fragments-1 1323 base_offset = self.fragment_bases 1324 else: # Calculate number of bases from start of fragment 1325 base_offset = base % self.fragment_bases 1326 assert fragment < self.fragments, (base, self.start, self.end, self.length, self.fragment_bases) 1327 # Calculate number of pixels from start of fragment 1328 x_offset = 1. * self.pagewidth * base_offset / self.fragment_bases 1329 return fragment, x_offset
1330
1331 - def _draw_sigil_box(self, bottom, center, top, x1, x2, strand, **kwargs):
1332 """Draw BOX sigil.""" 1333 if strand == 1: 1334 y1 = center 1335 y2 = top 1336 elif strand == -1: 1337 y1 = bottom 1338 y2 = center 1339 else: 1340 y1 = bottom 1341 y2 = top 1342 return draw_box((x1,y1), (x2,y2), **kwargs)
1343
1344 - def _draw_sigil_octo(self, bottom, center, top, x1, x2, strand, **kwargs):
1345 """Draw OCTO sigil, a box with the corners cut off.""" 1346 if strand == 1: 1347 y1 = center 1348 y2 = top 1349 elif strand == -1: 1350 y1 = bottom 1351 y2 = center 1352 else: 1353 y1 = bottom 1354 y2 = top 1355 return draw_cut_corner_box((x1,y1), (x2,y2), **kwargs)
1356
1357 - def _draw_sigil_jaggy(self, bottom, center, top, x1, x2, strand, 1358 color, border=None, **kwargs):
1359 """Draw JAGGY sigil. 1360 1361 Although we may in future expose the head/tail jaggy lengths, for now 1362 both the left and right edges are drawn jagged. 1363 """ 1364 if strand == 1: 1365 y1 = center 1366 y2 = top 1367 teeth = 2 1368 elif strand == -1: 1369 y1 = bottom 1370 y2 = center 1371 teeth = 2 1372 else: 1373 y1 = bottom 1374 y2 = top 1375 teeth = 4 1376 1377 xmin = min(x1, x2) 1378 xmax = max(x1, x2) 1379 height = y2 - y1 1380 boxwidth = x2 - x1 1381 tooth_length = min(height/teeth, boxwidth*0.5) 1382 1383 headlength = tooth_length 1384 taillength = tooth_length 1385 1386 strokecolor, color = _stroke_and_fill_colors(color, border) 1387 1388 points = [] 1389 for i in range(teeth): 1390 points.extend((xmin, y1+i*height/teeth, 1391 xmin+taillength, y1+(i+1)*height/teeth)) 1392 for i in range(teeth): 1393 points.extend((xmax, y1+(teeth-i)*height/teeth, 1394 xmax-headlength, y1+(teeth-i-1)*height/teeth)) 1395 1396 return Polygon(points, 1397 strokeColor=strokecolor, 1398 strokeWidth=1, 1399 strokeLineJoin=1, # 1=round 1400 fillColor=color, 1401 **kwargs)
1402
1403 - def _draw_sigil_arrow(self, bottom, center, top, x1, x2, strand, **kwargs):
1404 """Draw ARROW sigil.""" 1405 if strand == 1: 1406 y1 = center 1407 y2 = top 1408 orientation = "right" 1409 elif strand== -1: 1410 y1 = bottom 1411 y2 = center 1412 orientation = "left" 1413 else: 1414 y1 = bottom 1415 y2 = top 1416 orientation = "right" # backward compatibility 1417 return draw_arrow((x1,y1), (x2,y2), orientation=orientation, **kwargs)
1418
1419 - def _draw_sigil_big_arrow(self, bottom, center, top, x1, x2, strand, **kwargs):
1420 """Draw BIGARROW sigil, like ARROW but straddles the axis.""" 1421 if strand == -1: 1422 orientation = "left" 1423 else: 1424 orientation = "right" 1425 return draw_arrow((x1,bottom), (x2,top), orientation=orientation, **kwargs)
1426