Back to index

python-biopython  1.60
_CircularDrawer.py
Go to the documentation of this file.
00001 # Copyright 2003-2008 by Leighton Pritchard.  All rights reserved.
00002 # Revisions copyright 2008-2009 by Peter Cock.
00003 # This code is part of the Biopython distribution and governed by its
00004 # license.  Please see the LICENSE file that should have been included
00005 # as part of this package.
00006 #
00007 # Contact:       Leighton Pritchard, Scottish Crop Research Institute,
00008 #                Invergowrie, Dundee, Scotland, DD2 5DA, UK
00009 #                L.Pritchard@scri.ac.uk
00010 ################################################################################
00011 
00012 """ CircularDrawer module
00013 
00014     Provides:
00015 
00016     o CircularDrawer -  Drawing object for circular diagrams
00017 
00018     For drawing capabilities, this module uses reportlab to draw and write
00019     the diagram:
00020 
00021     http://www.reportlab.com
00022 
00023     For dealing with biological information, the package expects BioPython
00024     objects:
00025 
00026     http://www.biopython.org
00027 """
00028 
00029 # ReportLab imports
00030 from reportlab.graphics.shapes import *
00031 from reportlab.lib import colors
00032 from reportlab.pdfbase import _fontdata
00033 from reportlab.graphics.shapes import ArcPath
00034 
00035 # GenomeDiagram imports
00036 from _AbstractDrawer import AbstractDrawer, draw_polygon, intermediate_points
00037 from _FeatureSet import FeatureSet
00038 from _GraphSet import GraphSet
00039 
00040 from math import ceil, pi, cos, sin, asin
00041 
00042 class CircularDrawer(AbstractDrawer):
00043     """ CircularDrawer(AbstractDrawer)
00044 
00045         Inherits from:
00046 
00047         o AbstractDrawer
00048 
00049         Provides:
00050 
00051         Methods:
00052 
00053         o __init__(self, ...) Called on instantiation
00054 
00055         o set_page_size(self, pagesize, orientation)    Set the page size to the
00056                                                     passed size and orientation
00057 
00058         o set_margins(self, x, y, xl, xr, yt, yb)   Set the drawable area of the
00059                                                     page
00060 
00061         o set_bounds(self, start, end)  Set the bounds for the elements to be
00062                                         drawn
00063 
00064         o is_in_bounds(self, value)     Returns a boolean for whether the position
00065                                         is actually to be drawn
00066 
00067         o __len__(self)     Returns the length of sequence that will be drawn
00068 
00069 
00070         o draw(self)    Place the drawing elements on the diagram
00071 
00072         o init_fragments(self)  Calculate information
00073                                 about sequence fragment locations on the drawing
00074 
00075         o set_track_heights(self)   Calculate information about the offset of
00076                                     each track from the fragment base
00077                                     
00078         o draw_test_tracks(self)    Add lines demarcating each track to the
00079                                     drawing
00080 
00081         o draw_track(self, track)   Return the contents of the passed track as
00082                                     drawing elements
00083 
00084         o draw_scale(self, track)   Return a scale for the passed track as
00085                                     drawing elements
00086 
00087         o draw_greytrack(self, track)   Return a grey background and superposed
00088                                         label for the passed track as drawing
00089                                         elements
00090 
00091         o draw_feature_set(self, set)   Return the features in the passed set as
00092                                         drawing elements
00093 
00094         o draw_feature(self, feature)   Return a single feature as drawing
00095                                         elements
00096 
00097         o get_feature_sigil(self, feature, x0, x1, fragment)    Return a single
00098                                         feature as its sigil in drawing elements
00099 
00100         o draw_graph_set(self, set)     Return the data in a set of graphs as
00101                                         drawing elements
00102 
00103         o draw_line_graph(self, graph)  Return the data in a graph as a line
00104                                         graph in drawing elements
00105 
00106         o draw_heat_graph(self, graph)  Return the data in a graph as a heat
00107                                         graph in drawing elements
00108 
00109         o draw_bar_graph(self, graph)   Return the data in a graph as a bar
00110                                         graph in drawing elements
00111 
00112         o canvas_angle(self, base)      Return the angle, and cos and sin of
00113                                         that angle, subtended by the passed
00114                                         base position at the diagram center
00115 
00116         o draw_arc(self, inner_radius, outer_radius, startangle, endangle,
00117                     color)    Return a drawable element describing an arc
00118 
00119         Attributes:
00120 
00121         o tracklines    Boolean for whether to draw lines dilineating tracks
00122 
00123         o pagesize      Tuple describing the size of the page in pixels
00124 
00125         o x0            Float X co-ord for leftmost point of drawable area
00126 
00127         o xlim          Float X co-ord for rightmost point of drawable area
00128 
00129         o y0            Float Y co-ord for lowest point of drawable area
00130 
00131         o ylim          Float Y co-ord for topmost point of drawable area
00132 
00133         o pagewidth     Float pixel width of drawable area
00134 
00135         o pageheight    Float pixel height of drawable area
00136 
00137         o xcenter       Float X co-ord of center of drawable area
00138 
00139         o ycenter       Float Y co-ord of center of drawable area
00140 
00141         o start         Int, base to start drawing from
00142 
00143         o end           Int, base to stop drawing at
00144 
00145         o length        Size of sequence to be drawn
00146 
00147         o track_size    Float (0->1) the proportion of the track height to
00148                         draw in
00149 
00150         o drawing       Drawing canvas
00151 
00152         o drawn_tracks  List of ints denoting which tracks are to be drawn
00153 
00154         o current_track_level   Int denoting which track is currently being
00155                                 drawn
00156 
00157         o track_offsets     Dictionary of number of pixels that each track top,
00158                             center and bottom is offset from the base of a
00159                             fragment, keyed by track
00160 
00161         o sweep     Float (0->1) the proportion of the circle circumference to
00162                     use for the diagram
00163 
00164         o cross_track_links List of tuples each with four entries (track A,
00165                             feature A, track B, feature B) to be linked.
00166     """
00167     def __init__(self, parent=None, pagesize='A3', orientation='landscape',
00168                  x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
00169                  start=None, end=None, tracklines=0, track_size=0.75,
00170                  circular=1, circle_core=0.0, cross_track_links=None):
00171         """ __init__(self, parent, pagesize='A3', orientation='landscape',
00172                      x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
00173                      start=None, end=None, tracklines=0, track_size=0.75,
00174                      circular=1)
00175 
00176             o parent    Diagram object containing the data that the drawer
00177                         draws
00178 
00179             o pagesize  String describing the ISO size of the image, or a tuple
00180                         of pixels
00181 
00182             o orientation   String describing the required orientation of the
00183                             final drawing ('landscape' or 'portrait')
00184 
00185             o x         Float (0->1) describing the relative size of the X
00186                         margins to the page
00187 
00188             o y         Float (0->1) describing the relative size of the Y
00189                         margins to the page
00190 
00191             o xl        Float (0->1) describing the relative size of the left X
00192                         margin to the page (overrides x)
00193 
00194             o xl        Float (0->1) describing the relative size of the left X
00195                         margin to the page (overrides x)
00196 
00197             o xr        Float (0->1) describing the relative size of the right X
00198                         margin to the page (overrides x)
00199 
00200             o yt        Float (0->1) describing the relative size of the top Y
00201                         margin to the page (overrides y)
00202 
00203             o yb        Float (0->1) describing the relative size of the lower Y
00204                         margin to the page (overrides y)
00205 
00206             o start     Int, the position to begin drawing the diagram at
00207 
00208             o end       Int, the position to stop drawing the diagram at
00209 
00210             o tracklines    Boolean flag to show (or not) lines delineating tracks
00211                             on the diagram            
00212                             
00213             o track_size    The proportion of the available track height that
00214                             should be taken up in drawing
00215 
00216             o circular      Boolean flaw to show whether the passed sequence is
00217                             circular or not
00218 
00219             o circle_core   The proportion of the available radius to leave
00220                             empty at the center of a circular diagram (0 to 1).
00221 
00222             o cross_track_links List of tuples each with four entries (track A,
00223                                 feature A, track B, feature B) to be linked.
00224         """
00225         # Use the superclass' instantiation method
00226         AbstractDrawer.__init__(self, parent, pagesize, orientation,
00227                                   x, y, xl, xr, yt, yb, start, end,
00228                                   tracklines, cross_track_links)
00229 
00230         # Useful measurements on the page
00231         self.track_size = track_size
00232         self.circle_core = circle_core
00233         if circular == False:   # Determine the proportion of the circumference
00234             self.sweep = 0.9    # around which information will be drawn
00235         else:
00236             self.sweep = 1
00237 
00238 
00239     def set_track_heights(self):
00240         """ set_track_heights(self)
00241 
00242             Since tracks may not be of identical heights, the bottom and top
00243             radius for each track is stored in a dictionary - self.track_radii,
00244             keyed by track number
00245         """
00246         bot_track = min(min(self.drawn_tracks), 1)
00247         top_track = max(self.drawn_tracks)     # The 'highest' track to draw
00248 
00249         trackunit_sum = 0           # Holds total number of 'units' taken up by all tracks
00250         trackunits = {}             # Holds start and end units for each track keyed by track number
00251         heightholder = 0            # placeholder variable
00252         for track in range(bot_track, top_track+1):    # track numbers to 'draw'
00253             try:
00254                 trackheight = self._parent[track].height    # Get track height
00255             except:
00256                 trackheight = 1                             # ...or default to 1
00257             trackunit_sum += trackheight    # increment total track unit height
00258             trackunits[track] = (heightholder, heightholder+trackheight)
00259             heightholder += trackheight     # move to next height
00260 
00261         trackunit_height = 0.5*min(self.pagewidth, self.pageheight)/trackunit_sum
00262         track_core = trackunit_height * self.circle_core
00263         trackunit_height = trackunit_height * (1-self.circle_core)
00264 
00265         # Calculate top and bottom radii for each track
00266         self.track_radii = {}      # The inner, outer and center radii for each track
00267         track_crop = trackunit_height*(1-self.track_size)/2.    # 'step back' in pixels
00268         for track in trackunits:
00269             top = trackunits[track][1]*trackunit_height - track_crop + track_core
00270             btm = trackunits[track][0]*trackunit_height + track_crop + track_core
00271             ctr = btm+(top-btm)/2.
00272             self.track_radii[track] = (btm, ctr, top)
00273 
00274     def draw(self):
00275         """ draw(self)
00276 
00277             Draw a circular diagram of the stored data
00278         """
00279         # Instantiate the drawing canvas
00280         self.drawing = Drawing(self.pagesize[0], self.pagesize[1])
00281 
00282         feature_elements = []           # holds feature elements
00283         feature_labels = []             # holds feature labels
00284         greytrack_bgs = []              # holds track background
00285         greytrack_labels = []           # holds track foreground labels
00286         scale_axes = []                 # holds scale axes
00287         scale_labels = []               # holds scale axis labels
00288 
00289         # Get tracks to be drawn and set track sizes
00290         self.drawn_tracks = self._parent.get_drawn_levels()        
00291         self.set_track_heights()
00292 
00293         # Go through each track in the parent (if it is to be drawn) one by
00294         # one and collate the data as drawing elements
00295         for track_level in self._parent.get_drawn_levels():
00296             self.current_track_level = track_level
00297             track = self._parent[track_level]
00298             gbgs, glabels = self.draw_greytrack(track)    # Greytracks
00299             greytrack_bgs.append(gbgs)
00300             greytrack_labels.append(glabels)
00301             features, flabels = self.draw_track(track)   # Features and graphs
00302             feature_elements.append(features)
00303             feature_labels.append(flabels)
00304             if track.scale:
00305                 axes, slabels = self.draw_scale(track)       # Scale axes
00306                 scale_axes.append(axes)
00307                 scale_labels.append(slabels)
00308 
00309         feature_cross_links = []
00310         for cross_link_obj in self.cross_track_links:
00311             cross_link_elements = self.draw_cross_link(cross_link_obj)
00312             if cross_link_elements:
00313                 feature_cross_links.append(cross_link_elements)
00314 
00315         # Groups listed in order of addition to page (from back to front)
00316         # Draw track backgrounds
00317         # Draw feature cross track links
00318         # Draw features and graphs
00319         # Draw scale axes
00320         # Draw scale labels
00321         # Draw feature labels
00322         # Draw track labels
00323         element_groups = [greytrack_bgs, feature_cross_links,
00324                           feature_elements,
00325                           scale_axes, scale_labels,
00326                           feature_labels, greytrack_labels
00327                           ]
00328         for element_group in element_groups:
00329             for element_list in element_group:
00330                 [self.drawing.add(element) for element in element_list]
00331             
00332         if self.tracklines:             # Draw test tracks over top of diagram
00333             self.draw_test_tracks()
00334 
00335 
00336     def draw_track(self, track):
00337         """ draw_track(self, track) -> ([element, element,...], [element, element,...])
00338 
00339             o track     Track object
00340 
00341             Return tuple of (list of track elements, list of track labels)           
00342         """
00343         track_elements = [] # Holds elements for features and graphs
00344         track_labels = []   # Holds labels for features and graphs
00345         
00346         # Distribution dictionary for dealing with different set types
00347         set_methods = {FeatureSet: self.draw_feature_set,
00348                        GraphSet: self.draw_graph_set
00349                        }
00350         
00351         for set in track.get_sets():        # Draw the feature or graph sets
00352             elements, labels = set_methods[set.__class__](set)
00353             track_elements += elements
00354             track_labels += labels
00355         return track_elements, track_labels
00356 
00357 
00358     def draw_feature_set(self, set):
00359         """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...])
00360 
00361             o set       FeatureSet object
00362 
00363             Returns a tuple (list of elements describing features, list of
00364             labels for elements)
00365         """
00366         #print 'draw feature set'
00367         feature_elements = []   # Holds diagram elements belonging to the features
00368         label_elements = []     # Holds diagram elements belonging to feature labels 
00369 
00370         # Collect all the elements for the feature set
00371         for feature in set.get_features():
00372             if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end):
00373                 features, labels = self.draw_feature(feature)
00374                 feature_elements += features
00375                 label_elements += labels
00376 
00377         return feature_elements, label_elements
00378         
00379 
00380     def draw_feature(self, feature):
00381         """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...])
00382 
00383             o feature           Feature containing location info
00384 
00385             Returns tuple of (list of elements describing single feature, list
00386             of labels for those elements)
00387         """        
00388         feature_elements = []   # Holds drawable elements for a single feature
00389         label_elements = []     # Holds labels for a single feature
00390 
00391         if feature.hide:    # Don't show feature: return early
00392             return feature_elements, label_elements
00393 
00394         start, end = self._current_track_start_end()
00395         # A single feature may be split into subfeatures, so loop over them
00396         for locstart, locend in feature.locations:
00397             if locend < start:
00398                 continue
00399             locstart = max(locstart, start)
00400             if end < locstart:
00401                 continue
00402             locend = min(locend, end)
00403             # Get sigil for the feature/ each subfeature
00404             feature_sigil, label = self.get_feature_sigil(feature, locstart, locend)
00405             feature_elements.append(feature_sigil)
00406             if label is not None:   # If there's a label
00407                 label_elements.append(label)
00408 
00409         return feature_elements, label_elements
00410 
00411 
00412     def get_feature_sigil(self, feature, locstart, locend, **kwargs):
00413         """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element)
00414 
00415             o feature       Feature object
00416 
00417             o locstart      The start position of the feature
00418 
00419             o locend        The end position of the feature
00420 
00421             Returns a drawable indicator of the feature, and any required label
00422             for it
00423         """
00424         # Establish the co-ordinates for the sigil
00425         btm, ctr, top = self.track_radii[self.current_track_level]
00426         
00427         startangle, startcos, startsin = self.canvas_angle(locstart)
00428         endangle, endcos, endsin = self.canvas_angle(locend)
00429         midangle, midcos, midsin = self.canvas_angle(float(locend+locstart)/2)
00430 
00431         # Distribution dictionary for various ways of drawing the feature
00432         # Each method takes the inner and outer radii, the start and end angle
00433         # subtended at the diagram center, and the color as arguments
00434         draw_methods = {'BOX': self._draw_arc,
00435                         'ARROW': self._draw_arc_arrow,
00436                         }
00437 
00438         # Get sigil for the feature, location dependent on the feature strand        
00439         method = draw_methods[feature.sigil]
00440         kwargs['head_length_ratio'] = feature.arrowhead_length
00441         kwargs['shaft_height_ratio'] = feature.arrowshaft_height
00442         
00443         #Support for clickable links... needs ReportLab 2.4 or later
00444         #which added support for links in SVG output.
00445         if hasattr(feature, "url") :
00446             kwargs["hrefURL"] = feature.url
00447             kwargs["hrefTitle"] = feature.name
00448 
00449         border = feature.border
00450 
00451         if feature.strand == 1:
00452             sigil = method(ctr, top, startangle, endangle, feature.color,
00453                            border, orientation='right', **kwargs)
00454         elif feature.strand == -1:
00455             sigil = method(btm, ctr, startangle, endangle, feature.color,
00456                            border, orientation='left', **kwargs)
00457         else:
00458             sigil = method(btm, top, startangle, endangle, feature.color,
00459                            border, **kwargs)
00460 
00461         if feature.label:   # Feature needs a label
00462             label = String(0, 0, feature.name.strip(),
00463                            fontName=feature.label_font,
00464                            fontSize=feature.label_size,
00465                            fillColor=feature.label_color)
00466             labelgroup = Group(label)
00467             label_angle = startangle + 0.5 * pi     # Make text radial
00468             sinval, cosval = startsin, startcos
00469             if feature.strand != -1:
00470                 # Feature is on top, or covers both strands
00471                 if startangle < pi: # Turn text round and anchor end to inner radius
00472                     sinval, cosval = endsin, endcos
00473                     label_angle = endangle - 0.5 * pi
00474                     labelgroup.contents[0].textAnchor = 'end'
00475                 pos = self.xcenter+top*sinval
00476                 coslabel = cos(label_angle)
00477                 sinlabel = sin(label_angle)    
00478                 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
00479                                         pos, self.ycenter+top*cosval)
00480             else:
00481                 # Feature on bottom strand
00482                 if startangle < pi: # Turn text round and anchor end to inner radius
00483                     sinval, cosval = endsin, endcos
00484                     label_angle = endangle - 0.5 * pi
00485                 else:
00486                     labelgroup.contents[0].textAnchor = 'end'
00487                 pos = self.xcenter+btm*sinval
00488                 coslabel = cos(label_angle)
00489                 sinlabel = sin(label_angle)
00490                 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
00491                                         pos, self.ycenter+btm*cosval)
00492 
00493         else:
00494             labelgroup = None
00495         #if locstart > locend:
00496         #    print locstart, locend, feature.strand, sigil, feature.name
00497         #print locstart, locend, feature.name
00498         return sigil, labelgroup
00499 
00500     def draw_cross_link(self, cross_link):
00501         startA = cross_link.startA
00502         startB = cross_link.startB
00503         endA = cross_link.endA
00504         endB = cross_link.endB
00505            
00506         if not self.is_in_bounds(startA) \
00507         and not self.is_in_bounds(endA):
00508             return None
00509         if not self.is_in_bounds(startB) \
00510         and not self.is_in_bounds(endB):
00511             return None
00512 
00513         if startA < self.start: startA = self.start
00514         if startB < self.start: startB = self.start
00515         if self.end < endA: endA = self.end
00516         if self.end < endB: endB = self.end
00517 
00518         trackobjA = cross_link._trackA(self._parent.tracks.values())
00519         trackobjB = cross_link._trackB(self._parent.tracks.values())
00520         assert trackobjA is not None
00521         assert trackobjB is not None
00522         if trackobjA == trackobjB: raise NotImplementedError()
00523 
00524         if trackobjA.start is not None:
00525             if endA < trackobjA.start:
00526                 return
00527             startA = max(startA, trackobjA.start)
00528         if trackobjA.end is not None:
00529             if trackobjA.end < startA:
00530                 return
00531             endA = min(endA, trackobjA.end)
00532         if trackobjB.start is not None:
00533             if endB < trackobjB.start:
00534                 return
00535             startB = max(startB, trackobjB.start)
00536         if trackobjB.end is not None:
00537             if trackobjB.end < startB:
00538                 return
00539             endB = min(endB, trackobjB.end)
00540 
00541         for track_level in self._parent.get_drawn_levels():
00542             track = self._parent[track_level]
00543             if track == trackobjA:
00544                 trackA = track_level
00545             if track == trackobjB:
00546                 trackB = track_level
00547         if trackA == trackB: raise NotImplementedError()
00548 
00549         startangleA, startcosA, startsinA = self.canvas_angle(startA)
00550         startangleB, startcosB, startsinB = self.canvas_angle(startB)
00551         endangleA, endcosA, endsinA = self.canvas_angle(endA)
00552         endangleB, endcosB, endsinB = self.canvas_angle(endB)
00553 
00554         btmA, ctrA, topA = self.track_radii[trackA]
00555         btmB, ctrB, topB = self.track_radii[trackB]
00556 
00557         if ctrA < ctrB:
00558             return [self._draw_arc_poly(topA, btmB,
00559                            startangleA, endangleA,
00560                            startangleB, endangleB,
00561                            cross_link.color, cross_link.border, cross_link.flip)]
00562         else:
00563             return [self._draw_arc_poly(btmA, topB,
00564                            startangleA, endangleA,
00565                            startangleB, endangleB,
00566                            cross_link.color, cross_link.border, cross_link.flip)]
00567 
00568 
00569     def draw_graph_set(self, set):
00570         """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...])
00571         
00572             o set       GraphSet object
00573 
00574             Returns tuple (list of graph elements, list of graph labels)
00575         """
00576         #print 'draw graph set'
00577         elements = []   # Holds graph elements
00578 
00579         # Distribution dictionary for how to draw the graph
00580         style_methods = {'line': self.draw_line_graph,
00581                          'heat': self.draw_heat_graph,
00582                          'bar': self.draw_bar_graph
00583                          }
00584 
00585         for graph in set.get_graphs():
00586             #print graph.name
00587             elements += style_methods[graph.style](graph)
00588 
00589         return elements, []
00590 
00591 
00592     def draw_line_graph(self, graph):
00593         """ draw_line_graph(self, graph, center) -> [element, element,...]
00594 
00595             o graph     GraphData object
00596 
00597             Returns a line graph as a list of drawable elements
00598         """
00599         #print '\tdraw_line_graph'
00600         line_elements = []  # holds drawable elements
00601 
00602         # Get graph data
00603         data_quartiles = graph.quartiles()
00604         minval, maxval = data_quartiles[0],data_quartiles[4]
00605         btm, ctr, top = self.track_radii[self.current_track_level]
00606         trackheight = 0.5*(top-btm)
00607         datarange = maxval - minval
00608         if datarange == 0:
00609             datarange = trackheight
00610 
00611         start, end = self._current_track_start_end()
00612         data = graph[start:end]
00613         
00614         if not data:
00615             return []
00616 
00617         # midval is the value at which the x-axis is plotted, and is the
00618         # central ring in the track
00619         if graph.center is None:
00620             midval = (maxval + minval)/2.    
00621         else:
00622             midval = graph.center
00623         # Whichever is the greatest difference: max-midval or min-midval, is
00624         # taken to specify the number of pixel units resolved along the
00625         # y-axis
00626         resolution = max((midval-minval), (maxval-midval))
00627 
00628         # Start from first data point
00629         pos, val = data[0]
00630         lastangle, lastcos, lastsin = self.canvas_angle(pos)
00631         # We calculate the track height
00632         posheight = trackheight*(val-midval)/resolution + ctr
00633         lastx = self.xcenter+posheight*lastsin  # start xy coords
00634         lasty = self.ycenter+posheight*lastcos
00635         for pos, val in data:
00636             posangle, poscos, possin = self.canvas_angle(pos)
00637             posheight = trackheight*(val-midval)/resolution + ctr
00638             x = self.xcenter+posheight*possin   # next xy coords
00639             y = self.ycenter+posheight*poscos
00640             line_elements.append(Line(lastx, lasty, x, y,
00641                                       strokeColor = graph.poscolor,
00642                                       strokeWidth = graph.linewidth))
00643             lastx, lasty, = x, y
00644         return line_elements
00645         
00646 
00647     def draw_bar_graph(self, graph):
00648         """ draw_bar_graph(self, graph) -> [element, element,...]
00649 
00650             o graph     Graph object
00651 
00652             Returns a list of drawable elements for a bar graph of the passed
00653             Graph object
00654         """
00655         #print '\tdraw_bar_graph'
00656         # At each point contained in the graph data, we draw a vertical bar
00657         # from the track center to the height of the datapoint value (positive
00658         # values go up in one color, negative go down in the alternative
00659         # color).
00660         bar_elements = []
00661         
00662         # Set the number of pixels per unit for the data
00663         data_quartiles = graph.quartiles()
00664         minval, maxval = data_quartiles[0],data_quartiles[4]
00665         btm, ctr, top = self.track_radii[self.current_track_level]
00666         trackheight = 0.5*(top-btm)
00667         datarange = maxval - minval
00668         if datarange == 0:
00669             datarange = trackheight
00670         data = graph[self.start:self.end]
00671         # midval is the value at which the x-axis is plotted, and is the
00672         # central ring in the track
00673         if graph.center is None:
00674             midval = (maxval + minval)/2.    
00675         else:
00676             midval = graph.center
00677 
00678         # Convert data into 'binned' blocks, covering half the distance to the
00679         # next data point on either side, accounting for the ends of fragments
00680         # and tracks
00681         start, end = self._current_track_start_end()
00682         data = intermediate_points(start, end, graph[start:end])
00683 
00684         if not data:
00685             return []
00686 
00687         # Whichever is the greatest difference: max-midval or min-midval, is
00688         # taken to specify the number of pixel units resolved along the
00689         # y-axis
00690         resolution = max((midval-minval), (maxval-midval))
00691         if resolution == 0:
00692             resolution = trackheight
00693 
00694         # Create elements for the bar graph based on newdata
00695         for pos0, pos1, val in data:
00696             pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
00697             pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
00698 
00699             barval = trackheight*(val-midval)/resolution
00700             if barval >=0:
00701                 barcolor = graph.poscolor
00702             else:
00703                 barcolor = graph.negcolor
00704 
00705             # Draw bar
00706             bar_elements.append(self._draw_arc(ctr, ctr+barval, pos0angle,
00707                                               pos1angle, barcolor))
00708         return bar_elements
00709 
00710     
00711 
00712 
00713     def draw_heat_graph(self, graph):
00714         """ draw_heat_graph(self, graph) -> [element, element,...]
00715 
00716             o graph     Graph object
00717 
00718             Returns a list of drawable elements for the heat graph
00719         """
00720         #print '\tdraw_heat_graph'
00721         # At each point contained in the graph data, we draw a box that is the
00722         # full height of the track, extending from the midpoint between the
00723         # previous and current data points to the midpoint between the current
00724         # and next data points
00725         heat_elements = []      # holds drawable elements
00726 
00727         # Get graph data
00728         data_quartiles = graph.quartiles()
00729         minval, maxval = data_quartiles[0],data_quartiles[4]
00730         midval = (maxval + minval)/2.    # mid is the value at the X-axis
00731         btm, ctr, top = self.track_radii[self.current_track_level]
00732         trackheight = (top-btm)
00733 
00734         start, end = self._current_track_start_end()
00735         data = intermediate_points(start, end, graph[start:end])
00736 
00737         # Create elements on the graph, indicating a large positive value by
00738         # the graph's poscolor, and a large negative value by the graph's
00739         # negcolor attributes
00740         for pos0, pos1, val in data:
00741             pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
00742             pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
00743 
00744             # Calculate the heat color, based on the differential between
00745             # the value and the median value
00746             heat = colors.linearlyInterpolatedColor(graph.poscolor,
00747                                                     graph.negcolor,
00748                                                     maxval, minval, val)
00749             
00750             # Draw heat box
00751             heat_elements.append(self._draw_arc(btm, top, pos0angle, pos1angle,
00752                                                 heat, border=heat))
00753         return heat_elements
00754 
00755 
00756     def draw_scale(self, track):
00757         """ draw_scale(self, track) -> ([element, element,...], [element, element,...])
00758 
00759             o track     Track object
00760 
00761             Returns a tuple of (list of elements in the scale, list of labels
00762             in the scale)
00763         """
00764         scale_elements = []     # holds axes and ticks
00765         scale_labels = []       # holds labels
00766 
00767         if not track.scale:     # no scale required, exit early
00768             return [], []
00769 
00770         # Get track locations
00771         btm, ctr, top = self.track_radii[self.current_track_level]
00772         trackheight = (top-ctr)
00773         
00774         # X-axis
00775         start, end = self._current_track_start_end()
00776         if track.start is not None or track.end is not None:
00777             #Draw an arc, leaving out the wedge
00778             p = ArcPath(strokeColor=track.scale_color, fillColor=None)
00779             startangle, startcos, startsin = self.canvas_angle(start)
00780             endangle, endcos, endsin = self.canvas_angle(end)
00781             p.addArc(self.xcenter, self.ycenter, ctr,
00782                      90 - (endangle * 180 / pi),
00783                      90 - (startangle * 180 / pi))
00784             scale_elements.append(p)
00785             del p
00786             # Y-axis start marker
00787             x0, y0 = self.xcenter+btm*startsin, self.ycenter+btm*startcos
00788             x1, y1 = self.xcenter+top*startsin, self.ycenter+top*startcos
00789             scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
00790             # Y-axis end marker
00791             x0, y0 = self.xcenter+btm*endsin, self.ycenter+btm*endcos
00792             x1, y1 = self.xcenter+top*endsin, self.ycenter+top*endcos
00793             scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
00794         elif self.sweep < 1:
00795             #Draw an arc, leaving out the wedge
00796             p = ArcPath(strokeColor=track.scale_color, fillColor=None)
00797             #Note reportlab counts angles anti-clockwise from the horizontal
00798             #(as in mathematics, e.g. complex numbers and polar coordinates)
00799             #in degrees.
00800             p.addArc(self.xcenter, self.ycenter, ctr,
00801                      startangledegrees=90-360*self.sweep,
00802                      endangledegrees=90)
00803             scale_elements.append(p)
00804             del p
00805             # Y-axis start marker
00806             x0, y0 = self.xcenter, self.ycenter+btm
00807             x1, y1 = self.xcenter, self.ycenter+top
00808             scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
00809             # Y-axis end marker
00810             alpha = 2*pi*self.sweep
00811             x0, y0 = self.xcenter+btm*sin(alpha), self.ycenter+btm*cos(alpha)
00812             x1, y1 = self.xcenter+top*sin(alpha), self.ycenter+top*cos(alpha)
00813             scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
00814         else:
00815             #Draw a full circle
00816             scale_elements.append(Circle(self.xcenter, self.ycenter, ctr,
00817                                          strokeColor=track.scale_color,
00818                                          fillColor=None))
00819 
00820         start, end = self._current_track_start_end()
00821         if track.scale_ticks:   # Ticks are required on the scale
00822             # Draw large ticks 
00823             #I want the ticks to be consistently positioned relative to
00824             #the start of the sequence (position 0), not relative to the
00825             #current viewpoint (self.start and self.end)
00826 
00827             ticklen = track.scale_largeticks * trackheight
00828             tickiterval = int(track.scale_largetick_interval)
00829             #Note that we could just start the list of ticks using
00830             #range(0,self.end,tickinterval) and the filter out the
00831             #ones before self.start - but this seems wasteful.
00832             #Using tickiterval * (self.start/tickiterval) is a shortcut.
00833             for tickpos in range(tickiterval * (self.start//tickiterval),
00834                                  int(self.end), tickiterval):
00835                 if tickpos <= start or end <= tickpos:
00836                     continue
00837                 tick, label = self.draw_tick(tickpos, ctr, ticklen,
00838                                              track,
00839                                              track.scale_largetick_labels)
00840                 scale_elements.append(tick)
00841                 if label is not None:   # If there's a label, add it
00842                     scale_labels.append(label)
00843             # Draw small ticks
00844             ticklen = track.scale_smallticks * trackheight
00845             tickiterval = int(track.scale_smalltick_interval)
00846             for tickpos in range(tickiterval * (self.start//tickiterval),
00847                                  int(self.end), tickiterval):
00848                 if tickpos <= start or end <= tickpos:
00849                     continue
00850                 tick, label = self.draw_tick(tickpos, ctr, ticklen,
00851                                              track,
00852                                              track.scale_smalltick_labels)
00853                 scale_elements.append(tick)
00854                 if label is not None:   # If there's a label, add it
00855                     scale_labels.append(label)
00856         
00857         # Check to see if the track contains a graph - if it does, get the
00858         # minimum and maximum values, and put them on the scale Y-axis
00859         # at 60 degree intervals, ordering the labels by graph_id
00860         startangle, startcos, startsin = self.canvas_angle(start)
00861         endangle, endcos, endsin = self.canvas_angle(end)
00862         if track.axis_labels:
00863             for set in track.get_sets():
00864                 if set.__class__ is GraphSet:
00865                     # Y-axis
00866                     for n in xrange(7):
00867                         angle = n * 1.0471975511965976
00868                         if angle < startangle or endangle < angle:
00869                             continue
00870                         ticksin, tickcos = sin(angle), cos(angle)
00871                         x0, y0 = self.xcenter+btm*ticksin, self.ycenter+btm*tickcos
00872                         x1, y1 = self.xcenter+top*ticksin, self.ycenter+top*tickcos
00873                         scale_elements.append(Line(x0, y0, x1, y1,
00874                                                    strokeColor=track.scale_color))
00875 
00876                         graph_label_min = []
00877                         graph_label_max = []
00878                         graph_label_mid = []
00879                         for graph in set.get_graphs():                        
00880                             quartiles = graph.quartiles()
00881                             minval, maxval = quartiles[0], quartiles[4]
00882                             if graph.center is None:
00883                                 midval = (maxval + minval)/2.
00884                                 graph_label_min.append("%.3f" % minval)
00885                                 graph_label_max.append("%.3f" % maxval)
00886                                 graph_label_mid.append("%.3f" % midval)
00887                             else:
00888                                 diff = max((graph.center-minval),
00889                                            (maxval-graph.center))
00890                                 minval = graph.center-diff
00891                                 maxval = graph.center+diff
00892                                 midval = graph.center
00893                                 graph_label_mid.append("%.3f" % midval)
00894                                 graph_label_min.append("%.3f" % minval)
00895                                 graph_label_max.append("%.3f" % maxval)
00896                         xmid, ymid = (x0+x1)/2., (y0+y1)/2.
00897                         for limit, x, y, in [(graph_label_min, x0, y0),
00898                                              (graph_label_max, x1, y1),
00899                                              (graph_label_mid, xmid, ymid)]:
00900                             label = String(0, 0, ";".join(limit),
00901                                            fontName=track.scale_font,
00902                                            fontSize=track.scale_fontsize,
00903                                            fillColor=track.scale_color)
00904                             label.textAnchor = 'middle'
00905                             labelgroup = Group(label)
00906                             labelgroup.transform = (tickcos, -ticksin,
00907                                                     ticksin, tickcos,
00908                                                     x, y)
00909                             scale_labels.append(labelgroup)
00910 
00911         return scale_elements, scale_labels
00912 
00913 
00914     def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
00915         """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element)
00916 
00917             o tickpos   Int, position of the tick on the sequence
00918 
00919             o ctr       Float, Y co-ord of the center of the track
00920 
00921             o ticklen   How long to draw the tick
00922 
00923             o track     Track, the track the tick is drawn on
00924 
00925             o draw_label    Boolean, write the tick label?
00926 
00927             Returns a drawing element that is the tick on the scale
00928         """
00929         # Calculate tick co-ordinates
00930         tickangle, tickcos, ticksin = self.canvas_angle(tickpos)
00931         x0, y0 = self.xcenter+ctr*ticksin, self.ycenter+ctr*tickcos
00932         x1, y1 = self.xcenter+(ctr+ticklen)*ticksin, self.ycenter+(ctr+ticklen)*tickcos
00933         # Calculate height of text label so it can be offset on lower half
00934         # of diagram
00935         # LP: not used, as not all fonts have ascent_descent data in reportlab.pdfbase._fontdata
00936         #label_offset = _fontdata.ascent_descent[track.scale_font][0]*\
00937         #               track.scale_fontsize/1000.
00938         tick = Line(x0, y0, x1, y1, strokeColor=track.scale_color)
00939         if draw_label:                          # Put tick position on as label
00940             if track.scale_format == 'SInt':
00941                 if tickpos >= 1000000:
00942                     tickstring = str(tickpos//1000000) + " Mbp"
00943                 elif tickpos >= 1000:
00944                     tickstring = str(tickpos//1000) + " Kbp"
00945                 else:
00946                     tickstring = str(tickpos)
00947             else:
00948                 tickstring = str(tickpos)
00949             label = String(0, 0, tickstring,  # Make label string
00950                            fontName=track.scale_font,
00951                            fontSize=track.scale_fontsize,
00952                            fillColor=track.scale_color)
00953             if tickangle > pi:
00954                 label.textAnchor = 'end'
00955             # LP: This label_offset depends on ascent_descent data, which is not available for all 
00956             # fonts, so has been deprecated.
00957             #if 0.5*pi < tickangle < 1.5*pi:
00958             #    y1 -= label_offset
00959             labelgroup = Group(label)
00960             labelgroup.transform = (1,0,0,1, x1, y1)
00961         else:
00962             labelgroup = None
00963         return tick, labelgroup
00964 
00965 
00966     def draw_test_tracks(self):
00967         """ draw_test_tracks(self)
00968 
00969             Draw blue ones indicating tracks to be drawn, with a green line
00970             down the center.
00971         """
00972         #print 'drawing test tracks'
00973         # Add lines only for drawn tracks
00974         for track in self.drawn_tracks:
00975             btm, ctr, top = self.track_radii[track]            
00976             self.drawing.add(Circle(self.xcenter, self.ycenter, top,
00977                                     strokeColor=colors.blue,
00978                                     fillColor=None))  # top line
00979             self.drawing.add(Circle(self.xcenter, self.ycenter, ctr,
00980                                     strokeColor=colors.green,
00981                                     fillColor=None))  # middle line
00982             self.drawing.add(Circle(self.xcenter, self.ycenter, btm,
00983                                     strokeColor=colors.blue,
00984                                     fillColor=None))  # bottom line
00985 
00986 
00987     def draw_greytrack(self, track):
00988         """ draw_greytrack(self)
00989 
00990             o track     Track object
00991 
00992             Put in a grey background to the current track, if the track
00993             specifies that we should
00994         """
00995         greytrack_bgs = []      # Holds track backgrounds
00996         greytrack_labels = []   # Holds track foreground labels
00997 
00998         if not track.greytrack: # No greytrack required, return early
00999             return [], []
01000 
01001         # Get track location
01002         btm, ctr, top = self.track_radii[self.current_track_level]
01003 
01004         start, end = self._current_track_start_end()
01005         startangle, startcos, startsin = self.canvas_angle(start)
01006         endangle, endcos, endsin = self.canvas_angle(end)
01007 
01008         # Make background
01009         if track.start is not None or track.end is not None:
01010             #Draw an arc, leaving out the wedge
01011             p = ArcPath(strokeColor=track.scale_color, fillColor=None)
01012             greytrack_bgs.append(self._draw_arc(btm, top, startangle, endangle,
01013                                  colors.Color(0.96, 0.96, 0.96)))
01014         elif self.sweep < 1:
01015             #Make a partial circle, a large arc box
01016             #This method assumes the correct center for us.
01017             greytrack_bgs.append(self._draw_arc(btm, top, 0, 2*pi*self.sweep,
01018                                  colors.Color(0.96, 0.96, 0.96)))
01019         else:
01020             #Make a full circle (using a VERY thick linewidth)
01021             greytrack_bgs.append(Circle(self.xcenter, self.ycenter, ctr, 
01022                                  strokeColor = colors.Color(0.96, 0.96, 0.96),
01023                                  fillColor=None, strokeWidth=top-btm))
01024 
01025         if track.greytrack_labels:  # Labels are required for this track
01026             labelstep = self.length//track.greytrack_labels  # label interval
01027             for pos in range(self.start, self.end, labelstep):
01028                 label = String(0, 0, track.name,            # Add a new label at
01029                            fontName=track.greytrack_font,   # each interval
01030                            fontSize=track.greytrack_fontsize,
01031                            fillColor=track.greytrack_fontcolor)
01032                 theta, costheta, sintheta = self.canvas_angle(pos)
01033                 if theta < startangle or endangle < theta:
01034                     continue
01035                 x,y = self.xcenter+btm*sintheta, self.ycenter+btm*costheta  # start text halfway up marker
01036                 labelgroup = Group(label)
01037                 labelangle = self.sweep*2*pi*(pos-self.start)/self.length - pi/2
01038                 if theta > pi:  
01039                     label.textAnchor = 'end'    # Anchor end of text to inner radius
01040                     labelangle += pi            # and reorient it
01041                 cosA, sinA = cos(labelangle), sin(labelangle)
01042                 labelgroup.transform = (cosA, -sinA, sinA,
01043                                         cosA, x, y)
01044                 if not self.length-x <= labelstep:  # Don't overrun the circle
01045                     greytrack_labels.append(labelgroup)
01046 
01047         return greytrack_bgs, greytrack_labels
01048 
01049 
01050     def canvas_angle(self, base):
01051         """ canvas_angle(self, base) -> (float, float, float)
01052         """
01053         angle = self.sweep*2*pi*(base-self.start)/self.length
01054         return (angle, cos(angle), sin(angle))
01055 
01056 
01057     def _draw_arc(self, inner_radius, outer_radius, startangle, endangle,
01058                  color, border=None, colour=None, **kwargs):
01059         """ draw_arc(self, inner_radius, outer_radius, startangle, endangle, color)
01060                 -> Group
01061 
01062             o inner_radius  Float distance of inside of arc from drawing center
01063 
01064             o outer_radius  Float distance of outside of arc from drawing center
01065 
01066             o startangle    Float angle subtended by start of arc at drawing center
01067                             (in radians)
01068 
01069             o endangle      Float angle subtended by end of arc at drawing center
01070                             (in radians)
01071 
01072             o color        colors.Color object for arc (overridden by backwards
01073                            compatible argument with UK spelling, colour).
01074 
01075             Returns a closed path object describing an arced box corresponding to
01076             the passed values.  For very small angles, a simple four sided
01077             polygon is used.
01078         """
01079         #Let the UK spelling (colour) override the USA spelling (color)
01080         if colour is not None:
01081             color = colour
01082 
01083         if color == colors.white and border is None:   # Force black border on 
01084             strokecolor = colors.black                 # white boxes with
01085         elif border is None:                           # undefined border, else
01086             strokecolor = color                        # use fill color
01087         elif border:
01088             if not isinstance(border, colors.Color):
01089                 raise ValueError("Invalid border color %s" % repr(border))
01090             strokecolor = border
01091         else:
01092             #e.g. False
01093             strokecolor = None
01094 
01095         if abs(float(endangle - startangle))>.01:
01096             # Wide arc, must use full curves
01097             p = ArcPath(strokeColor=strokecolor,
01098                         fillColor=color,
01099                         strokewidth=0)
01100             #Note reportlab counts angles anti-clockwise from the horizontal
01101             #(as in mathematics, e.g. complex numbers and polar coordinates)
01102             #but we use clockwise from the vertical.  Also reportlab uses
01103             #degrees, but we use radians.
01104             p.addArc(self.xcenter, self.ycenter, inner_radius,
01105                      90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
01106                      moveTo=True)
01107             p.addArc(self.xcenter, self.ycenter, outer_radius,
01108                      90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
01109                      reverse=True)
01110             p.closePath()
01111             return p
01112         else:
01113             #Cheat and just use a four sided polygon.
01114             # Calculate trig values for angle and coordinates
01115             startcos, startsin = cos(startangle), sin(startangle)
01116             endcos, endsin = cos(endangle), sin(endangle)
01117             x0,y0 = self.xcenter, self.ycenter      # origin of the circle
01118             x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
01119             x2,y2 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
01120             x3,y3 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
01121             x4,y4 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
01122             return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border)
01123 
01124     def _draw_arc_poly(self, inner_radius, outer_radius,
01125                        inner_startangle, inner_endangle,
01126                        outer_startangle, outer_endangle,
01127                        color, border=None, flip=False,
01128                        **kwargs):
01129 
01130         if color == colors.white and border is None:   # Force black border on 
01131             strokecolor = colors.black                 # white boxes with
01132         elif border is None:                           # undefined border, else
01133             strokecolor = color                        # use fill color
01134         elif border:
01135             if not isinstance(border, colors.Color):
01136                 raise ValueError("Invalid border color %s" % repr(border))
01137             strokecolor = border
01138         else:
01139             #e.g. False
01140             strokecolor = None
01141         
01142         x0, y0 = self.xcenter, self.ycenter      # origin of the circle
01143         if abs(inner_endangle - outer_startangle)>0.01 \
01144         or abs(outer_endangle - inner_startangle)>0.01 \
01145         or abs(inner_startangle - outer_startangle)>0.01 \
01146         or abs(outer_startangle - outer_startangle)>0.01:
01147             # Wide arc, must use full curves
01148             p = ArcPath(strokeColor=strokecolor,
01149                         fillColor=color,
01150                         #default is mitre/miter which can stick out too much:
01151                         strokeLineJoin=1, #1=round
01152                         strokewidth=0)
01153             #Note reportlab counts angles anti-clockwise from the horizontal
01154             #(as in mathematics, e.g. complex numbers and polar coordinates)
01155             #but we use clockwise from the vertical.  Also reportlab uses
01156             #degrees, but we use radians.
01157             i_start = 90 - (inner_startangle * 180 / pi)
01158             i_end = 90 - (inner_endangle * 180 / pi)
01159             o_start = 90 - (outer_startangle * 180 / pi)
01160             o_end = 90 - (outer_endangle * 180 / pi)
01161             p.addArc(x0, y0, inner_radius, i_end, i_start,
01162                      moveTo=True, reverse=True)
01163             if flip:
01164                 #Flipped, join end to start,
01165                 dx = 0.1
01166                 x = dx
01167                 while x < 1:
01168                     r = inner_radius + x*(outer_radius-inner_radius)
01169                     a = (i_end + x*(o_start-i_end))*pi/180 #to radians for sin/cos
01170                     p.lineTo(x0+r*cos(a), y0+r*sin(a))
01171                     x += dx
01172                 p.addArc(x0, y0, outer_radius, o_end, o_start, reverse=True)
01173                 x = dx
01174                 while x < 1:
01175                     r = outer_radius - x*(outer_radius-inner_radius)
01176                     a = (o_end + x*(i_start-o_end))*pi/180 #to radians for sin/cos
01177                     p.lineTo(x0+r*cos(a), y0+r*sin(a))
01178                     x += dx
01179             else:
01180                 #Not flipped, join start to start, end to end
01181                 dx = 0.1
01182                 x = dx
01183                 while x < 1:
01184                     r = inner_radius + x*(outer_radius-inner_radius)
01185                     a = (i_end + x*(o_end-i_end))*pi/180 #to radians for sin/cos
01186                     p.lineTo(x0+r*cos(a), y0+r*sin(a))
01187                     x += dx
01188                 p.addArc(x0, y0, outer_radius, o_end, o_start,
01189                          reverse=False)
01190                 x = dx
01191                 while x < 1:
01192                     r = outer_radius - x*(outer_radius-inner_radius)
01193                     a = (o_start + x*(i_start-o_start))*pi/180 #to radians for sin/cos
01194                     p.lineTo(x0+r*cos(a), y0+r*sin(a))
01195                     x += dx
01196             p.closePath()
01197             return p
01198         else:
01199             #Cheat and just use a four sided polygon.
01200             # Calculate trig values for angle and coordinates
01201             inner_startcos, inner_startsin = cos(inner_startangle), sin(inner_startangle)
01202             inner_endcos, inner_endsin = cos(inner_endangle), sin(inner_endangle)
01203             outer_startcos, outer_startsin = cos(outer_startangle), sin(outer_startangle)
01204             outer_endcos, outer_endsin = cos(outer_endangle), sin(outer_endangle)
01205             x1,y1 = (x0+inner_radius*inner_startsin, y0+inner_radius*inner_startcos)
01206             x2,y2 = (x0+inner_radius*inner_endsin, y0+inner_radius*inner_endcos)
01207             x3,y3 = (x0+outer_radius*outer_endsin, y0+outer_radius*outer_endcos)
01208             x4,y4 = (x0+outer_radius*outer_startsin, y0+outer_radius*outer_startcos)
01209             return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border,
01210                                 #default is mitre/miter which can stick out too much:
01211                                 strokeLineJoin=1, #1=round
01212                                 )
01213 
01214     def _draw_arc_arrow(self, inner_radius, outer_radius, startangle, endangle,
01215                   color, border=None,
01216                   shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right',
01217                   colour=None, **kwargs):
01218         """Draw an arrow along an arc."""
01219         #Let the UK spelling (colour) override the USA spelling (color)
01220         if colour is not None:
01221             color = colour
01222 
01223         if color == colors.white and border is None:   # Force black border on 
01224             strokecolor = colors.black                 # white boxes with
01225         elif border is None:                           # undefined border, else
01226             strokecolor = color                        # use fill color
01227         elif border:
01228             if not isinstance(border, colors.Color):
01229                 raise ValueError("Invalid border color %s" % repr(border))
01230             strokecolor = border
01231         else:
01232             #e.g. False
01233             strokecolor = None
01234 
01235         #if orientation == 'right':
01236         #    startangle, endangle = min(startangle, endangle), max(startangle, endangle)
01237         #elif orientation == 'left':
01238         #    startangle, endangle = max(startangle, endangle), min(startangle, endangle)
01239         #else:
01240         startangle, endangle = min(startangle, endangle), max(startangle, endangle)
01241         if orientation != "left" and orientation != "right":
01242             raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \
01243                              % repr(orientation))
01244 
01245         angle = float(endangle - startangle)    # angle subtended by arc
01246         middle_radius = 0.5*(inner_radius+outer_radius)
01247         boxheight = outer_radius - inner_radius
01248         shaft_height = boxheight*shaft_height_ratio
01249         shaft_inner_radius = middle_radius - 0.5*shaft_height
01250         shaft_outer_radius = middle_radius + 0.5*shaft_height
01251         headangle_delta = max(0.0,min(abs(boxheight)*head_length_ratio/middle_radius, abs(angle)))
01252         if angle < 0:
01253             headangle_delta *= -1 #reverse it
01254         if orientation=="right":
01255             headangle = endangle-headangle_delta
01256         else:
01257             headangle = startangle+headangle_delta
01258         if startangle <= endangle:
01259             headangle = max(min(headangle, endangle), startangle)
01260         else:
01261             headangle = max(min(headangle, startangle), endangle)
01262         assert startangle <= headangle <= endangle \
01263             or endangle <= headangle <= startangle, \
01264             (startangle, headangle, endangle, angle)
01265         
01266 
01267         # Calculate trig values for angle and coordinates
01268         startcos, startsin = cos(startangle), sin(startangle)
01269         headcos, headsin = cos(headangle), sin(headangle)
01270         endcos, endsin = cos(endangle), sin(endangle)
01271         x0,y0 = self.xcenter, self.ycenter      # origin of the circle
01272         if 0.5 >= abs(angle) and abs(headangle_delta) >= abs(angle):
01273             #If the angle is small, and the arrow is all head,
01274             #cheat and just use a triangle.
01275             if orientation=="right":
01276                 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
01277                 x2,y2 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
01278                 x3,y3 = (x0+middle_radius*endsin, y0+middle_radius*endcos)
01279             else:
01280                 x1,y1 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
01281                 x2,y2 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
01282                 x3,y3 = (x0+middle_radius*startsin, y0+middle_radius*startcos)
01283             #return draw_polygon([(x1,y1),(x2,y2),(x3,y3)], color, border,
01284             #                    stroke_line_join=1)
01285             return Polygon([x1,y1,x2,y2,x3,y3],
01286                            strokeColor=border or color,
01287                            fillColor=color,
01288                            strokeLineJoin=1, #1=round, not mitre!
01289                            strokewidth=0)
01290         elif orientation=="right":
01291             p = ArcPath(strokeColor=strokecolor,
01292                         fillColor=color,
01293                         #default is mitre/miter which can stick out too much:
01294                         strokeLineJoin=1, #1=round
01295                         strokewidth=0,
01296                         **kwargs)
01297             #Note reportlab counts angles anti-clockwise from the horizontal
01298             #(as in mathematics, e.g. complex numbers and polar coordinates)
01299             #but we use clockwise from the vertical.  Also reportlab uses
01300             #degrees, but we use radians.
01301             p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
01302                      90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
01303                      moveTo=True)
01304             p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
01305                      90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
01306                      reverse=True)
01307             p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
01308             if abs(angle) < 0.5:
01309                 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos)
01310                 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
01311             else:
01312                 dx = min(0.1, abs(angle)/50.0) #auto-scale number of steps
01313                 x = dx
01314                 while x < 1:
01315                     r = outer_radius - x*(outer_radius-middle_radius)
01316                     a = headangle + x*(endangle-headangle)
01317                     p.lineTo(x0+r*sin(a), y0+r*cos(a))
01318                     x += dx
01319                 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos)
01320                 x = dx
01321                 while x < 1:
01322                     r = middle_radius - x*(middle_radius-inner_radius)
01323                     a = headangle + (1-x)*(endangle-headangle)
01324                     p.lineTo(x0+r*sin(a), y0+r*cos(a))
01325                     x += dx
01326                 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
01327             p.closePath()
01328             return p
01329         else:
01330             p = ArcPath(strokeColor=strokecolor,
01331                         fillColor=color,
01332                         #default is mitre/miter which can stick out too much:
01333                         strokeLineJoin=1, #1=round
01334                         strokewidth=0,
01335                         **kwargs)
01336             #Note reportlab counts angles anti-clockwise from the horizontal
01337             #(as in mathematics, e.g. complex numbers and polar coordinates)
01338             #but we use clockwise from the vertical.  Also reportlab uses
01339             #degrees, but we use radians.
01340             p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
01341                      90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
01342                      moveTo=True, reverse=True)
01343             p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
01344                      90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
01345                      reverse=False)
01346             p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
01347             #Note - two staight lines is only a good approximation for small
01348             #head angle, in general will need to curved lines here:
01349             if abs(angle) < 0.5:
01350                 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos)
01351                 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
01352             else:
01353                 dx = min(0.1, abs(angle)/50.0) #auto-scale number of steps
01354                 x = dx
01355                 while x < 1:
01356                     r = outer_radius - x*(outer_radius-middle_radius)
01357                     a = headangle + x*(startangle-headangle)
01358                     p.lineTo(x0+r*sin(a), y0+r*cos(a))
01359                     x += dx
01360                 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos)
01361                 x = dx
01362                 while x < 1:
01363                     r = middle_radius - x*(middle_radius-inner_radius)
01364                     a = headangle + (1-x)*(startangle-headangle)
01365                     p.lineTo(x0+r*sin(a), y0+r*cos(a))
01366                     x += dx
01367                 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
01368             p.closePath()
01369             return p
01370