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