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

Source Code for Module Bio.Graphics.GenomeDiagram._CircularDrawer

   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  """ CircularDrawer module 
  13   
  14      Provides: 
  15   
  16      o CircularDrawer -  Drawing object for circular 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  from reportlab.pdfbase import _fontdata 
  33  from reportlab.graphics.shapes import ArcPath 
  34   
  35  # GenomeDiagram imports 
  36  from _AbstractDrawer import AbstractDrawer, draw_polygon, intermediate_points 
  37  from _FeatureSet import FeatureSet 
  38  from _GraphSet import GraphSet 
  39   
  40  from math import ceil, pi, cos, sin, asin 
  41   
42 -class CircularDrawer(AbstractDrawer):
43 """ CircularDrawer(AbstractDrawer) 44 45 Inherits from: 46 47 o AbstractDrawer 48 49 Provides: 50 51 Methods: 52 53 o __init__(self, parent=None, pagesize='A3', orientation='landscape', 54 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 55 start=None, end=None, tracklines=0, track_size=0.75, 56 circular=1) 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_greytrack(self, track) Return a grey background and superposed 91 label for the passed track as drawing 92 elements 93 94 o draw_feature_set(self, set) Return the features in the passed set as 95 drawing elements 96 97 o draw_feature(self, feature) Return a single feature as drawing 98 elements 99 100 o get_feature_sigil(self, feature, x0, x1, fragment) Return a single 101 feature as its sigil in drawing elements 102 103 o draw_graph_set(self, set) Return the data in a set of graphs as 104 drawing elements 105 106 o draw_line_graph(self, graph) Return the data in a graph as a line 107 graph in drawing elements 108 109 o draw_heat_graph(self, graph) Return the data in a graph as a heat 110 graph in drawing elements 111 112 o draw_bar_graph(self, graph) Return the data in a graph as a bar 113 graph in drawing elements 114 115 o canvas_angle(self, base) Return the angle, and cos and sin of 116 that angle, subtended by the passed 117 base position at the diagram center 118 119 o draw_arc(self, inner_radius, outer_radius, startangle, endangle, 120 color) Return a drawable element describing an arc 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 Size of sequence to be drawn 149 150 o track_size Float (0->1) the proportion of the track height to 151 draw in 152 153 o drawing Drawing canvas 154 155 o drawn_tracks List of ints denoting which tracks are to be drawn 156 157 o current_track_level Int denoting which track is currently being 158 drawn 159 160 o track_offsets Dictionary of number of pixels that each track top, 161 center and bottom is offset from the base of a 162 fragment, keyed by track 163 164 o sweep Float (0->1) the proportion of the circle circumference to 165 use for the diagram 166 167 """
168 - def __init__(self, parent=None, pagesize='A3', orientation='landscape', 169 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 170 start=None, end=None, tracklines=0, track_size=0.75, 171 circular=1):
172 """ __init__(self, parent, pagesize='A3', orientation='landscape', 173 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None, 174 start=None, end=None, tracklines=0, track_size=0.75, 175 circular=1) 176 177 o parent Diagram object containing the data that the drawer 178 draws 179 180 o pagesize String describing the ISO size of the image, or a tuple 181 of pixels 182 183 o orientation String describing the required orientation of the 184 final drawing ('landscape' or 'portrait') 185 186 o x Float (0->1) describing the relative size of the X 187 margins to the page 188 189 o y Float (0->1) describing the relative size of the Y 190 margins to the page 191 192 o xl Float (0->1) describing the relative size of the left X 193 margin to the page (overrides x) 194 195 o xl Float (0->1) describing the relative size of the left X 196 margin to the page (overrides x) 197 198 o xr Float (0->1) describing the relative size of the right X 199 margin to the page (overrides x) 200 201 o yt Float (0->1) describing the relative size of the top Y 202 margin to the page (overrides y) 203 204 o yb Float (0->1) describing the relative size of the lower Y 205 margin to the page (overrides y) 206 207 o start Int, the position to begin drawing the diagram at 208 209 o end Int, the position to stop drawing the diagram at 210 211 o tracklines Boolean flag to show (or not) lines delineating tracks 212 on the diagram 213 214 o track_size The proportion of the available track height that 215 should be taken up in drawing 216 217 o circular Boolean flaw to show whether the passed sequence is 218 circular or not 219 """ 220 # Use the superclass' instantiation method 221 AbstractDrawer.__init__(self, parent, pagesize, orientation, 222 x, y, xl, xr, yt, yb, start, end, 223 tracklines) 224 225 # Useful measurements on the page 226 self.track_size = track_size 227 if circular == False: # Determine the proportion of the circumference 228 self.sweep = 0.9 # around which information will be drawn 229 else: 230 self.sweep = 1
231 232
233 - def set_track_heights(self):
234 """ set_track_heights(self) 235 236 Since tracks may not be of identical heights, the bottom and top 237 radius for each track is stored in a dictionary - self.track_radii, 238 keyed by track number 239 """ 240 top_track = max(self.drawn_tracks) # The 'highest' track to draw 241 242 trackunit_sum = 0 # Holds total number of 'units' taken up by all tracks 243 trackunits = {} # Holds start and end units for each track keyed by track number 244 heightholder = 0 # placeholder variable 245 for track in range(1, top_track+1): # track numbers to 'draw' 246 try: 247 trackheight = self._parent[track].height # Get track height 248 except: 249 trackheight = 1 # ...or default to 1 250 trackunit_sum += trackheight # increment total track unit height 251 trackunits[track] = (heightholder, heightholder+trackheight) 252 heightholder += trackheight # move to next height 253 trackunit_height = 0.5*min(self.pagewidth, self.pageheight)/trackunit_sum 254 255 # Calculate top and bottom radii for each track 256 self.track_radii = {} # The inner, outer and center radii for each track 257 track_crop = trackunit_height*(1-self.track_size)/2. # 'step back' in pixels 258 for track in trackunits: 259 top = trackunits[track][1]*trackunit_height-track_crop 260 btm = trackunits[track][0]*trackunit_height+track_crop 261 ctr = btm+(top-btm)/2. 262 self.track_radii[track] = (btm, ctr, top)
263
264 - def draw(self):
265 """ draw(self) 266 267 Draw a circular diagram of the stored data 268 """ 269 # Instantiate the drawing canvas 270 self.drawing = Drawing(self.pagesize[0], self.pagesize[1]) 271 272 feature_elements = [] # holds feature elements 273 feature_labels = [] # holds feature labels 274 greytrack_bgs = [] # holds track background 275 greytrack_labels = [] # holds track foreground labels 276 scale_axes = [] # holds scale axes 277 scale_labels = [] # holds scale axis labels 278 279 # Get tracks to be drawn and set track sizes 280 self.drawn_tracks = self._parent.get_drawn_levels() 281 self.set_track_heights() 282 283 # Go through each track in the parent (if it is to be drawn) one by 284 # one and collate the data as drawing elements 285 for track_level in self._parent.get_drawn_levels(): 286 self.current_track_level = track_level 287 track = self._parent[track_level] 288 gbgs, glabels = self.draw_greytrack(track) # Greytracks 289 greytrack_bgs.append(gbgs) 290 greytrack_labels.append(glabels) 291 features, flabels = self.draw_track(track) # Features and graphs 292 feature_elements.append(features) 293 feature_labels.append(flabels) 294 if track.scale: 295 axes, slabels = self.draw_scale(track) # Scale axes 296 scale_axes.append(axes) 297 scale_labels.append(slabels) 298 299 # Groups listed in order of addition to page (from back to front) 300 # Draw track backgrounds 301 # Draw features and graphs 302 # Draw scale axes 303 # Draw scale labels 304 # Draw feature labels 305 # Draw track labels 306 element_groups = [greytrack_bgs, feature_elements, 307 scale_axes, scale_labels, 308 feature_labels, greytrack_labels 309 ] 310 for element_group in element_groups: 311 for element_list in element_group: 312 [self.drawing.add(element) for element in element_list] 313 314 if self.tracklines: # Draw test tracks over top of diagram 315 self.draw_test_tracks()
316 317
318 - def draw_track(self, track):
319 """ draw_track(self, track) -> ([element, element,...], [element, element,...]) 320 321 o track Track object 322 323 Return tuple of (list of track elements, list of track labels) 324 """ 325 track_elements = [] # Holds elements for features and graphs 326 track_labels = [] # Holds labels for features and graphs 327 328 # Distribution dictionary for dealing with different set types 329 set_methods = {FeatureSet: self.draw_feature_set, 330 GraphSet: self.draw_graph_set 331 } 332 333 for set in track.get_sets(): # Draw the feature or graph sets 334 elements, labels = set_methods[set.__class__](set) 335 track_elements += elements 336 track_labels += labels 337 return track_elements, track_labels
338 339
340 - def draw_feature_set(self, set):
341 """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...]) 342 343 o set FeatureSet object 344 345 Returns a tuple (list of elements describing features, list of 346 labels for elements) 347 """ 348 #print 'draw feature set' 349 feature_elements = [] # Holds diagram elements belonging to the features 350 label_elements = [] # Holds diagram elements belonging to feature labels 351 352 # Collect all the elements for the feature set 353 for feature in set.get_features(): 354 if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end): 355 features, labels = self.draw_feature(feature) 356 feature_elements += features 357 label_elements += labels 358 359 return feature_elements, label_elements
360 361
362 - def draw_feature(self, feature):
363 """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...]) 364 365 o feature Feature containing location info 366 367 Returns tuple of (list of elements describing single feature, list 368 of labels for those elements) 369 """ 370 feature_elements = [] # Holds drawable elements for a single feature 371 label_elements = [] # Holds labels for a single feature 372 373 if feature.hide: # Don't show feature: return early 374 return feature_elements, label_elements 375 376 # A single feature may be split into subfeatures, so loop over them 377 for locstart, locend in feature.locations: 378 # Get sigil for the feature/ each subfeature 379 feature_sigil, label = self.get_feature_sigil(feature, locstart, locend) 380 feature_elements.append(feature_sigil) 381 if label is not None: # If there's a label 382 label_elements.append(label) 383 384 return feature_elements, label_elements
385 386
387 - def get_feature_sigil(self, feature, locstart, locend, **kwargs):
388 """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element) 389 390 o feature Feature object 391 392 o locstart The start position of the feature 393 394 o locend The end position of the feature 395 396 Returns a drawable indicator of the feature, and any required label 397 for it 398 """ 399 # Establish the co-ordinates for the sigil 400 btm, ctr, top = self.track_radii[self.current_track_level] 401 startangle, startcos, startsin = self.canvas_angle(locstart) 402 endangle, endcos, endsin = self.canvas_angle(locend) 403 midangle, midcos, midsin = self.canvas_angle(locend+locstart/2) 404 405 # Distribution dictionary for various ways of drawing the feature 406 # Each method takes the inner and outer radii, the start and end angle 407 # subtended at the diagram center, and the color as arguments 408 draw_methods = {'BOX': self._draw_arc, 409 'ARROW': self._draw_arc_arrow, 410 } 411 412 # Get sigil for the feature, location dependent on the feature strand 413 method = draw_methods[feature.sigil] 414 kwargs['head_length_ratio'] = feature.arrowhead_length 415 kwargs['shaft_height_ratio'] = feature.arrowshaft_height 416 417 #Support for clickable links... needs ReportLab 2.4 or later 418 #which added support for links in SVG output. 419 if hasattr(feature, "url") : 420 kwargs["hrefURL"] = feature.url 421 kwargs["hrefTitle"] = feature.name 422 423 if feature.color == colors.white: 424 border = colors.black 425 else: 426 border = feature.color 427 428 if feature.strand == 1: 429 sigil = method(ctr, top, startangle, endangle, feature.color, 430 border, orientation='right', **kwargs) 431 elif feature.strand == -1: 432 sigil = method(btm, ctr, startangle, endangle, feature.color, 433 border, orientation='left', **kwargs) 434 else: 435 sigil = method(btm, top, startangle, endangle, feature.color, 436 border, **kwargs) 437 438 if feature.label: # Feature needs a label 439 label = String(0, 0, feature.name.strip(), 440 fontName=feature.label_font, 441 fontSize=feature.label_size, 442 fillColor=feature.label_color) 443 labelgroup = Group(label) 444 label_angle = startangle + 0.5 * pi # Make text radial 445 sinval, cosval = startsin, startcos 446 if feature.strand != -1: 447 # Feature is on top, or covers both strands 448 if startangle < pi: # Turn text round and anchor end to inner radius 449 sinval, cosval = endsin, endcos 450 label_angle = endangle - 0.5 * pi 451 labelgroup.contents[0].textAnchor = 'end' 452 pos = self.xcenter+top*sinval 453 coslabel = cos(label_angle) 454 sinlabel = sin(label_angle) 455 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel, 456 pos, self.ycenter+top*cosval) 457 else: 458 # Feature on bottom strand 459 if startangle < pi: # Turn text round and anchor end to inner radius 460 sinval, cosval = endsin, endcos 461 label_angle = endangle - 0.5 * pi 462 else: 463 labelgroup.contents[0].textAnchor = 'end' 464 pos = self.xcenter+btm*sinval 465 coslabel = cos(label_angle) 466 sinlabel = sin(label_angle) 467 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel, 468 pos, self.ycenter+btm*cosval) 469 470 else: 471 labelgroup = None 472 #if locstart > locend: 473 # print locstart, locend, feature.strand, sigil, feature.name 474 #print locstart, locend, feature.name 475 return sigil, labelgroup
476 477 478
479 - def draw_graph_set(self, set):
480 """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...]) 481 482 o set GraphSet object 483 484 Returns tuple (list of graph elements, list of graph labels) 485 """ 486 #print 'draw graph set' 487 elements = [] # Holds graph elements 488 489 # Distribution dictionary for how to draw the graph 490 style_methods = {'line': self.draw_line_graph, 491 'heat': self.draw_heat_graph, 492 'bar': self.draw_bar_graph 493 } 494 495 for graph in set.get_graphs(): 496 #print graph.name 497 elements += style_methods[graph.style](graph) 498 499 return elements, []
500 501
502 - def draw_line_graph(self, graph):
503 """ draw_line_graph(self, graph, center) -> [element, element,...] 504 505 o graph GraphData object 506 507 Returns a line graph as a list of drawable elements 508 """ 509 #print '\tdraw_line_graph' 510 line_elements = [] # holds drawable elements 511 512 # Get graph data 513 data_quartiles = graph.quartiles() 514 minval, maxval = data_quartiles[0],data_quartiles[4] 515 btm, ctr, top = self.track_radii[self.current_track_level] 516 trackheight = 0.5*(top-btm) 517 datarange = maxval - minval 518 if datarange == 0: 519 datarange = trackheight 520 data = graph[self.start:self.end] 521 522 # midval is the value at which the x-axis is plotted, and is the 523 # central ring in the track 524 if graph.center is None: 525 midval = (maxval + minval)/2. 526 else: 527 midval = graph.center 528 # Whichever is the greatest difference: max-midval or min-midval, is 529 # taken to specify the number of pixel units resolved along the 530 # y-axis 531 resolution = max((midval-minval), (maxval-midval)) 532 533 # Start from first data point 534 pos, val = data[0] 535 lastangle, lastcos, lastsin = self.canvas_angle(pos) 536 # We calculate the track height 537 posheight = trackheight*(val-midval)/resolution + ctr 538 lastx = self.xcenter+posheight*lastsin # start xy coords 539 lasty = self.ycenter+posheight*lastcos 540 for pos, val in data: 541 posangle, poscos, possin = self.canvas_angle(pos) 542 posheight = trackheight*(val-midval)/resolution + ctr 543 x = self.xcenter+posheight*possin # next xy coords 544 y = self.ycenter+posheight*poscos 545 line_elements.append(Line(lastx, lasty, x, y, 546 strokeColor = graph.poscolor, 547 strokeWidth = graph.linewidth)) 548 lastx, lasty, = x, y 549 return line_elements
550 551
552 - def draw_bar_graph(self, graph):
553 """ draw_bar_graph(self, graph) -> [element, element,...] 554 555 o graph Graph object 556 557 Returns a list of drawable elements for a bar graph of the passed 558 Graph object 559 """ 560 #print '\tdraw_bar_graph' 561 # At each point contained in the graph data, we draw a vertical bar 562 # from the track center to the height of the datapoint value (positive 563 # values go up in one color, negative go down in the alternative 564 # color). 565 bar_elements = [] 566 567 # Set the number of pixels per unit for the data 568 data_quartiles = graph.quartiles() 569 minval, maxval = data_quartiles[0],data_quartiles[4] 570 btm, ctr, top = self.track_radii[self.current_track_level] 571 trackheight = 0.5*(top-btm) 572 datarange = maxval - minval 573 if datarange == 0: 574 datarange = trackheight 575 data = graph[self.start:self.end] 576 # midval is the value at which the x-axis is plotted, and is the 577 # central ring in the track 578 if graph.center is None: 579 midval = (maxval + minval)/2. 580 else: 581 midval = graph.center 582 583 # Convert data into 'binned' blocks, covering half the distance to the 584 # next data point on either side, accounting for the ends of fragments 585 # and tracks 586 newdata = intermediate_points(self.start, self.end, 587 graph[self.start:self.end]) 588 589 # Whichever is the greatest difference: max-midval or min-midval, is 590 # taken to specify the number of pixel units resolved along the 591 # y-axis 592 resolution = max((midval-minval), (maxval-midval)) 593 if resolution == 0: 594 resolution = trackheight 595 596 # Create elements for the bar graph based on newdata 597 for pos0, pos1, val in newdata: 598 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0) 599 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1) 600 601 barval = trackheight*(val-midval)/resolution 602 if barval >=0: 603 barcolor = graph.poscolor 604 else: 605 barcolor = graph.negcolor 606 607 # Draw bar 608 bar_elements.append(self._draw_arc(ctr, ctr+barval, pos0angle, 609 pos1angle, barcolor)) 610 return bar_elements
611 612 613 614
615 - def draw_heat_graph(self, graph):
616 """ draw_heat_graph(self, graph) -> [element, element,...] 617 618 o graph Graph object 619 620 Returns a list of drawable elements for the heat graph 621 """ 622 #print '\tdraw_heat_graph' 623 # At each point contained in the graph data, we draw a box that is the 624 # full height of the track, extending from the midpoint between the 625 # previous and current data points to the midpoint between the current 626 # and next data points 627 heat_elements = [] # holds drawable elements 628 629 # Get graph data 630 data_quartiles = graph.quartiles() 631 minval, maxval = data_quartiles[0],data_quartiles[4] 632 midval = (maxval + minval)/2. # mid is the value at the X-axis 633 btm, ctr, top = self.track_radii[self.current_track_level] 634 trackheight = (top-btm) 635 newdata = intermediate_points(self.start, self.end, 636 graph[self.start:self.end]) 637 638 # Create elements on the graph, indicating a large positive value by 639 # the graph's poscolor, and a large negative value by the graph's 640 # negcolor attributes 641 for pos0, pos1, val in newdata: 642 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0) 643 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1) 644 645 # Calculate the heat color, based on the differential between 646 # the value and the median value 647 heat = colors.linearlyInterpolatedColor(graph.poscolor, 648 graph.negcolor, 649 maxval, minval, val) 650 651 # Draw heat box 652 heat_elements.append(self._draw_arc(btm, top, pos0angle, pos1angle, 653 heat, border=heat)) 654 return heat_elements
655 656
657 - def draw_scale(self, track):
658 """ draw_scale(self, track) -> ([element, element,...], [element, element,...]) 659 660 o track Track object 661 662 Returns a tuple of (list of elements in the scale, list of labels 663 in the scale) 664 """ 665 scale_elements = [] # holds axes and ticks 666 scale_labels = [] # holds labels 667 668 if not track.scale: # no scale required, exit early 669 return [], [] 670 671 # Get track locations 672 btm, ctr, top = self.track_radii[self.current_track_level] 673 trackheight = (top-ctr) 674 675 # X-axis 676 if self.sweep < 1: 677 #Draw an arc, leaving out the wedge 678 p = ArcPath(strokeColor=track.scale_color, fillColor=None) 679 #Note reportlab counts angles anti-clockwise from the horizontal 680 #(as in mathematics, e.g. complex numbers and polar coordinates) 681 #in degrees. 682 p.addArc(self.xcenter, self.ycenter, ctr, 683 startangledegrees=90-360*self.sweep, 684 endangledegrees=90) 685 scale_elements.append(p) 686 del p 687 else: 688 #Draw a full circle 689 scale_elements.append(Circle(self.xcenter, self.ycenter, ctr, 690 strokeColor=track.scale_color, 691 fillColor=None)) 692 693 if track.scale_ticks: # Ticks are required on the scale 694 # Draw large ticks 695 #I want the ticks to be consistently positioned relative to 696 #the start of the sequence (position 0), not relative to the 697 #current viewpoint (self.start and self.end) 698 699 ticklen = track.scale_largeticks * trackheight 700 tickiterval = int(track.scale_largetick_interval) 701 #Note that we could just start the list of ticks using 702 #range(0,self.end,tickinterval) and the filter out the 703 #ones before self.start - but this seems wasteful. 704 #Using tickiterval * (self.start/tickiterval) is a shortcut. 705 largeticks = [pos for pos \ 706 in range(tickiterval * (self.start/tickiterval), 707 int(self.end), 708 tickiterval) \ 709 if pos >= self.start] 710 for tickpos in largeticks: 711 tick, label = self.draw_tick(tickpos, ctr, ticklen, 712 track, 713 track.scale_largetick_labels) 714 scale_elements.append(tick) 715 if label is not None: # If there's a label, add it 716 scale_labels.append(label) 717 # Draw small ticks 718 ticklen = track.scale_smallticks * trackheight 719 tickiterval = int(track.scale_smalltick_interval) 720 smallticks = [pos for pos \ 721 in range(tickiterval * (self.start/tickiterval), 722 int(self.end), 723 tickiterval) \ 724 if pos >= self.start] 725 for tickpos in smallticks: 726 tick, label = self.draw_tick(tickpos, ctr, ticklen, 727 track, 728 track.scale_smalltick_labels) 729 scale_elements.append(tick) 730 if label is not None: # If there's a label, add it 731 scale_labels.append(label) 732 733 # Check to see if the track contains a graph - if it does, get the 734 # minimum and maximum values, and put them on the scale Y-axis 735 # at 60 degree intervals, ordering the labels by graph_id 736 if track.axis_labels: 737 for set in track.get_sets(): 738 if set.__class__ is GraphSet: 739 # Y-axis 740 for n in xrange(7): 741 angle = n * 1.0471975511965976 742 ticksin, tickcos = sin(angle), cos(angle) 743 x0, y0 = self.xcenter+btm*ticksin, self.ycenter+btm*tickcos 744 x1, y1 = self.xcenter+top*ticksin, self.ycenter+top*tickcos 745 scale_elements.append(Line(x0, y0, x1, y1, 746 strokeColor=track.scale_color)) 747 748 graph_label_min = [] 749 graph_label_max = [] 750 graph_label_mid = [] 751 for graph in set.get_graphs(): 752 quartiles = graph.quartiles() 753 minval, maxval = quartiles[0], quartiles[4] 754 if graph.center is None: 755 midval = (maxval + minval)/2. 756 graph_label_min.append("%.3f" % minval) 757 graph_label_max.append("%.3f" % maxval) 758 graph_label_mid.append("%.3f" % midval) 759 else: 760 diff = max((graph.center-minval), 761 (maxval-graph.center)) 762 minval = graph.center-diff 763 maxval = graph.center+diff 764 midval = graph.center 765 graph_label_mid.append("%.3f" % midval) 766 graph_label_min.append("%.3f" % minval) 767 graph_label_max.append("%.3f" % maxval) 768 xmid, ymid = (x0+x1)/2., (y0+y1)/2. 769 for limit, x, y, in [(graph_label_min, x0, y0), 770 (graph_label_max, x1, y1), 771 (graph_label_mid, xmid, ymid)]: 772 label = String(0, 0, ";".join(limit), 773 fontName=track.scale_font, 774 fontSize=track.scale_fontsize, 775 fillColor=track.scale_color) 776 label.textAnchor = 'middle' 777 labelgroup = Group(label) 778 labelgroup.transform = (tickcos, -ticksin, 779 ticksin, tickcos, 780 x, y) 781 scale_labels.append(labelgroup) 782 783 return scale_elements, scale_labels
784 785
786 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
787 """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element) 788 789 o tickpos Int, position of the tick on the sequence 790 791 o ctr Float, Y co-ord of the center of the track 792 793 o ticklen How long to draw the tick 794 795 o track Track, the track the tick is drawn on 796 797 o draw_label Boolean, write the tick label? 798 799 Returns a drawing element that is the tick on the scale 800 """ 801 # Calculate tick co-ordinates 802 tickangle, tickcos, ticksin = self.canvas_angle(tickpos) 803 x0, y0 = self.xcenter+ctr*ticksin, self.ycenter+ctr*tickcos 804 x1, y1 = self.xcenter+(ctr+ticklen)*ticksin, self.ycenter+(ctr+ticklen)*tickcos 805 # Calculate height of text label so it can be offset on lower half 806 # of diagram 807 # LP: not used, as not all fonts have ascent_descent data in reportlab.pdfbase._fontdata 808 #label_offset = _fontdata.ascent_descent[track.scale_font][0]*\ 809 # track.scale_fontsize/1000. 810 tick = Line(x0, y0, x1, y1, strokeColor=track.scale_color) 811 if draw_label: # Put tick position on as label 812 if track.scale_format == 'SInt': 813 if tickpos >= 1000000: 814 tickstring = str(tickpos/1000000) + " Mbp" 815 elif tickpos >= 1000: 816 tickstring = str(tickpos/1000) + " Kbp" 817 else: 818 tickstring = str(tickpos) 819 else: 820 tickstring = str(tickpos) 821 label = String(0, 0, tickstring, # Make label string 822 fontName=track.scale_font, 823 fontSize=track.scale_fontsize, 824 fillColor=track.scale_color) 825 if tickangle > pi: 826 label.textAnchor = 'end' 827 # LP: This label_offset depends on ascent_descent data, which is not available for all 828 # fonts, so has been deprecated. 829 #if 0.5*pi < tickangle < 1.5*pi: 830 # y1 -= label_offset 831 labelgroup = Group(label) 832 labelgroup.transform = (1,0,0,1, x1, y1) 833 else: 834 labelgroup = None 835 return tick, labelgroup
836 837
838 - def draw_test_tracks(self):
839 """ draw_test_tracks(self) 840 841 Draw blue ones indicating tracks to be drawn, with a green line 842 down the center. 843 """ 844 #print 'drawing test tracks' 845 # Add lines only for drawn tracks 846 for track in self.drawn_tracks: 847 btm, ctr, top = self.track_radii[track] 848 self.drawing.add(Circle(self.xcenter, self.ycenter, top, 849 strokeColor=colors.blue, 850 fillColor=None)) # top line 851 self.drawing.add(Circle(self.xcenter, self.ycenter, ctr, 852 strokeColor=colors.green, 853 fillColor=None)) # middle line 854 self.drawing.add(Circle(self.xcenter, self.ycenter, btm, 855 strokeColor=colors.blue, 856 fillColor=None)) # bottom line
857 858
859 - def draw_greytrack(self, track):
860 """ draw_greytrack(self) 861 862 o track Track object 863 864 Put in a grey background to the current track, if the track 865 specifies that we should 866 """ 867 greytrack_bgs = [] # Holds track backgrounds 868 greytrack_labels = [] # Holds track foreground labels 869 870 if not track.greytrack: # No greytrack required, return early 871 return [], [] 872 873 # Get track location 874 btm, ctr, top = self.track_radii[self.current_track_level] 875 876 # Make background 877 if self.sweep < 1: 878 #Make a partial circle, a large arc box 879 #This method assumes the correct center for us. 880 bg = self._draw_arc(btm, top, 0, 2*pi*self.sweep, 881 colors.Color(0.96, 0.96, 0.96)) 882 else: 883 #Make a full circle (using a VERY thick linewidth) 884 bg = Circle(self.xcenter, self.ycenter, ctr, 885 strokeColor = colors.Color(0.96, 0.96, 0.96), 886 fillColor=None, strokeWidth=top-btm) 887 greytrack_bgs.append(bg) 888 889 if track.greytrack_labels: # Labels are required for this track 890 labelstep = self.length/track.greytrack_labels # label interval 891 for pos in range(self.start, self.end, int(labelstep)): 892 label = String(0, 0, track.name, # Add a new label at 893 fontName=track.greytrack_font, # each interval 894 fontSize=track.greytrack_fontsize, 895 fillColor=track.greytrack_fontcolor) 896 theta, costheta, sintheta = self.canvas_angle(pos) 897 x,y = self.xcenter+btm*sintheta, self.ycenter+btm*costheta # start text halfway up marker 898 labelgroup = Group(label) 899 labelangle = self.sweep*2*pi*(pos-self.start)/self.length - pi/2 900 if theta > pi: 901 label.textAnchor = 'end' # Anchor end of text to inner radius 902 labelangle += pi # and reorient it 903 cosA, sinA = cos(labelangle), sin(labelangle) 904 labelgroup.transform = (cosA, -sinA, sinA, 905 cosA, x, y) 906 if not self.length-x <= labelstep: # Don't overrun the circle 907 greytrack_labels.append(labelgroup) 908 909 return greytrack_bgs, greytrack_labels
910 911
912 - def canvas_angle(self, base):
913 """ canvas_angle(self, base) -> (float, float, float) 914 """ 915 angle = self.sweep*2*pi*(base-self.start)/self.length 916 return (angle, cos(angle), sin(angle))
917 918
919 - def _draw_arc(self, inner_radius, outer_radius, startangle, endangle, 920 color, border=None, colour=None, **kwargs):
921 """ draw_arc(self, inner_radius, outer_radius, startangle, endangle, color) 922 -> Group 923 924 o inner_radius Float distance of inside of arc from drawing center 925 926 o outer_radius Float distance of outside of arc from drawing center 927 928 o startangle Float angle subtended by start of arc at drawing center 929 (in radians) 930 931 o endangle Float angle subtended by end of arc at drawing center 932 (in radians) 933 934 o color colors.Color object for arc (overridden by backwards 935 compatible argument with UK spelling, colour). 936 937 Returns a closed path object describing an arced box corresponding to 938 the passed values. For very small angles, a simple four sided 939 polygon is used. 940 """ 941 #Let the UK spelling (colour) override the USA spelling (color) 942 if colour is not None: 943 color = colour 944 945 if border is None: 946 border = color 947 948 if color is None: 949 color = colour 950 if color == colors.white and border is None: # Force black border on 951 strokecolor = colors.black # white boxes with 952 elif border is None: # undefined border, else 953 strokecolor = color # use fill colour 954 elif border is not None: 955 strokecolor = border 956 957 if abs(float(endangle - startangle))>.01: 958 # Wide arc, must use full curves 959 p = ArcPath(strokeColor=strokecolor, 960 fillColor=color, 961 strokewidth=0) 962 #Note reportlab counts angles anti-clockwise from the horizontal 963 #(as in mathematics, e.g. complex numbers and polar coordinates) 964 #but we use clockwise from the vertical. Also reportlab uses 965 #degrees, but we use radians. 966 p.addArc(self.xcenter, self.ycenter, inner_radius, 967 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi), 968 moveTo=True) 969 p.addArc(self.xcenter, self.ycenter, outer_radius, 970 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi), 971 reverse=True) 972 p.closePath() 973 return p 974 else: 975 #Cheat and just use a four sided polygon. 976 # Calculate trig values for angle and coordinates 977 startcos, startsin = cos(startangle), sin(startangle) 978 endcos, endsin = cos(endangle), sin(endangle) 979 x0,y0 = self.xcenter, self.ycenter # origin of the circle 980 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos) 981 x2,y2 = (x0+inner_radius*endsin, y0+inner_radius*endcos) 982 x3,y3 = (x0+outer_radius*endsin, y0+outer_radius*endcos) 983 x4,y4 = (x0+outer_radius*startsin, y0+outer_radius*startcos) 984 return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border)
985
986 - def _draw_arc_arrow(self, inner_radius, outer_radius, startangle, endangle, 987 color, border=None, 988 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right', 989 colour=None, **kwargs):
990 """Draw an arrow along an arc.""" 991 #Let the UK spelling (colour) override the USA spelling (color) 992 if colour is not None: 993 color = colour 994 995 if border is None: 996 border = color 997 998 if color is None: 999 color = colour 1000 if color == colors.white and border is None: # Force black border on 1001 strokecolor = colors.black # white boxes with 1002 elif border is None: # undefined border, else 1003 strokecolor = color # use fill colour 1004 elif border is not None: 1005 strokecolor = border 1006 1007 #if orientation == 'right': 1008 # startangle, endangle = min(startangle, endangle), max(startangle, endangle) 1009 #elif orientation == 'left': 1010 # startangle, endangle = max(startangle, endangle), min(startangle, endangle) 1011 #else: 1012 startangle, endangle = min(startangle, endangle), max(startangle, endangle) 1013 if orientation <> "left" and orientation <> "right": 1014 raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \ 1015 % repr(orientation)) 1016 1017 angle = float(endangle - startangle) # angle subtended by arc 1018 middle_radius = 0.5*(inner_radius+outer_radius) 1019 boxheight = outer_radius - inner_radius 1020 shaft_height = boxheight*shaft_height_ratio 1021 shaft_inner_radius = middle_radius - 0.5*shaft_height 1022 shaft_outer_radius = middle_radius + 0.5*shaft_height 1023 headangle_delta = max(0.0,min(abs(boxheight)*head_length_ratio/middle_radius, abs(angle))) 1024 if angle < 0: 1025 headangle_delta *= -1 #reverse it 1026 if orientation=="right": 1027 headangle = endangle-headangle_delta 1028 else: 1029 headangle = startangle+headangle_delta 1030 if startangle <= endangle: 1031 headangle = max(min(headangle, endangle), startangle) 1032 else: 1033 headangle = max(min(headangle, startangle), endangle) 1034 assert startangle <= headangle <= endangle \ 1035 or endangle <= headangle <= startangle, \ 1036 (startangle, headangle, endangle, angle) 1037 1038 1039 # Calculate trig values for angle and coordinates 1040 startcos, startsin = cos(startangle), sin(startangle) 1041 headcos, headsin = cos(headangle), sin(headangle) 1042 endcos, endsin = cos(endangle), sin(endangle) 1043 x0,y0 = self.xcenter, self.ycenter # origin of the circle 1044 if 0.5 >= abs(angle) and abs(headangle_delta) >= abs(angle): 1045 #If the angle is small, and the arrow is all head, 1046 #cheat and just use a triangle. 1047 if orientation=="right": 1048 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos) 1049 x2,y2 = (x0+outer_radius*startsin, y0+outer_radius*startcos) 1050 x3,y3 = (x0+middle_radius*endsin, y0+middle_radius*endcos) 1051 else: 1052 x1,y1 = (x0+inner_radius*endsin, y0+inner_radius*endcos) 1053 x2,y2 = (x0+outer_radius*endsin, y0+outer_radius*endcos) 1054 x3,y3 = (x0+middle_radius*startsin, y0+middle_radius*startcos) 1055 #return draw_polygon([(x1,y1),(x2,y2),(x3,y3)], color, border, 1056 # stroke_line_join=1) 1057 return Polygon([x1,y1,x2,y2,x3,y3], 1058 strokeColor=border or color, 1059 fillColor=color, 1060 strokeLineJoin=1, #1=round, not mitre! 1061 strokewidth=0) 1062 elif orientation=="right": 1063 p = ArcPath(strokeColor=strokecolor, 1064 fillColor=color, 1065 #default is mitre/miter which can stick out too much: 1066 strokeLineJoin=1, #1=round 1067 strokewidth=0, 1068 **kwargs) 1069 #Note reportlab counts angles anti-clockwise from the horizontal 1070 #(as in mathematics, e.g. complex numbers and polar coordinates) 1071 #but we use clockwise from the vertical. Also reportlab uses 1072 #degrees, but we use radians. 1073 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius, 1074 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi), 1075 moveTo=True) 1076 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius, 1077 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi), 1078 reverse=True) 1079 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos) 1080 if abs(angle) < 0.5: 1081 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos) 1082 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos) 1083 else: 1084 dx = min(0.1, abs(angle)/50.0) #auto-scale number of steps 1085 x = dx 1086 while x < 1: 1087 r = outer_radius - x*(outer_radius-middle_radius) 1088 a = headangle + x*(endangle-headangle) 1089 p.lineTo(x0+r*sin(a), y0+r*cos(a)) 1090 x += dx 1091 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos) 1092 x = dx 1093 while x < 1: 1094 r = middle_radius - x*(middle_radius-inner_radius) 1095 a = headangle + (1-x)*(endangle-headangle) 1096 p.lineTo(x0+r*sin(a), y0+r*cos(a)) 1097 x += dx 1098 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos) 1099 p.closePath() 1100 return p 1101 else: 1102 p = ArcPath(strokeColor=strokecolor, 1103 fillColor=color, 1104 #default is mitre/miter which can stick out too much: 1105 strokeLineJoin=1, #1=round 1106 strokewidth=0, 1107 **kwargs) 1108 #Note reportlab counts angles anti-clockwise from the horizontal 1109 #(as in mathematics, e.g. complex numbers and polar coordinates) 1110 #but we use clockwise from the vertical. Also reportlab uses 1111 #degrees, but we use radians. 1112 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius, 1113 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi), 1114 moveTo=True, reverse=True) 1115 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius, 1116 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi), 1117 reverse=False) 1118 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos) 1119 #TODO - two staight lines is only a good approximation for small 1120 #head angle, in general will need to curved lines here: 1121 if abs(angle) < 0.5: 1122 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos) 1123 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos) 1124 else: 1125 dx = min(0.1, abs(angle)/50.0) #auto-scale number of steps 1126 x = dx 1127 while x < 1: 1128 r = outer_radius - x*(outer_radius-middle_radius) 1129 a = headangle + x*(startangle-headangle) 1130 p.lineTo(x0+r*sin(a), y0+r*cos(a)) 1131 x += dx 1132 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos) 1133 x = dx 1134 while x < 1: 1135 r = middle_radius - x*(middle_radius-inner_radius) 1136 a = headangle + (1-x)*(startangle-headangle) 1137 p.lineTo(x0+r*sin(a), y0+r*cos(a)) 1138 x += dx 1139 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos) 1140 p.closePath() 1141 return p
1142