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  """Linear Drawer module. 
  13   
  14  Provides: 
  15   - LinearDrawer -  Drawing object for linear diagrams 
  16   
  17  For drawing capabilities, this module uses reportlab to draw and write 
  18  the diagram: http://www.reportlab.com 
  19  """ 
  20   
  21  # ReportLab imports 
  22  from __future__ import print_function 
  23   
  24  from reportlab.graphics.shapes import Drawing, Line, String, Group, Polygon 
  25  from reportlab.lib import colors 
  26   
  27  # GenomeDiagram imports 
  28  from ._AbstractDrawer import AbstractDrawer, draw_box, draw_arrow 
  29  from ._AbstractDrawer import draw_cut_corner_box, _stroke_and_fill_colors 
  30  from ._AbstractDrawer import intermediate_points, angle2trig 
  31  from ._FeatureSet import FeatureSet 
  32  from ._GraphSet import GraphSet 
  33   
  34  from math import ceil 
  35   
  36   
37 -class LinearDrawer(AbstractDrawer):
38 """Linear Drawer. 39 40 Inherits from: 41 - AbstractDrawer 42 43 Attributes: 44 - tracklines Boolean for whether to draw lines delineating tracks 45 - pagesize Tuple describing the size of the page in pixels 46 - x0 Float X co-ord for leftmost point of drawable area 47 - xlim Float X co-ord for rightmost point of drawable area 48 - y0 Float Y co-ord for lowest point of drawable area 49 - ylim Float Y co-ord for topmost point of drawable area 50 - pagewidth Float pixel width of drawable area 51 - pageheight Float pixel height of drawable area 52 - xcenter Float X co-ord of center of drawable area 53 - ycenter Float Y co-ord of center of drawable area 54 - start Int, base to start drawing from 55 - end Int, base to stop drawing at 56 - length Int, size of sequence to be drawn 57 - fragments Int, number of fragments into which to divide the 58 drawn sequence 59 - fragment_size Float (0->1) the proportion of the fragment height to 60 draw in 61 - track_size Float (0->1) the proportion of the track height to 62 draw in 63 - drawing Drawing canvas 64 - drawn_tracks List of ints denoting which tracks are to be drawn 65 - current_track_level Int denoting which track is currently being 66 drawn 67 - fragment_height Float total fragment height in pixels 68 - fragment_bases Int total fragment length in bases 69 - fragment_lines Dictionary of top and bottom y-coords of fragment, 70 keyed by fragment number 71 - fragment_limits Dictionary of start and end bases of each fragment, 72 keyed by fragment number 73 - track_offsets Dictionary of number of pixels that each track top, 74 center and bottom is offset from the base of a fragment, keyed by track 75 - cross_track_links List of tuples each with four entries (track A, 76 feature A, track B, feature B) to be linked. 77 78 """ 79
80 - def __init__(self, parent=None, pagesize='A3', orientation='landscape', 81 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 82 start=None, end=None, tracklines=0, fragments=10, 83 fragment_size=0.9, track_size=0.75, cross_track_links=None):
84 """Initialize. 85 86 Arguments: 87 - parent Diagram object containing the data that the drawer draws 88 - pagesize String describing the ISO size of the image, or a tuple 89 of pixels 90 - orientation String describing the required orientation of the 91 final drawing ('landscape' or 'portrait') 92 - x Float (0->1) describing the relative size of the X 93 margins to the page 94 - y Float (0->1) describing the relative size of the Y 95 margins to the page 96 - xl Float (0->1) describing the relative size of the left X 97 margin to the page (overrides x) 98 - xl Float (0->1) describing the relative size of the left X 99 margin to the page (overrides x) 100 - xr Float (0->1) describing the relative size of the right X 101 margin to the page (overrides x) 102 - yt Float (0->1) describing the relative size of the top Y 103 margin to the page (overrides y) 104 - yb Float (0->1) describing the relative size of the lower Y 105 margin to the page (overrides y) 106 - start Int, the position to begin drawing the diagram at 107 - end Int, the position to stop drawing the diagram at 108 - tracklines Boolean flag to show (or not) lines delineating tracks 109 on the diagram 110 - fragments Int, the number of equal fragments into which the 111 sequence should be divided for drawing 112 - fragment_size Float(0->1) The proportion of the available height 113 for the fragment that should be taken up in drawing 114 - track_size The proportion of the available track height that 115 should be taken up in drawing 116 - cross_track_links List of tuples each with four entries (track A, 117 feature A, track B, feature B) to be linked. 118 119 """ 120 # Use the superclass' instantiation method 121 AbstractDrawer.__init__(self, parent, pagesize, orientation, 122 x, y, xl, xr, yt, yb, start, end, 123 tracklines, cross_track_links) 124 125 # Useful measurements on the page 126 self.fragments = fragments 127 self.fragment_size = fragment_size 128 self.track_size = track_size
129
130 - def draw(self):
131 """Draw a linear diagram of the data in the parent Diagram object.""" 132 # Instantiate the drawing canvas 133 self.drawing = Drawing(self.pagesize[0], self.pagesize[1]) 134 135 feature_elements = [] # holds feature elements 136 feature_labels = [] # holds feature labels 137 greytrack_bgs = [] # holds track background 138 greytrack_labels = [] # holds track foreground labels 139 scale_axes = [] # holds scale axes 140 scale_labels = [] # holds scale axis labels 141 142 # Get the tracks to be drawn 143 self.drawn_tracks = self._parent.get_drawn_levels() 144 145 # Set fragment and track sizes 146 self.init_fragments() 147 self.set_track_heights() 148 149 # Go through each track in the parent (if it is to be drawn) one by 150 # one and collate the data as drawing elements 151 for track_level in self.drawn_tracks: # only use tracks to be drawn 152 self.current_track_level = track_level # establish track level 153 track = self._parent[track_level] # get the track at that level 154 gbgs, glabels = self.draw_greytrack(track) # get greytrack elements 155 greytrack_bgs.append(gbgs) 156 greytrack_labels.append(glabels) 157 features, flabels = self.draw_track(track) # get feature and graph elements 158 feature_elements.append(features) 159 feature_labels.append(flabels) 160 if track.scale: 161 axes, slabels = self.draw_scale(track) # get scale elements 162 scale_axes.append(axes) 163 scale_labels.append(slabels) 164 165 feature_cross_links = [] 166 for cross_link_obj in self.cross_track_links: 167 cross_link_elements = self.draw_cross_link(cross_link_obj) 168 if cross_link_elements: 169 feature_cross_links.append(cross_link_elements) 170 171 # Groups listed in order of addition to page (from back to front) 172 # Draw track backgrounds 173 # Draw feature cross track links 174 # Draw features and graphs 175 # Draw scale axes 176 # Draw scale labels 177 # Draw feature labels 178 # Draw track labels 179 element_groups = [greytrack_bgs, feature_cross_links, 180 feature_elements, scale_axes, 181 scale_labels, feature_labels, greytrack_labels] 182 for element_group in element_groups: 183 for element_list in element_group: 184 [self.drawing.add(element) for element in element_list] 185 186 if self.tracklines: # Draw test tracks over top of diagram 187 self.draw_test_tracks()
188
189 - def init_fragments(self):
190 """Initialize useful values for positioning diagram elements.""" 191 # Set basic heights, lengths etc 192 self.fragment_height = 1. * self.pageheight / self.fragments # total fragment height in pixels 193 self.fragment_bases = ceil(1. * self.length / self.fragments) # fragment length in bases 194 195 # Key fragment base and top lines by fragment number 196 self.fragment_lines = {} # Holds bottom and top line locations of fragments, keyed by fragment number 197 fragment_crop = (1 - self.fragment_size) / 2 # No of pixels to crop the fragment 198 fragy = self.ylim # Holder for current absolute fragment base 199 for fragment in range(self.fragments): 200 fragtop = fragy - fragment_crop * self.fragment_height # top - crop 201 fragbtm = fragy - (1 - fragment_crop) * self.fragment_height # bottom + crop 202 self.fragment_lines[fragment] = (fragbtm, fragtop) 203 fragy -= self.fragment_height # next fragment base 204 205 # Key base starts and ends for each fragment by fragment number 206 self.fragment_limits = {} # Holds first and last base positions in a fragment 207 fragment_step = self.fragment_bases # bases per fragment 208 fragment_count = 0 209 # Add start and end positions for each fragment to dictionary 210 for marker in range(int(self.start), int(self.end), int(fragment_step)): 211 self.fragment_limits[fragment_count] = (marker, marker + fragment_step) 212 fragment_count += 1
213
214 - def set_track_heights(self):
215 """Set track heights. 216 217 Since tracks may not be of identical heights, the bottom and top 218 offsets of each track relative to the fragment top and bottom is 219 stored in a dictionary - self.track_offsets, keyed by track number. 220 """ 221 bot_track = min(min(self.drawn_tracks), 1) 222 top_track = max(self.drawn_tracks) # The 'highest' track number to draw 223 224 trackunit_sum = 0 # Total number of 'units' for the tracks 225 trackunits = {} # The start and end units for each track, keyed by track number 226 heightholder = 0 # placeholder variable 227 for track in range(bot_track, top_track + 1): # for all track numbers to 'draw' 228 try: 229 trackheight = self._parent[track].height # Get track height 230 except Exception: # TODO: IndexError? 231 trackheight = 1 # ...or default to 1 232 trackunit_sum += trackheight # increment total track unit height 233 trackunits[track] = (heightholder, heightholder + trackheight) 234 heightholder += trackheight # move to next height 235 trackunit_height = 1. * self.fragment_height * self.fragment_size / trackunit_sum 236 237 # Calculate top and bottom offsets for each track, relative to fragment 238 # base 239 track_offsets = {} # The offsets from fragment base for each track 240 track_crop = trackunit_height * (1 - self.track_size) / 2. # 'step back' in pixels 241 assert track_crop >= 0 242 for track in trackunits: 243 top = trackunits[track][1] * trackunit_height - track_crop # top offset 244 btm = trackunits[track][0] * trackunit_height + track_crop # bottom offset 245 ctr = btm + (top - btm) / 2. # center offset 246 track_offsets[track] = (btm, ctr, top) 247 self.track_offsets = track_offsets
248
249 - def draw_test_tracks(self):
250 """Draw test tracks. 251 252 Draw red lines indicating the top and bottom of each fragment, 253 and blue ones indicating tracks to be drawn. 254 """ 255 # Add lines for each fragment 256 for fbtm, ftop in self.fragment_lines.values(): 257 self.drawing.add(Line(self.x0, ftop, self.xlim, ftop, 258 strokeColor=colors.red)) # top line 259 self.drawing.add(Line(self.x0, fbtm, self.xlim, fbtm, 260 strokeColor=colors.red)) # bottom line 261 262 # Add track lines for this fragment - but only for drawn tracks 263 for track in self.drawn_tracks: 264 trackbtm = fbtm + self.track_offsets[track][0] 265 trackctr = fbtm + self.track_offsets[track][1] 266 tracktop = fbtm + self.track_offsets[track][2] 267 self.drawing.add(Line(self.x0, tracktop, self.xlim, tracktop, 268 strokeColor=colors.blue)) # top line 269 self.drawing.add(Line(self.x0, trackctr, self.xlim, trackctr, 270 strokeColor=colors.green)) # center line 271 self.drawing.add(Line(self.x0, trackbtm, self.xlim, trackbtm, 272 strokeColor=colors.blue)) # bottom line
273
274 - def draw_track(self, track):
275 """Draw track. 276 277 Arguments: 278 - track Track object 279 280 Returns a tuple (list of elements in the track, list of labels in 281 the track). 282 """ 283 track_elements = [] # Holds elements from features and graphs 284 track_labels = [] # Holds labels from features and graphs 285 286 # Distribution dictionary for dealing with different set types 287 set_methods = {FeatureSet: self.draw_feature_set, 288 GraphSet: self.draw_graph_set 289 } 290 291 for set in track.get_sets(): # Draw the feature or graph sets 292 elements, labels = set_methods[set.__class__](set) 293 track_elements += elements 294 track_labels += labels 295 return track_elements, track_labels
296
297 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
298 """Draw tick. 299 300 Arguments: 301 - tickpos Int, position of the tick on the sequence 302 - ctr Float, Y co-ord of the center of the track 303 - ticklen How long to draw the tick 304 - track Track, the track the tick is drawn on 305 - draw_label Boolean, write the tick label? 306 307 Returns a drawing element that is the tick on the scale 308 """ 309 assert self.start <= tickpos and tickpos <= self.end, \ 310 "Tick at %i, but showing %i to %i" \ 311 % (tickpos, self.start, self.end) 312 assert (track.start is None or track.start <= tickpos) and \ 313 (track.end is None or tickpos <= track.end), \ 314 "Tick at %i, but showing %r to %r for track" \ 315 % (tickpos, track.start, track.end) 316 fragment, tickx = self.canvas_location(tickpos) # Tick co-ordinates 317 assert fragment >= 0, \ 318 "Fragment %i, tickpos %i" % (fragment, tickpos) 319 tctr = ctr + self.fragment_lines[fragment][0] # Center line of the track 320 tickx += self.x0 # Tick X co-ord 321 ticktop = tctr + ticklen # Y co-ord of tick top 322 tick = Line(tickx, tctr, tickx, ticktop, strokeColor=track.scale_color) 323 if draw_label: # Put tick position on as label 324 if track.scale_format == 'SInt': 325 if tickpos >= 1000000: 326 tickstring = str(tickpos // 1000000) + " Mbp" 327 elif tickpos >= 1000: 328 tickstring = str(tickpos // 1000) + " Kbp" 329 else: 330 tickstring = str(tickpos) 331 else: 332 tickstring = str(tickpos) 333 label = String(0, 0, tickstring, # Make label string 334 fontName=track.scale_font, 335 fontSize=track.scale_fontsize, 336 fillColor=track.scale_color) 337 labelgroup = Group(label) 338 rotation = angle2trig(track.scale_fontangle) 339 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 340 rotation[3], tickx, ticktop) 341 else: 342 labelgroup = None 343 return tick, labelgroup
344
345 - def draw_scale(self, track):
346 """Draw scale. 347 348 Argument: 349 - track Track object 350 351 Returns a tuple of (list of elements in the scale, list of labels 352 in the scale). 353 """ 354 scale_elements = [] # Holds axes and ticks 355 scale_labels = [] # Holds labels 356 357 if not track.scale: # No scale required, exit early 358 return [], [] 359 360 # Get track location 361 btm, ctr, top = self.track_offsets[self.current_track_level] 362 trackheight = (top - ctr) 363 364 # For each fragment, draw the scale for this track 365 start, end = self._current_track_start_end() 366 start_f, start_x = self.canvas_location(start) 367 end_f, end_x = self.canvas_location(end) 368 369 for fragment in range(start_f, end_f + 1): 370 tbtm = btm + self.fragment_lines[fragment][0] 371 tctr = ctr + self.fragment_lines[fragment][0] 372 ttop = top + self.fragment_lines[fragment][0] 373 # X-axis 374 if fragment == start_f: 375 x_left = start_x 376 else: 377 x_left = 0 378 if fragment == end_f: 379 x_right = end_x 380 # Y-axis end marker 381 scale_elements.append(Line(self.x0 + x_right, tbtm, self.x0 + x_right, ttop, 382 strokeColor=track.scale_color)) 383 else: 384 x_right = self.xlim - self.x0 385 scale_elements.append(Line(self.x0 + x_left, tctr, self.x0 + x_right, tctr, 386 strokeColor=track.scale_color)) 387 # Y-axis start marker 388 scale_elements.append(Line(self.x0 + x_left, tbtm, self.x0 + x_left, ttop, 389 strokeColor=track.scale_color)) 390 391 start, end = self._current_track_start_end() 392 if track.scale_ticks: # Ticks are required on the scale 393 # Draw large ticks 394 # I want the ticks to be consistently positioned relative to 395 # the start of the sequence (position 0), not relative to the 396 # current viewpoint (self.start and self.end) 397 398 ticklen = track.scale_largeticks * trackheight 399 tickiterval = int(track.scale_largetick_interval) 400 # Note that we could just start the list of ticks using 401 # range(0,self.end,tickinterval) and the filter out the 402 # ones before self.start - but this seems wasteful. 403 # Using tickiterval * (self.start//tickiterval) is a shortcut. 404 for tickpos in range(tickiterval * (self.start // tickiterval), 405 int(self.end), tickiterval): 406 if tickpos <= start or end <= tickpos: 407 continue 408 tick, label = self.draw_tick(tickpos, ctr, ticklen, 409 track, 410 track.scale_largetick_labels) 411 scale_elements.append(tick) 412 if label is not None: # If there's a label, add it 413 scale_labels.append(label) 414 # Draw small ticks 415 ticklen = track.scale_smallticks * trackheight 416 tickiterval = int(track.scale_smalltick_interval) 417 for tickpos in range(tickiterval * (self.start // tickiterval), 418 int(self.end), tickiterval): 419 if tickpos <= start or end <= tickpos: 420 continue 421 tick, label = self.draw_tick(tickpos, ctr, ticklen, 422 track, 423 track.scale_smalltick_labels) 424 scale_elements.append(tick) 425 if label is not None: # If there's a label, add it 426 scale_labels.append(label) 427 428 # Check to see if the track contains a graph - if it does, get the 429 # minimum and maximum values, and put them on the scale Y-axis 430 if track.axis_labels: 431 for set in track.get_sets(): # Check all sets... 432 if set.__class__ is GraphSet: # ...for a graph set 433 graph_label_min = [] 434 graph_label_mid = [] 435 graph_label_max = [] 436 for graph in set.get_graphs(): 437 quartiles = graph.quartiles() 438 minval, maxval = quartiles[0], quartiles[4] 439 if graph.center is None: 440 midval = (maxval + minval) / 2. 441 graph_label_min.append("%.3f" % minval) 442 graph_label_max.append("%.3f" % maxval) 443 else: 444 diff = max((graph.center - minval), 445 (maxval - graph.center)) 446 minval = graph.center - diff 447 maxval = graph.center + diff 448 midval = graph.center 449 graph_label_mid.append("%.3f" % midval) 450 graph_label_min.append("%.3f" % minval) 451 graph_label_max.append("%.3f" % maxval) 452 for fragment in range(start_f, end_f + 1): # Add to all used fragment axes 453 tbtm = btm + self.fragment_lines[fragment][0] 454 tctr = ctr + self.fragment_lines[fragment][0] 455 ttop = top + self.fragment_lines[fragment][0] 456 if fragment == start_f: 457 x_left = start_x 458 else: 459 x_left = 0 460 for val, pos in [(";".join(graph_label_min), tbtm), 461 (";".join(graph_label_max), ttop), 462 (";".join(graph_label_mid), tctr)]: 463 label = String(0, 0, val, 464 fontName=track.scale_font, 465 fontSize=track.scale_fontsize, 466 fillColor=track.scale_color) 467 labelgroup = Group(label) 468 rotation = angle2trig(track.scale_fontangle) 469 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 470 rotation[3], self.x0 + x_left, pos) 471 scale_labels.append(labelgroup) 472 473 return scale_elements, scale_labels
474
475 - def draw_greytrack(self, track):
476 """Draw greytrack. 477 478 Arguments: 479 - track Track object 480 481 Put in a grey background to the current track in all fragments, 482 if track specifies that we should. 483 """ 484 greytrack_bgs = [] # Holds grey track backgrounds 485 greytrack_labels = [] # Holds grey foreground labels 486 487 if not track.greytrack: # No greytrack required, return early 488 return [], [] 489 490 # Get track location 491 btm, ctr, top = self.track_offsets[self.current_track_level] 492 493 start, end = self._current_track_start_end() 494 start_fragment, start_offset = self.canvas_location(start) 495 end_fragment, end_offset = self.canvas_location(end) 496 497 # Add greytrack to all fragments for this track 498 for fragment in range(start_fragment, end_fragment + 1): 499 tbtm = btm + self.fragment_lines[fragment][0] 500 tctr = ctr + self.fragment_lines[fragment][0] 501 ttop = top + self.fragment_lines[fragment][0] 502 if fragment == start_fragment: 503 x1 = self.x0 + start_offset 504 else: 505 x1 = self.x0 506 if fragment == end_fragment: 507 x2 = self.x0 + end_offset 508 else: 509 x2 = self.xlim 510 box = draw_box((x1, tbtm), (x2, ttop), # Grey track bg 511 colors.Color(0.96, 0.96, 0.96)) # is just a box 512 greytrack_bgs.append(box) 513 514 if track.greytrack_labels: # If labels are required 515 labelstep = (self.pagewidth) / track.greytrack_labels # how far apart should they be? 516 label = String(0, 0, track.name, # label contents 517 fontName=track.greytrack_font, 518 fontSize=track.greytrack_fontsize, 519 fillColor=track.greytrack_fontcolor) 520 # Create a new labelgroup at each position the label is required 521 for x in range(int(self.x0), int(self.xlim), int(labelstep)): 522 if fragment == start_fragment and x < start_offset: 523 continue 524 if fragment == end_fragment and end_offset < x + label.getBounds()[2]: 525 continue 526 labelgroup = Group(label) 527 rotation = angle2trig(track.greytrack_font_rotation) 528 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 529 rotation[3], x, tbtm) 530 if not self.xlim - x <= labelstep: # Don't overlap the end of the track 531 greytrack_labels.append(labelgroup) 532 533 return greytrack_bgs, greytrack_labels
534
535 - def draw_feature_set(self, set):
536 """Draw feature set. 537 538 Arguments: 539 - set FeatureSet object 540 541 Returns a tuple (list of elements describing features, list of 542 labels for elements). 543 """ 544 # print 'draw feature set' 545 feature_elements = [] # Holds diagram elements belonging to the features 546 label_elements = [] # Holds diagram elements belonging to feature labels 547 548 # Collect all the elements for the feature set 549 for feature in set.get_features(): 550 if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end): 551 features, labels = self.draw_feature(feature) # get elements and labels 552 feature_elements += features 553 label_elements += labels 554 555 return feature_elements, label_elements
556
557 - def draw_feature(self, feature):
558 """Draw feature. 559 560 Arguments: 561 - feature Feature containing location info 562 563 Returns tuple of (list of elements describing single feature, list 564 of labels for those elements). 565 """ 566 if feature.hide: # Feature hidden, don't draw it... 567 return [], [] 568 569 feature_elements = [] # Holds diagram elements belonging to the feature 570 label_elements = [] # Holds labels belonging to the feature 571 572 start, end = self._current_track_start_end() 573 # A single feature may be split into subfeatures, so loop over them 574 for locstart, locend in feature.locations: 575 if locend < start: 576 continue 577 locstart = max(locstart, start) 578 if end < locstart: 579 continue 580 locend = min(locend, end) 581 feature_boxes = self.draw_feature_location(feature, locstart, locend) 582 for box, label in feature_boxes: 583 feature_elements.append(box) 584 if label is not None: 585 label_elements.append(label) 586 587 return feature_elements, label_elements
588
589 - def draw_feature_location(self, feature, locstart, locend):
590 """Draw feature location.""" 591 feature_boxes = [] 592 # Get start and end positions for feature/subfeatures 593 start_fragment, start_offset = self.canvas_location(locstart) 594 end_fragment, end_offset = self.canvas_location(locend) 595 # print "start_fragment, start_offset", start_fragment, start_offset 596 # print "end_fragment, end_offset", end_fragment, end_offset 597 # print "start, end", locstart, locend 598 599 # Note that there is a strange situation where a feature may be in 600 # several parts, and one or more of those parts may end up being 601 # drawn on a non-existent fragment. So we check that the start and 602 # end fragments do actually exist in terms of the drawing 603 allowed_fragments = list(self.fragment_limits.keys()) 604 if start_fragment in allowed_fragments and end_fragment in allowed_fragments: 605 # print feature.name, feature.start, feature.end, start_offset, end_offset 606 if start_fragment == end_fragment: # Feature is found on one fragment 607 feature_box, label = self.get_feature_sigil(feature, start_offset, 608 end_offset, start_fragment) 609 feature_boxes.append((feature_box, label)) 610 # feature_elements.append(feature_box) 611 # if label is not None: # There is a label for the feature 612 # label_elements.append(label) 613 else: # Feature is split over two or more fragments 614 fragment = start_fragment 615 start = start_offset 616 # The bit that runs up to the end of the first fragment, 617 # and any bits that subsequently span whole fragments 618 while self.fragment_limits[fragment][1] < locend: 619 # print fragment, self.fragment_limits[fragment][1], locend 620 feature_box, label = self.get_feature_sigil(feature, start, 621 self.pagewidth, 622 fragment) 623 624 fragment += 1 # move to next fragment 625 start = 0 # start next sigil from start of fragment 626 feature_boxes.append((feature_box, label)) 627 # feature_elements.append(feature_box) 628 # if label is not None: # There's a label for the feature 629 # label_elements.append(label) 630 # The last bit of the feature 631 # print locend, self.end, fragment 632 # print self.fragment_bases, self.length 633 feature_box, label = self.get_feature_sigil(feature, 0, 634 end_offset, fragment) 635 feature_boxes.append((feature_box, label)) 636 # if locstart > locend: 637 # print locstart, locend, feature.strand, feature_boxes, feature.name 638 return feature_boxes
639 846
847 - def get_feature_sigil(self, feature, x0, x1, fragment, **kwargs):
848 """Get feature sigil. 849 850 Arguments: 851 - feature Feature object 852 - x0 Start X co-ordinate on diagram 853 - x1 End X co-ordinate on diagram 854 - fragment The fragment on which the feature appears 855 856 Returns a drawable indicator of the feature, and any required label 857 for it. 858 """ 859 # Establish co-ordinates for drawing 860 x0, x1 = self.x0 + x0, self.x0 + x1 861 btm, ctr, top = self.track_offsets[self.current_track_level] 862 try: 863 btm += self.fragment_lines[fragment][0] 864 ctr += self.fragment_lines[fragment][0] 865 top += self.fragment_lines[fragment][0] 866 except Exception: # Only called if the method screws up big time 867 print("We've got a screw-up") 868 print("%s %s" % (self.start, self.end)) 869 print(self.fragment_bases) 870 print("%r %r" % (x0, x1)) 871 for locstart, locend in feature.locations: 872 print(self.canvas_location(locstart)) 873 print(self.canvas_location(locend)) 874 print('FEATURE\n%s' % feature) 875 raise 876 877 # Distribution dictionary for various ways of drawing the feature 878 draw_methods = {'BOX': self._draw_sigil_box, 879 'ARROW': self._draw_sigil_arrow, 880 'BIGARROW': self._draw_sigil_big_arrow, 881 'OCTO': self._draw_sigil_octo, 882 'JAGGY': self._draw_sigil_jaggy, 883 } 884 885 method = draw_methods[feature.sigil] 886 kwargs['head_length_ratio'] = feature.arrowhead_length 887 kwargs['shaft_height_ratio'] = feature.arrowshaft_height 888 889 # Support for clickable links... needs ReportLab 2.4 or later 890 # which added support for links in SVG output. 891 if hasattr(feature, "url"): 892 kwargs["hrefURL"] = feature.url 893 kwargs["hrefTitle"] = feature.name 894 895 # Get sigil for the feature, give it the bounding box straddling 896 # the axis (it decides strand specific placement) 897 sigil = method(btm, ctr, top, x0, x1, strand=feature.strand, 898 color=feature.color, border=feature.border, 899 **kwargs) 900 901 if feature.label_strand: 902 strand = feature.label_strand 903 else: 904 strand = feature.strand 905 if feature.label: # Feature requires a label 906 label = String(0, 0, feature.name, 907 fontName=feature.label_font, 908 fontSize=feature.label_size, 909 fillColor=feature.label_color) 910 labelgroup = Group(label) 911 # Feature is on top, or covers both strands (location affects 912 # the height and rotation of the label) 913 if strand != -1: 914 rotation = angle2trig(feature.label_angle) 915 if feature.label_position in ('end', "3'", 'right'): 916 pos = x1 917 elif feature.label_position in ('middle', 'center', 'centre'): 918 pos = (x1 + x0) / 2. 919 else: 920 # Default to start, i.e. 'start', "5'", 'left' 921 pos = x0 922 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 923 rotation[3], pos, top) 924 else: # Feature on bottom strand 925 rotation = angle2trig(feature.label_angle + 180) 926 if feature.label_position in ('end', "3'", 'right'): 927 pos = x0 928 elif feature.label_position in ('middle', 'center', 'centre'): 929 pos = (x1 + x0) / 2. 930 else: 931 # Default to start, i.e. 'start', "5'", 'left' 932 pos = x1 933 labelgroup.transform = (rotation[0], rotation[1], rotation[2], 934 rotation[3], pos, btm) 935 else: 936 labelgroup = None 937 return sigil, labelgroup
938
939 - def draw_graph_set(self, set):
940 """Draw graph set. 941 942 Arguments: 943 - set GraphSet object 944 945 Returns tuple (list of graph elements, list of graph labels). 946 """ 947 # print 'draw graph set' 948 elements = [] # Holds graph elements 949 950 # Distribution dictionary for how to draw the graph 951 style_methods = {'line': self.draw_line_graph, 952 'heat': self.draw_heat_graph, 953 'bar': self.draw_bar_graph 954 } 955 956 for graph in set.get_graphs(): 957 elements += style_methods[graph.style](graph) 958 959 return elements, []
960
961 - def draw_line_graph(self, graph):
962 """Return a line graph as a list of drawable elements. 963 964 Arguments: 965 - graph Graph object 966 967 """ 968 # print '\tdraw_line_graph' 969 line_elements = [] # Holds drawable elements 970 971 # Get graph data 972 data_quartiles = graph.quartiles() 973 minval, maxval = data_quartiles[0], data_quartiles[4] 974 btm, ctr, top = self.track_offsets[self.current_track_level] 975 trackheight = 0.5 * (top - btm) 976 datarange = maxval - minval 977 if datarange == 0: 978 datarange = trackheight 979 980 start, end = self._current_track_start_end() 981 data = graph[start:end] 982 983 # midval is the value at which the x-axis is plotted, and is the 984 # central ring in the track 985 if graph.center is None: 986 midval = (maxval + minval) / 2. 987 else: 988 midval = graph.center 989 # Whichever is the greatest difference: max-midval or min-midval, is 990 # taken to specify the number of pixel units resolved along the 991 # y-axis 992 resolution = max((midval - minval), (maxval - midval)) 993 994 # Start from first data point 995 pos, val = data[0] 996 lastfrag, lastx = self.canvas_location(pos) 997 lastx += self.x0 # Start xy co-ords 998 lasty = trackheight * (val - midval) / resolution + \ 999 self.fragment_lines[lastfrag][0] + ctr 1000 lastval = val 1001 # Add a series of lines linking consecutive data points 1002 for pos, val in data: 1003 frag, x = self.canvas_location(pos) 1004 x += self.x0 # next xy co-ords 1005 y = trackheight * (val - midval) / resolution + \ 1006 self.fragment_lines[frag][0] + ctr 1007 if frag == lastfrag: # Points on the same fragment: draw the line 1008 line_elements.append(Line(lastx, lasty, x, y, 1009 strokeColor=graph.poscolor, 1010 strokeWidth=graph.linewidth)) 1011 else: # Points not on the same fragment, so interpolate 1012 tempy = trackheight * (val - midval) / resolution + \ 1013 self.fragment_lines[lastfrag][0] + ctr 1014 line_elements.append(Line(lastx, lasty, self.xlim, tempy, 1015 strokeColor=graph.poscolor, 1016 strokeWidth=graph.linewidth)) 1017 tempy = trackheight * (val - midval) / resolution + \ 1018 self.fragment_lines[frag][0] + ctr 1019 line_elements.append(Line(self.x0, tempy, x, y, 1020 strokeColor=graph.poscolor, 1021 strokeWidth=graph.linewidth)) 1022 lastfrag, lastx, lasty, lastval = frag, x, y, val 1023 1024 return line_elements
1025
1026 - def draw_heat_graph(self, graph):
1027 """Return a list of drawable elements for the heat graph.""" 1028 # print '\tdraw_heat_graph' 1029 # At each point contained in the graph data, we draw a box that is the 1030 # full height of the track, extending from the midpoint between the 1031 # previous and current data points to the midpoint between the current 1032 # and next data points 1033 heat_elements = [] # Holds drawable elements for the graph 1034 1035 # Get graph data and information 1036 data_quartiles = graph.quartiles() 1037 minval, maxval = data_quartiles[0], data_quartiles[4] 1038 midval = (maxval + minval) / 2. # mid is the value at the X-axis 1039 btm, ctr, top = self.track_offsets[self.current_track_level] 1040 trackheight = (top - btm) 1041 1042 start, end = self._current_track_start_end() 1043 data = intermediate_points(start, end, graph[start:end]) 1044 1045 if not data: 1046 return [] 1047 1048 # Create elements on the graph, indicating a large positive value by 1049 # the graph's poscolor, and a large negative value by the graph's 1050 # negcolor attributes 1051 for pos0, pos1, val in data: 1052 # assert start <= pos0 <= pos1 <= end 1053 fragment0, x0 = self.canvas_location(pos0) 1054 fragment1, x1 = self.canvas_location(pos1) 1055 x0, x1 = self.x0 + x0, self.x0 + x1 # account for margin 1056 # print 'x1 before:', x1 1057 1058 # Calculate the heat color, based on the differential between 1059 # the value and the median value 1060 heat = colors.linearlyInterpolatedColor(graph.poscolor, 1061 graph.negcolor, 1062 maxval, minval, val) 1063 1064 # Draw heat box 1065 if fragment0 == fragment1: # Box is contiguous on one fragment 1066 if pos1 >= self.fragment_limits[fragment0][1]: 1067 x1 = self.xlim 1068 ttop = top + self.fragment_lines[fragment0][0] 1069 tbtm = btm + self.fragment_lines[fragment0][0] 1070 # print 'equal', pos0, pos1, val 1071 # print pos0, pos1, fragment0, fragment1 1072 heat_elements.append(draw_box((x0, tbtm), (x1, ttop), 1073 color=heat, border=None)) 1074 else: # box is split over two or more fragments 1075 # if pos0 >= self.fragment_limits[fragment0][0]: 1076 # fragment0 += 1 1077 fragment = fragment0 1078 start_x = x0 1079 while self.fragment_limits[fragment][1] <= pos1: 1080 # print pos0, self.fragment_limits[fragment][1], pos1 1081 ttop = top + self.fragment_lines[fragment][0] 1082 tbtm = btm + self.fragment_lines[fragment][0] 1083 heat_elements.append(draw_box((start_x, tbtm), 1084 (self.xlim, ttop), 1085 color=heat, 1086 border=None)) 1087 fragment += 1 1088 start_x = self.x0 1089 ttop = top + self.fragment_lines[fragment][0] 1090 tbtm = btm + self.fragment_lines[fragment][0] 1091 # Add the last part of the bar 1092 # print 'x1 after:', x1, '\n' 1093 heat_elements.append(draw_box((self.x0, tbtm), (x1, ttop), 1094 color=heat, border=None)) 1095 1096 return heat_elements
1097
1098 - def draw_bar_graph(self, graph):
1099 """Return list of drawable elements for a bar graph.""" 1100 # print '\tdraw_bar_graph' 1101 # At each point contained in the graph data, we draw a vertical bar 1102 # from the track center to the height of the datapoint value (positive 1103 # values go up in one color, negative go down in the alternative 1104 # color). 1105 bar_elements = [] # Holds drawable elements for the graph 1106 1107 # Set the number of pixels per unit for the data 1108 data_quartiles = graph.quartiles() 1109 minval, maxval = data_quartiles[0], data_quartiles[4] 1110 btm, ctr, top = self.track_offsets[self.current_track_level] 1111 trackheight = 0.5 * (top - btm) 1112 datarange = maxval - minval 1113 if datarange == 0: 1114 datarange = trackheight 1115 data = graph[self.start:self.end] 1116 # midval is the value at which the x-axis is plotted, and is the 1117 # central ring in the track 1118 if graph.center is None: 1119 midval = (maxval + minval) / 2. 1120 else: 1121 midval = graph.center 1122 1123 # Convert data into 'binned' blocks, covering half the distance to the 1124 # next data point on either side, accounting for the ends of fragments 1125 # and tracks 1126 start, end = self._current_track_start_end() 1127 data = intermediate_points(start, end, graph[start:end]) 1128 1129 if not data: 1130 return [] 1131 1132 # Whichever is the greatest difference: max-midval or min-midval, is 1133 # taken to specify the number of pixel units resolved along the 1134 # y-axis 1135 resolution = max((midval - minval), (maxval - midval)) 1136 if resolution == 0: 1137 resolution = trackheight 1138 1139 # Create elements for the bar graph based on newdata 1140 for pos0, pos1, val in data: 1141 fragment0, x0 = self.canvas_location(pos0) 1142 fragment1, x1 = self.canvas_location(pos1) 1143 x0, x1 = self.x0 + x0, self.x0 + x1 # account for margin 1144 barval = trackheight * (val - midval) / resolution 1145 if barval >= 0: # Different colors for bars that extend above... 1146 barcolor = graph.poscolor 1147 else: # ...or below the axis 1148 barcolor = graph.negcolor 1149 1150 # Draw bar 1151 if fragment0 == fragment1: # Box is contiguous 1152 if pos1 >= self.fragment_limits[fragment0][1]: 1153 x1 = self.xlim 1154 tctr = ctr + self.fragment_lines[fragment0][0] 1155 barval += tctr 1156 bar_elements.append(draw_box((x0, tctr), (x1, barval), 1157 color=barcolor)) 1158 else: # Box is split over two or more fragments 1159 fragment = fragment0 1160 # if pos0 >= self.fragment_limits[fragment0][0]: 1161 # fragment += 1 1162 start = x0 1163 while self.fragment_limits[fragment][1] < pos1: 1164 tctr = ctr + self.fragment_lines[fragment][0] 1165 thisbarval = barval + tctr 1166 bar_elements.append(draw_box((start, tctr), 1167 (self.xlim, thisbarval), 1168 color=barcolor)) 1169 fragment += 1 1170 start = self.x0 1171 tctr = ctr + self.fragment_lines[fragment1][0] 1172 barval += tctr 1173 # Add the last part of the bar 1174 bar_elements.append(draw_box((self.x0, tctr), (x1, barval), 1175 color=barcolor)) 1176 1177 return bar_elements
1178
1179 - def canvas_location(self, base):
1180 """Canvas location of a base on the genome. 1181 1182 Arguments: 1183 - base The base number on the genome sequence 1184 1185 Returns the x-coordinate and fragment number of a base on the 1186 genome sequence, in the context of the current drawing setup 1187 """ 1188 base = int(base - self.start) # number of bases we are from the start 1189 fragment = int(base / self.fragment_bases) 1190 if fragment < 1: # First fragment 1191 base_offset = base 1192 fragment = 0 1193 elif fragment >= self.fragments: 1194 fragment = self.fragments - 1 1195 base_offset = self.fragment_bases 1196 else: # Calculate number of bases from start of fragment 1197 base_offset = base % self.fragment_bases 1198 assert fragment < self.fragments, (base, self.start, self.end, self.length, self.fragment_bases) 1199 # Calculate number of pixels from start of fragment 1200 x_offset = 1. * self.pagewidth * base_offset / self.fragment_bases 1201 return fragment, x_offset
1202
1203 - def _draw_sigil_box(self, bottom, center, top, x1, x2, strand, **kwargs):
1204 """Draw BOX sigil.""" 1205 if strand == 1: 1206 y1 = center 1207 y2 = top 1208 elif strand == -1: 1209 y1 = bottom 1210 y2 = center 1211 else: 1212 y1 = bottom 1213 y2 = top 1214 return draw_box((x1, y1), (x2, y2), **kwargs)
1215
1216 - def _draw_sigil_octo(self, bottom, center, top, x1, x2, strand, **kwargs):
1217 """Draw OCTO sigil, a box with the corners cut off.""" 1218 if strand == 1: 1219 y1 = center 1220 y2 = top 1221 elif strand == -1: 1222 y1 = bottom 1223 y2 = center 1224 else: 1225 y1 = bottom 1226 y2 = top 1227 return draw_cut_corner_box((x1, y1), (x2, y2), **kwargs)
1228
1229 - def _draw_sigil_jaggy(self, bottom, center, top, x1, x2, strand, 1230 color, border=None, **kwargs):
1231 """Draw JAGGY sigil. 1232 1233 Although we may in future expose the head/tail jaggy lengths, for now 1234 both the left and right edges are drawn jagged. 1235 """ 1236 if strand == 1: 1237 y1 = center 1238 y2 = top 1239 teeth = 2 1240 elif strand == -1: 1241 y1 = bottom 1242 y2 = center 1243 teeth = 2 1244 else: 1245 y1 = bottom 1246 y2 = top 1247 teeth = 4 1248 1249 xmin = min(x1, x2) 1250 xmax = max(x1, x2) 1251 height = y2 - y1 1252 boxwidth = x2 - x1 1253 tooth_length = min(height / teeth, boxwidth * 0.5) 1254 1255 headlength = tooth_length 1256 taillength = tooth_length 1257 1258 strokecolor, color = _stroke_and_fill_colors(color, border) 1259 1260 points = [] 1261 for i in range(teeth): 1262 points.extend((xmin, y1 + i * height / teeth, 1263 xmin + taillength, y1 + (i + 1) * height / teeth)) 1264 for i in range(teeth): 1265 points.extend((xmax, y1 + (teeth - i) * height / teeth, 1266 xmax - headlength, y1 + (teeth - i - 1) * height / teeth)) 1267 1268 return Polygon(points, 1269 strokeColor=strokecolor, 1270 strokeWidth=1, 1271 strokeLineJoin=1, # 1=round 1272 fillColor=color, 1273 **kwargs)
1274
1275 - def _draw_sigil_arrow(self, bottom, center, top, x1, x2, strand, **kwargs):
1276 """Draw ARROW sigil.""" 1277 if strand == 1: 1278 y1 = center 1279 y2 = top 1280 orientation = "right" 1281 elif strand == -1: 1282 y1 = bottom 1283 y2 = center 1284 orientation = "left" 1285 else: 1286 y1 = bottom 1287 y2 = top 1288 orientation = "right" # backward compatibility 1289 return draw_arrow((x1, y1), (x2, y2), orientation=orientation, **kwargs)
1290
1291 - def _draw_sigil_big_arrow(self, bottom, center, top, x1, x2, strand, **kwargs):
1292 """Draw BIGARROW sigil, like ARROW but straddles the axis.""" 1293 if strand == -1: 1294 orientation = "left" 1295 else: 1296 orientation = "right" 1297 return draw_arrow((x1, bottom), (x2, top), orientation=orientation, **kwargs)
1298