[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-2012 by Peter Cock.
3  # This code is part of the Biopython distribution and governed by its
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 for GenomeDiagram."""
13
14  # ReportLab imports
15  from __future__ import print_function
16
17  from reportlab.graphics.shapes import Drawing, String, Group, Line, Circle, Polygon
18  from reportlab.lib import colors
19  from reportlab.graphics.shapes import ArcPath
20
21  from Bio._py3k import range
22
23  # GenomeDiagram imports
24  from ._AbstractDrawer import AbstractDrawer, draw_polygon, intermediate_points
25  from ._AbstractDrawer import _stroke_and_fill_colors
26  from ._FeatureSet import FeatureSet
27  from ._GraphSet import GraphSet
28
29  from math import pi, cos, sin
30
31
32 -class CircularDrawer(AbstractDrawer):
33      """Object for drawing circular diagrams.
34
35          o __init__(self, ...) Called on instantiation
36
37          o set_page_size(self, pagesize, orientation)    Set the page size to the
38                                                      passed size and orientation
39
40          o set_margins(self, x, y, xl, xr, yt, yb)   Set the drawable area of the
41                                                      page
42
43          o set_bounds(self, start, end)  Set the bounds for the elements to be
44                                          drawn
45
46          o is_in_bounds(self, value)     Returns a boolean for whether the position
47                                          is actually to be drawn
48
49          o __len__(self)     Returns the length of sequence that will be drawn
50
51
52          o draw(self)    Place the drawing elements on the diagram
53
54          o init_fragments(self)  Calculate information
55                                  about sequence fragment locations on the drawing
56
57          o set_track_heights(self)   Calculate information about the offset of
58                                      each track from the fragment base
59
60          o draw_test_tracks(self)    Add lines demarcating each track to the
61                                      drawing
62
63          o draw_track(self, track)   Return the contents of the passed track as
64                                      drawing elements
65
66          o draw_scale(self, track)   Return a scale for the passed track as
67                                      drawing elements
68
69          o draw_greytrack(self, track)   Return a grey background and superposed
70                                          label for the passed track as drawing
71                                          elements
72
73          o draw_feature_set(self, set)   Return the features in the passed set as
74                                          drawing elements
75
76          o draw_feature(self, feature)   Return a single feature as drawing
77                                          elements
78
79          o get_feature_sigil(self, feature, x0, x1, fragment)    Return a single
80                                          feature as its sigil in drawing elements
81
82          o draw_graph_set(self, set)     Return the data in a set of graphs as
83                                          drawing elements
84
85          o draw_line_graph(self, graph)  Return the data in a graph as a line
86                                          graph in drawing elements
87
88          o draw_heat_graph(self, graph)  Return the data in a graph as a heat
89                                          graph in drawing elements
90
91          o draw_bar_graph(self, graph)   Return the data in a graph as a bar
92                                          graph in drawing elements
93
94          o canvas_angle(self, base)      Return the angle, and cos and sin of
95                                          that angle, subtended by the passed
96                                          base position at the diagram center
97
99                      color)    Return a drawable element describing an arc
100
101          Attributes:
102
103          o tracklines    Boolean for whether to draw lines dilineating tracks
104
105          o pagesize      Tuple describing the size of the page in pixels
106
107          o x0            Float X co-ord for leftmost point of drawable area
108
109          o xlim          Float X co-ord for rightmost point of drawable area
110
111          o y0            Float Y co-ord for lowest point of drawable area
112
113          o ylim          Float Y co-ord for topmost point of drawable area
114
115          o pagewidth     Float pixel width of drawable area
116
117          o pageheight    Float pixel height of drawable area
118
119          o xcenter       Float X co-ord of center of drawable area
120
121          o ycenter       Float Y co-ord of center of drawable area
122
123          o start         Int, base to start drawing from
124
125          o end           Int, base to stop drawing at
126
127          o length        Size of sequence to be drawn
128
129          o track_size    Float (0->1) the proportion of the track height to
130                          draw in
131
132          o drawing       Drawing canvas
133
134          o drawn_tracks  List of ints denoting which tracks are to be drawn
135
136          o current_track_level   Int denoting which track is currently being
137                                  drawn
138
139          o track_offsets     Dictionary of number of pixels that each track top,
140                              center and bottom is offset from the base of a
141                              fragment, keyed by track
142
143          o sweep     Float (0->1) the proportion of the circle circumference to
144                      use for the diagram
145
146          o cross_track_links List of tuples each with four entries (track A,
147                              feature A, track B, feature B) to be linked.
148      """
149 -    def __init__(self, parent=None, pagesize='A3', orientation='landscape',
150                   x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
151                   start=None, end=None, tracklines=0, track_size=0.75,
153          """Create CircularDrawer object.
154
155              o parent    Diagram object containing the data that the drawer
156                          draws
157
158              o pagesize  String describing the ISO size of the image, or a tuple
159                          of pixels
160
161              o orientation   String describing the required orientation of the
162                              final drawing ('landscape' or 'portrait')
163
164              o x         Float (0->1) describing the relative size of the X
165                          margins to the page
166
167              o y         Float (0->1) describing the relative size of the Y
168                          margins to the page
169
170              o xl        Float (0->1) describing the relative size of the left X
171                          margin to the page (overrides x)
172
173              o xl        Float (0->1) describing the relative size of the left X
174                          margin to the page (overrides x)
175
176              o xr        Float (0->1) describing the relative size of the right X
177                          margin to the page (overrides x)
178
179              o yt        Float (0->1) describing the relative size of the top Y
180                          margin to the page (overrides y)
181
182              o yb        Float (0->1) describing the relative size of the lower Y
183                          margin to the page (overrides y)
184
185              o start     Int, the position to begin drawing the diagram at
186
187              o end       Int, the position to stop drawing the diagram at
188
189              o tracklines    Boolean flag to show (or not) lines delineating tracks
190                              on the diagram
191
192              o track_size    The proportion of the available track height that
193                              should be taken up in drawing
194
195              o circular      Boolean flaw to show whether the passed sequence is
196                              circular or not
197
198              o circle_core   The proportion of the available radius to leave
199                              empty at the center of a circular diagram (0 to 1).
200
201              o cross_track_links List of tuples each with four entries (track A,
202                                  feature A, track B, feature B) to be linked.
203          """
204          # Use the superclass' instantiation method
205          AbstractDrawer.__init__(self, parent, pagesize, orientation,
206                                    x, y, xl, xr, yt, yb, start, end,
208
209          # Useful measurements on the page
210          self.track_size = track_size
211          self.circle_core = circle_core
212          if not circular:   # Determine the proportion of the circumference
213              self.sweep = 0.9    # around which information will be drawn
214          else:
215              self.sweep = 1
216
217 -    def set_track_heights(self):
218          """Initialise track heights.
219
220          Since tracks may not be of identical heights, the bottom and top
221          radius for each track is stored in a dictionary - self.track_radii,
222          keyed by track number
223          """
224          bot_track = min(min(self.drawn_tracks), 1)
225          top_track = max(self.drawn_tracks)  # The 'highest' track to draw
226
227          trackunit_sum = 0  # Total number of 'units' taken up by all tracks
228          trackunits = {}  # Start and & units for each track keyed by track number
229          heightholder = 0  # placeholder variable
230          for track in range(bot_track, top_track + 1):  # track numbers to 'draw'
231              try:
232                  trackheight = self._parent[track].height  # Get track height
233              except Exception:  # TODO: ValueError? IndexError?
234                  trackheight = 1
235              trackunit_sum += trackheight  # increment total track unit height
236              trackunits[track] = (heightholder, heightholder + trackheight)
237              heightholder += trackheight  # move to next height
238
239          max_radius = 0.5 * min(self.pagewidth, self.pageheight)
240          trackunit_height = max_radius * (1 - self.circle_core) / trackunit_sum
241          track_core = max_radius * self.circle_core
242
243          # Calculate top and bottom radii for each track
244          self.track_radii = {}  # The inner, outer and center radii for each track
245          track_crop = trackunit_height * (1 - self.track_size) / 2.  # 'step back' in pixels
246          for track in trackunits:
247              top = trackunits[track][1] * trackunit_height - track_crop + track_core
248              btm = trackunits[track][0] * trackunit_height + track_crop + track_core
249              ctr = btm + (top - btm) / 2.
250              self.track_radii[track] = (btm, ctr, top)
251
252 -    def draw(self):
253          """Draw a circular diagram of the stored data."""
254          # Instantiate the drawing canvas
255          self.drawing = Drawing(self.pagesize[0], self.pagesize[1])
256
257          feature_elements = []           # holds feature elements
258          feature_labels = []             # holds feature labels
259          greytrack_bgs = []              # holds track background
260          greytrack_labels = []           # holds track foreground labels
261          scale_axes = []                 # holds scale axes
262          scale_labels = []               # holds scale axis labels
263
264          # Get tracks to be drawn and set track sizes
265          self.drawn_tracks = self._parent.get_drawn_levels()
266          self.set_track_heights()
267
268          # Go through each track in the parent (if it is to be drawn) one by
269          # one and collate the data as drawing elements
270          for track_level in self._parent.get_drawn_levels():
271              self.current_track_level = track_level
272              track = self._parent[track_level]
273              gbgs, glabels = self.draw_greytrack(track)    # Greytracks
274              greytrack_bgs.append(gbgs)
275              greytrack_labels.append(glabels)
276              features, flabels = self.draw_track(track)   # Features and graphs
277              feature_elements.append(features)
278              feature_labels.append(flabels)
279              if track.scale:
280                  axes, slabels = self.draw_scale(track)       # Scale axes
281                  scale_axes.append(axes)
282                  scale_labels.append(slabels)
283
289
290          # Groups listed in order of addition to page (from back to front)
291          # Draw track backgrounds
292          # Draw feature cross track links
293          # Draw features and graphs
294          # Draw scale axes
295          # Draw scale labels
296          # Draw feature labels
297          # Draw track labels
299                            feature_elements,
300                            scale_axes, scale_labels,
301                            feature_labels, greytrack_labels
302                            ]
303          for element_group in element_groups:
304              for element_list in element_group:
305                  [self.drawing.add(element) for element in element_list]
306
307          if self.tracklines:
308              # Draw test tracks over top of diagram
309              self.draw_test_tracks()
310
311 -    def draw_track(self, track):
312          """Returns list of track elements and list of track labels."""
313          track_elements = []  # Holds elements for features and graphs
314          track_labels = []   # Holds labels for features and graphs
315
316          # Distribution dictionary for dealing with different set types
317          set_methods = {FeatureSet: self.draw_feature_set,
318                         GraphSet: self.draw_graph_set
319                         }
320
321          for set in track.get_sets():  # Draw the feature or graph sets
322              elements, labels = set_methods[set.__class__](set)
323              track_elements += elements
324              track_labels += labels
325          return track_elements, track_labels
326
327 -    def draw_feature_set(self, set):
328          """Returns list of feature elements and list of labels for them."""
329          # print 'draw feature set'
330          feature_elements = []  # Holds diagram elements belonging to the features
331          label_elements = []   # Holds diagram elements belonging to feature labels
332
333          # Collect all the elements for the feature set
334          for feature in set.get_features():
335              if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end):
336                  features, labels = self.draw_feature(feature)
337                  feature_elements += features
338                  label_elements += labels
339
340          return feature_elements, label_elements
341
342 -    def draw_feature(self, feature):
343          """Returns list of feature elements and list of labels for them."""
344          feature_elements = []  # Holds drawable elements for a single feature
345          label_elements = []   # Holds labels for a single feature
346
347          if feature.hide:    # Don't show feature: return early
348              return feature_elements, label_elements
349
350          start, end = self._current_track_start_end()
351          # A single feature may be split into subfeatures, so loop over them
352          for locstart, locend in feature.locations:
353              if locend < start:
354                  continue
355              locstart = max(locstart, start)
356              if end < locstart:
357                  continue
358              locend = min(locend, end)
359              # Get sigil for the feature/ each subfeature
360              feature_sigil, label = self.get_feature_sigil(feature, locstart, locend)
361              feature_elements.append(feature_sigil)
362              if label is not None:   # If there's a label
363                  label_elements.append(label)
364
365          return feature_elements, label_elements
366
367 -    def get_feature_sigil(self, feature, locstart, locend, **kwargs):
368          """Returns graphics for feature, and any required label for it.
369
370              o feature       Feature object
371
372              o locstart      The start position of the feature
373
374              o locend        The end position of the feature
375          """
376          # Establish the co-ordinates for the sigil
377          btm, ctr, top = self.track_radii[self.current_track_level]
378
379          startangle, startcos, startsin = self.canvas_angle(locstart)
380          endangle, endcos, endsin = self.canvas_angle(locend)
381          midangle, midcos, midsin = self.canvas_angle(float(locend + locstart) / 2)
382
383          # Distribution dictionary for various ways of drawing the feature
384          # Each method takes the inner and outer radii, the start and end angle
385          # subtended at the diagram center, and the color as arguments
386          draw_methods = {'BOX': self._draw_sigil_box,
387                          'OCTO': self._draw_sigil_cut_corner_box,
388                          'JAGGY': self._draw_sigil_jaggy,
389                          'ARROW': self._draw_sigil_arrow,
390                          'BIGARROW': self._draw_sigil_big_arrow,
391                          }
392
393          # Get sigil for the feature, location dependent on the feature strand
394          method = draw_methods[feature.sigil]
396          kwargs['shaft_height_ratio'] = feature.arrowshaft_height
397
398          # Support for clickable links... needs ReportLab 2.4 or later
400          if hasattr(feature, "url"):
401              kwargs["hrefURL"] = feature.url
402              kwargs["hrefTitle"] = feature.name
403
404          sigil = method(btm, ctr, top, startangle, endangle, feature.strand,
405                         color=feature.color, border=feature.border, **kwargs)
406
407          if feature.label:   # Feature needs a label
408              # The spaces are a hack to force a little space between the label
409              # and the edge of the feature
410              label = String(0, 0, " %s " % feature.name.strip(),
411                             fontName=feature.label_font,
412                             fontSize=feature.label_size,
413                             fillColor=feature.label_color)
414              labelgroup = Group(label)
415              if feature.label_strand:
416                  strand = feature.label_strand
417              else:
418                  strand = feature.strand
419              if feature.label_position in ('start', "5'", 'left'):
420                  # Position the label at the feature's start
421                  if strand != -1:
422                      label_angle = startangle + 0.5 * pi  # Make text radial
423                      sinval, cosval = startsin, startcos
424                  else:
425                      label_angle = endangle + 0.5 * pi  # Make text radial
426                      sinval, cosval = endsin, endcos
427              elif feature.label_position in ('middle', 'center', 'centre'):
428                  # Position the label at the feature's midpoint
429                  label_angle = midangle + 0.5 * pi  # Make text radial
430                  sinval, cosval = midsin, midcos
431              elif feature.label_position in ('end', "3'", 'right'):
432                  # Position the label at the feature's end
433                  if strand != -1:
434                      label_angle = endangle + 0.5 * pi  # Make text radial
435                      sinval, cosval = endsin, endcos
436                  else:
437                      label_angle = startangle + 0.5 * pi  # Make text radial
438                      sinval, cosval = startsin, startcos
439              elif startangle < pi:
440                  # Default to placing the label the bottom of the feature
441                  # as drawn on the page, meaning feature end on left half
442                  label_angle = endangle + 0.5 * pi  # Make text radial
443                  sinval, cosval = endsin, endcos
444              else:
445                  # Default to placing the label on the bottom of the feature,
446                  # which means the feature end when on right hand half
447                  label_angle = startangle + 0.5 * pi  # Make text radial
448                  sinval, cosval = startsin, startcos
449              if strand != -1:
450                  # Feature label on top
452                  if startangle < pi:  # Turn text round
453                      label_angle -= pi
454                  else:
455                      labelgroup.contents[0].textAnchor = 'end'
456              else:
457                  # Feature label on bottom
459                  if startangle < pi:  # Turn text round and anchor end
460                      label_angle -= pi
461                      labelgroup.contents[0].textAnchor = 'end'
462              x_pos = self.xcenter + radius * sinval
463              y_pos = self.ycenter + radius * cosval
464              coslabel = cos(label_angle)
465              sinlabel = sin(label_angle)
466              labelgroup.transform = (coslabel, -sinlabel, sinlabel, coslabel,
467                                      x_pos, y_pos)
468          else:
469              # No label required
470              labelgroup = None
471          # if locstart > locend:
472          #    print locstart, locend, feature.strand, sigil, feature.name
473          # print locstart, locend, feature.name
474          return sigil, labelgroup
475
481
482          if not self.is_in_bounds(startA) \
483          and not self.is_in_bounds(endA):
484              return None
485          if not self.is_in_bounds(startB) \
486          and not self.is_in_bounds(endB):
487              return None
488
489          if startA < self.start:
490              startA = self.start
491          if startB < self.start:
492              startB = self.start
493          if self.end < endA:
494              endA = self.end
495          if self.end < endB:
496              endB = self.end
497
500          assert trackobjA is not None
501          assert trackobjB is not None
502          if trackobjA == trackobjB:
503              raise NotImplementedError()
504
505          if trackobjA.start is not None:
506              if endA < trackobjA.start:
507                  return
508              startA = max(startA, trackobjA.start)
509          if trackobjA.end is not None:
510              if trackobjA.end < startA:
511                  return
512              endA = min(endA, trackobjA.end)
513          if trackobjB.start is not None:
514              if endB < trackobjB.start:
515                  return
516              startB = max(startB, trackobjB.start)
517          if trackobjB.end is not None:
518              if trackobjB.end < startB:
519                  return
520              endB = min(endB, trackobjB.end)
521
522          for track_level in self._parent.get_drawn_levels():
523              track = self._parent[track_level]
524              if track == trackobjA:
525                  trackA = track_level
526              if track == trackobjB:
527                  trackB = track_level
528          if trackA == trackB:
529              raise NotImplementedError()
530
531          startangleA, startcosA, startsinA = self.canvas_angle(startA)
532          startangleB, startcosB, startsinB = self.canvas_angle(startB)
533          endangleA, endcosA, endsinA = self.canvas_angle(endA)
534          endangleB, endcosB, endsinB = self.canvas_angle(endB)
535
536          btmA, ctrA, topA = self.track_radii[trackA]
537          btmB, ctrB, topB = self.track_radii[trackB]
538
539          if ctrA < ctrB:
540              return [self._draw_arc_poly(topA, btmB,
541                             startangleA, endangleA,
542                             startangleB, endangleB,
544          else:
545              return [self._draw_arc_poly(btmA, topB,
546                             startangleA, endangleA,
547                             startangleB, endangleB,
549
550 -    def draw_graph_set(self, set):
551          """Returns list of graph elements and list of their labels.
552
553              o set       GraphSet object
554          """
555          # print 'draw graph set'
556          elements = []  # Holds graph elements
557
558          # Distribution dictionary for how to draw the graph
559          style_methods = {'line': self.draw_line_graph,
560                           'heat': self.draw_heat_graph,
561                           'bar': self.draw_bar_graph
562                           }
563
564          for graph in set.get_graphs():
565              elements += style_methods[graph.style](graph)
566
567          return elements, []
568
569 -    def draw_line_graph(self, graph):
570          """Returns line graph as list of drawable elements.
571
572              o graph     GraphData object
573          """
574          line_elements = []  # holds drawable elements
575
576          # Get graph data
577          data_quartiles = graph.quartiles()
578          minval, maxval = data_quartiles[0], data_quartiles[4]
579          btm, ctr, top = self.track_radii[self.current_track_level]
580          trackheight = 0.5 * (top - btm)
581          datarange = maxval - minval
582          if datarange == 0:
583              datarange = trackheight
584
585          start, end = self._current_track_start_end()
586          data = graph[start:end]
587
588          if not data:
589              return []
590
591          # midval is the value at which the x-axis is plotted, and is the
592          # central ring in the track
593          if graph.center is None:
594              midval = (maxval + minval) / 2.
595          else:
596              midval = graph.center
597          # Whichever is the greatest difference: max-midval or min-midval, is
598          # taken to specify the number of pixel units resolved along the
599          # y-axis
600          resolution = max((midval - minval), (maxval - midval))
601
602          # Start from first data point
603          pos, val = data[0]
604          lastangle, lastcos, lastsin = self.canvas_angle(pos)
605          # We calculate the track height
606          posheight = trackheight * (val - midval) / resolution + ctr
607          lastx = self.xcenter + posheight * lastsin  # start xy coords
608          lasty = self.ycenter + posheight * lastcos
609          for pos, val in data:
610              posangle, poscos, possin = self.canvas_angle(pos)
611              posheight = trackheight * (val - midval) / resolution + ctr
612              x = self.xcenter + posheight * possin   # next xy coords
613              y = self.ycenter + posheight * poscos
614              line_elements.append(Line(lastx, lasty, x, y,
615                                        strokeColor=graph.poscolor,
616                                        strokeWidth=graph.linewidth))
617              lastx, lasty, = x, y
618          return line_elements
619
620 -    def draw_bar_graph(self, graph):
621          """Returns list of drawable elements for a bar graph.
622
623              o graph     Graph object
624          """
625          # At each point contained in the graph data, we draw a vertical bar
626          # from the track center to the height of the datapoint value (positive
627          # values go up in one color, negative go down in the alternative
628          # color).
629          bar_elements = []
630
631          # Set the number of pixels per unit for the data
632          data_quartiles = graph.quartiles()
633          minval, maxval = data_quartiles[0], data_quartiles[4]
634          btm, ctr, top = self.track_radii[self.current_track_level]
635          trackheight = 0.5 * (top - btm)
636          datarange = maxval - minval
637          if datarange == 0:
638              datarange = trackheight
639          data = graph[self.start:self.end]
640          # midval is the value at which the x-axis is plotted, and is the
641          # central ring in the track
642          if graph.center is None:
643              midval = (maxval + minval) / 2.
644          else:
645              midval = graph.center
646
647          # Convert data into 'binned' blocks, covering half the distance to the
648          # next data point on either side, accounting for the ends of fragments
649          # and tracks
650          start, end = self._current_track_start_end()
651          data = intermediate_points(start, end, graph[start:end])
652
653          if not data:
654              return []
655
656          # Whichever is the greatest difference: max-midval or min-midval, is
657          # taken to specify the number of pixel units resolved along the
658          # y-axis
659          resolution = max((midval - minval), (maxval - midval))
660          if resolution == 0:
661              resolution = trackheight
662
663          # Create elements for the bar graph based on newdata
664          for pos0, pos1, val in data:
665              pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
666              pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
667
668              barval = trackheight * (val - midval) / resolution
669              if barval >= 0:
670                  barcolor = graph.poscolor
671              else:
672                  barcolor = graph.negcolor
673
674              # Draw bar
675              bar_elements.append(self._draw_arc(ctr, ctr + barval, pos0angle,
676                                                pos1angle, barcolor))
677          return bar_elements
678
679 -    def draw_heat_graph(self, graph):
680          """Returns list of drawable elements for the heat graph.
681
682              o graph     Graph object
683          """
684          # At each point contained in the graph data, we draw a box that is the
685          # full height of the track, extending from the midpoint between the
686          # previous and current data points to the midpoint between the current
687          # and next data points
688          heat_elements = []  # holds drawable elements
689
690          # Get graph data
691          data_quartiles = graph.quartiles()
692          minval, maxval = data_quartiles[0], data_quartiles[4]
693          midval = (maxval + minval) / 2.  # mid is the value at the X-axis
694          btm, ctr, top = self.track_radii[self.current_track_level]
695          trackheight = (top - btm)
696
697          start, end = self._current_track_start_end()
698          data = intermediate_points(start, end, graph[start:end])
699
700          # Create elements on the graph, indicating a large positive value by
701          # the graph's poscolor, and a large negative value by the graph's
702          # negcolor attributes
703          for pos0, pos1, val in data:
704              pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
705              pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
706
707              # Calculate the heat color, based on the differential between
708              # the value and the median value
709              heat = colors.linearlyInterpolatedColor(graph.poscolor,
710                                                      graph.negcolor,
711                                                      maxval, minval, val)
712
713              # Draw heat box
714              heat_elements.append(self._draw_arc(btm, top, pos0angle, pos1angle,
715                                                  heat, border=heat))
716          return heat_elements
717
718 -    def draw_scale(self, track):
719          """Returns list of elements in the scale and list of their labels.
720
721              o track     Track object
722          """
723          scale_elements = []  # holds axes and ticks
724          scale_labels = []  # holds labels
725
726          if not track.scale:
727              # no scale required, exit early
728              return [], []
729
730          # Get track locations
731          btm, ctr, top = self.track_radii[self.current_track_level]
732          trackheight = (top - ctr)
733
734          # X-axis
735          start, end = self._current_track_start_end()
736          if track.start is not None or track.end is not None:
737              # Draw an arc, leaving out the wedge
738              p = ArcPath(strokeColor=track.scale_color, fillColor=None)
739              startangle, startcos, startsin = self.canvas_angle(start)
740              endangle, endcos, endsin = self.canvas_angle(end)
742                       90 - (endangle * 180 / pi),
743                       90 - (startangle * 180 / pi))
744              scale_elements.append(p)
745              del p
746              # Y-axis start marker
747              x0, y0 = self.xcenter + btm * startsin, self.ycenter + btm * startcos
748              x1, y1 = self.xcenter + top * startsin, self.ycenter + top * startcos
749              scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
750              # Y-axis end marker
751              x0, y0 = self.xcenter + btm * endsin, self.ycenter + btm * endcos
752              x1, y1 = self.xcenter + top * endsin, self.ycenter + top * endcos
753              scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
754          elif self.sweep < 1:
755              # Draw an arc, leaving out the wedge
756              p = ArcPath(strokeColor=track.scale_color, fillColor=None)
757              # Note reportlab counts angles anti-clockwise from the horizontal
758              # (as in mathematics, e.g. complex numbers and polar coordinates)
759              # in degrees.
761                       startangledegrees=90 - 360 * self.sweep,
762                       endangledegrees=90)
763              scale_elements.append(p)
764              del p
765              # Y-axis start marker
766              x0, y0 = self.xcenter, self.ycenter + btm
767              x1, y1 = self.xcenter, self.ycenter + top
768              scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
769              # Y-axis end marker
770              alpha = 2 * pi * self.sweep
771              x0, y0 = self.xcenter + btm * sin(alpha), self.ycenter + btm * cos(alpha)
772              x1, y1 = self.xcenter + top * sin(alpha), self.ycenter + top * cos(alpha)
773              scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
774          else:
775              # Draw a full circle
776              scale_elements.append(Circle(self.xcenter, self.ycenter, ctr,
777                                           strokeColor=track.scale_color,
778                                           fillColor=None))
779
780          start, end = self._current_track_start_end()
781          if track.scale_ticks:  # Ticks are required on the scale
782              # Draw large ticks
783              # I want the ticks to be consistently positioned relative to
784              # the start of the sequence (position 0), not relative to the
785              # current viewpoint (self.start and self.end)
786
787              ticklen = track.scale_largeticks * trackheight
788              tickiterval = int(track.scale_largetick_interval)
789              # Note that we could just start the list of ticks using
790              # range(0,self.end,tickinterval) and the filter out the
791              # ones before self.start - but this seems wasteful.
792              # Using tickiterval * (self.start/tickiterval) is a shortcut.
793              for tickpos in range(tickiterval * (self.start // tickiterval),
794                                   int(self.end), tickiterval):
795                  if tickpos <= start or end <= tickpos:
796                      continue
797                  tick, label = self.draw_tick(tickpos, ctr, ticklen,
798                                               track,
799                                               track.scale_largetick_labels)
800                  scale_elements.append(tick)
801                  if label is not None:  # If there's a label, add it
802                      scale_labels.append(label)
803              # Draw small ticks
804              ticklen = track.scale_smallticks * trackheight
805              tickiterval = int(track.scale_smalltick_interval)
806              for tickpos in range(tickiterval * (self.start // tickiterval),
807                                   int(self.end), tickiterval):
808                  if tickpos <= start or end <= tickpos:
809                      continue
810                  tick, label = self.draw_tick(tickpos, ctr, ticklen,
811                                               track,
812                                               track.scale_smalltick_labels)
813                  scale_elements.append(tick)
814                  if label is not None:  # If there's a label, add it
815                      scale_labels.append(label)
816
817          # Check to see if the track contains a graph - if it does, get the
818          # minimum and maximum values, and put them on the scale Y-axis
819          # at 60 degree intervals, ordering the labels by graph_id
820          startangle, startcos, startsin = self.canvas_angle(start)
821          endangle, endcos, endsin = self.canvas_angle(end)
822          if track.axis_labels:
823              for set in track.get_sets():
824                  if set.__class__ is GraphSet:
825                      # Y-axis
826                      for n in range(7):
827                          angle = n * 1.0471975511965976
828                          if angle < startangle or endangle < angle:
829                              continue
830                          ticksin, tickcos = sin(angle), cos(angle)
831                          x0, y0 = self.xcenter + btm * ticksin, self.ycenter + btm * tickcos
832                          x1, y1 = self.xcenter + top * ticksin, self.ycenter + top * tickcos
833                          scale_elements.append(Line(x0, y0, x1, y1,
834                                                     strokeColor=track.scale_color))
835
836                          graph_label_min = []
837                          graph_label_max = []
838                          graph_label_mid = []
839                          for graph in set.get_graphs():
840                              quartiles = graph.quartiles()
841                              minval, maxval = quartiles[0], quartiles[4]
842                              if graph.center is None:
843                                  midval = (maxval + minval) / 2.
844                                  graph_label_min.append("%.3f" % minval)
845                                  graph_label_max.append("%.3f" % maxval)
846                                  graph_label_mid.append("%.3f" % midval)
847                              else:
848                                  diff = max((graph.center - minval),
849                                             (maxval - graph.center))
850                                  minval = graph.center - diff
851                                  maxval = graph.center + diff
852                                  midval = graph.center
853                                  graph_label_mid.append("%.3f" % midval)
854                                  graph_label_min.append("%.3f" % minval)
855                                  graph_label_max.append("%.3f" % maxval)
856                          xmid, ymid = (x0 + x1) / 2., (y0 + y1) / 2.
857                          for limit, x, y, in [(graph_label_min, x0, y0),
858                                               (graph_label_max, x1, y1),
859                                               (graph_label_mid, xmid, ymid)]:
860                              label = String(0, 0, ";".join(limit),
861                                             fontName=track.scale_font,
862                                             fontSize=track.scale_fontsize,
863                                             fillColor=track.scale_color)
864                              label.textAnchor = 'middle'
865                              labelgroup = Group(label)
866                              labelgroup.transform = (tickcos, -ticksin,
867                                                      ticksin, tickcos,
868                                                      x, y)
869                              scale_labels.append(labelgroup)
870
871          return scale_elements, scale_labels
872
873 -    def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
874          """Returns drawing element for a tick on the scale.
875
876              o tickpos   Int, position of the tick on the sequence
877
878              o ctr       Float, Y co-ord of the center of the track
879
880              o ticklen   How long to draw the tick
881
882              o track     Track, the track the tick is drawn on
883
884              o draw_label    Boolean, write the tick label?
885          """
886          # Calculate tick co-ordinates
887          tickangle, tickcos, ticksin = self.canvas_angle(tickpos)
888          x0, y0 = self.xcenter + ctr * ticksin, self.ycenter + ctr * tickcos
889          x1, y1 = self.xcenter + (ctr + ticklen) * ticksin, self.ycenter + (ctr + ticklen) * tickcos
890          # Calculate height of text label so it can be offset on lower half
891          # of diagram
892          # LP: not used, as not all fonts have ascent_descent data in reportlab.pdfbase._fontdata
893          # label_offset = _fontdata.ascent_descent[track.scale_font][0]*\
894          #               track.scale_fontsize/1000.
895          tick = Line(x0, y0, x1, y1, strokeColor=track.scale_color)
896          if draw_label:
897              # Put tick position on as label
898              if track.scale_format == 'SInt':
899                  if tickpos >= 1000000:
900                      tickstring = str(tickpos // 1000000) + " Mbp"
901                  elif tickpos >= 1000:
902                      tickstring = str(tickpos // 1000) + " Kbp"
903                  else:
904                      tickstring = str(tickpos)
905              else:
906                  tickstring = str(tickpos)
907              label = String(0, 0, tickstring,  # Make label string
908                             fontName=track.scale_font,
909                             fontSize=track.scale_fontsize,
910                             fillColor=track.scale_color)
911              if tickangle > pi:
912                  label.textAnchor = 'end'
913              # LP: This label_offset depends on ascent_descent data, which is not available for all
914              # fonts, so has been deprecated.
915              # if 0.5*pi < tickangle < 1.5*pi:
916              #    y1 -= label_offset
917              labelgroup = Group(label)
918              labelgroup.transform = (1, 0, 0, 1, x1, y1)
919          else:
920              labelgroup = None
921          return tick, labelgroup
922
923 -    def draw_test_tracks(self):
924          """Draw blue test tracks with grene line down their center."""
925          # Add lines only for drawn tracks
926          for track in self.drawn_tracks:
927              btm, ctr, top = self.track_radii[track]
929                                      strokeColor=colors.blue,
930                                      fillColor=None))  # top line
932                                      strokeColor=colors.green,
933                                      fillColor=None))  # middle line
935                                      strokeColor=colors.blue,
936                                      fillColor=None))  # bottom line
937
938 -    def draw_greytrack(self, track):
939          """Drawing element for grey background to passed track.
940
941              o track     Track object
942          """
943          greytrack_bgs = []  # Holds track backgrounds
944          greytrack_labels = []  # Holds track foreground labels
945
946          if not track.greytrack:  # No greytrack required, return early
947              return [], []
948
949          # Get track location
950          btm, ctr, top = self.track_radii[self.current_track_level]
951
952          start, end = self._current_track_start_end()
953          startangle, startcos, startsin = self.canvas_angle(start)
954          endangle, endcos, endsin = self.canvas_angle(end)
955
956          # Make background
957          if track.start is not None or track.end is not None:
958              # Draw an arc, leaving out the wedge
959              p = ArcPath(strokeColor=track.scale_color, fillColor=None)
960              greytrack_bgs.append(self._draw_arc(btm, top, startangle, endangle,
961                                   colors.Color(0.96, 0.96, 0.96)))
962          elif self.sweep < 1:
963              # Make a partial circle, a large arc box
964              # This method assumes the correct center for us.
965              greytrack_bgs.append(self._draw_arc(btm, top, 0, 2 * pi * self.sweep,
966                                   colors.Color(0.96, 0.96, 0.96)))
967          else:
968              # Make a full circle (using a VERY thick linewidth)
969              greytrack_bgs.append(Circle(self.xcenter, self.ycenter, ctr,
970                                   strokeColor=colors.Color(0.96, 0.96, 0.96),
971                                   fillColor=None, strokeWidth=top - btm))
972
973          if track.greytrack_labels:
974              # Labels are required for this track
975              labelstep = self.length // track.greytrack_labels  # label interval
976              for pos in range(self.start, self.end, labelstep):
977                  label = String(0, 0, track.name,            # Add a new label at
978                             fontName=track.greytrack_font,   # each interval
979                             fontSize=track.greytrack_fontsize,
980                             fillColor=track.greytrack_fontcolor)
981                  theta, costheta, sintheta = self.canvas_angle(pos)
982                  if theta < startangle or endangle < theta:
983                      continue
984                  x, y = self.xcenter + btm * sintheta, self.ycenter + btm * costheta  # start text halfway up marker
985                  labelgroup = Group(label)
986                  labelangle = self.sweep * 2 * pi * (pos - self.start) / self.length - pi / 2
987                  if theta > pi:
988                      label.textAnchor = 'end'    # Anchor end of text to inner radius
989                      labelangle += pi            # and reorient it
990                  cosA, sinA = cos(labelangle), sin(labelangle)
991                  labelgroup.transform = (cosA, -sinA, sinA,
992                                          cosA, x, y)
993                  if not self.length - x <= labelstep:  # Don't overrun the circle
994                      greytrack_labels.append(labelgroup)
995
996          return greytrack_bgs, greytrack_labels
997
998 -    def canvas_angle(self, base):
999          """Given base-pair position, return (angle, cosine, sin)."""
1000          angle = self.sweep * 2 * pi * (base - self.start) / self.length
1001          return (angle, cos(angle), sin(angle))
1002
1003 -    def _draw_sigil_box(self, bottom, center, top,
1004                          startangle, endangle, strand,
1005                          **kwargs):
1006          """Draw BOX sigil."""
1007          if strand == 1:
1010          elif strand == -1:
1013          else:
1017
1019                   color, border=None, colour=None, **kwargs):
1020          """Returns close path describing an arc box.
1021
1022              o inner_radius  Float distance of inside of arc from drawing center
1023
1024              o outer_radius  Float distance of outside of arc from drawing center
1025
1026              o startangle    Float angle subtended by start of arc at drawing center
1028
1029              o endangle      Float angle subtended by end of arc at drawing center
1031
1032              o color        colors.Color object for arc (overridden by backwards
1033                             compatible argument with UK spelling, colour).
1034
1035          Returns a closed path object describing an arced box corresponding to
1036          the passed values.  For very small angles, a simple four sided
1037          polygon is used.
1038          """
1039          # Let the UK spelling (colour) override the USA spelling (color)
1040          if colour is not None:
1041              color = colour
1042
1043          strokecolor, color = _stroke_and_fill_colors(color, border)
1044
1045          if abs(float(endangle - startangle)) > .01:
1046              # Wide arc, must use full curves
1047              p = ArcPath(strokeColor=strokecolor,
1048                          fillColor=color,
1049                          strokewidth=0)
1050              # Note reportlab counts angles anti-clockwise from the horizontal
1051              # (as in mathematics, e.g. complex numbers and polar coordinates)
1052              # but we use clockwise from the vertical.  Also reportlab uses
1053              # degrees, but we use radians.
1055                       90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
1056                       moveTo=True)
1058                       90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
1059                       reverse=True)
1060              p.closePath()
1061              return p
1062          else:
1063              # Cheat and just use a four sided polygon.
1064              # Calculate trig values for angle and coordinates
1065              startcos, startsin = cos(startangle), sin(startangle)
1066              endcos, endsin = cos(endangle), sin(endangle)
1067              x0, y0 = self.xcenter, self.ycenter  # origin of the circle
1068              x1, y1 = (x0 + inner_radius * startsin, y0 + inner_radius * startcos)
1069              x2, y2 = (x0 + inner_radius * endsin, y0 + inner_radius * endcos)
1070              x3, y3 = (x0 + outer_radius * endsin, y0 + outer_radius * endcos)
1071              x4, y4 = (x0 + outer_radius * startsin, y0 + outer_radius * startcos)
1072              return draw_polygon([(x1, y1), (x2, y2), (x3, y3), (x4, y4)], color, border)
1073
1075                         move=False):
1076          """Adds a list of points to a path object.
1077
1078          Assumes angles given are in degrees!
1079
1080          Represents what would be a straight line on a linear diagram.
1081          """
1082          x0, y0 = self.xcenter, self.ycenter  # origin of the circle
1084          angle_diff = end_angle - start_angle
1085          dx = 0.01  # heuristic
1086          a = start_angle * pi / 180
1087          if move:
1089          else:
1091          x = dx
1092          if 0.01 <= abs(dx):
1093              while x < 1:
1095                  a = (start_angle + x * (angle_diff)) * pi / 180  # to radians for sin/cos
1096                  # print x0+r*cos(a), y0+r*sin(a)
1097                  path.lineTo(x0 + r * cos(a), y0 + r * sin(a))
1098                  x += dx
1099          a = end_angle * pi / 180
1101
1103                         inner_startangle, inner_endangle,
1104                         outer_startangle, outer_endangle,
1105                         color, border=None, flip=False,
1106                         **kwargs):
1107          """Returns polygon path describing an arc."""
1108          strokecolor, color = _stroke_and_fill_colors(color, border)
1109
1110          x0, y0 = self.xcenter, self.ycenter      # origin of the circle
1111          if abs(inner_endangle - outer_startangle) > 0.01 \
1112          or abs(outer_endangle - inner_startangle) > 0.01 \
1113          or abs(inner_startangle - outer_startangle) > 0.01 \
1114          or abs(outer_startangle - outer_startangle) > 0.01:
1115              # Wide arc, must use full curves
1116              p = ArcPath(strokeColor=strokecolor,
1117                          fillColor=color,
1118                          # default is mitre/miter which can stick out too much:
1119                          strokeLineJoin=1,  # 1=round
1120                          strokewidth=0)
1121              # Note reportlab counts angles anti-clockwise from the horizontal
1122              # (as in mathematics, e.g. complex numbers and polar coordinates)
1123              # but we use clockwise from the vertical.  Also reportlab uses
1124              # degrees, but we use radians.
1125              i_start = 90 - (inner_startangle * 180 / pi)
1126              i_end = 90 - (inner_endangle * 180 / pi)
1127              o_start = 90 - (outer_startangle * 180 / pi)
1128              o_end = 90 - (outer_endangle * 180 / pi)
1130                       moveTo=True, reverse=True)
1131              if flip:
1132                  # Flipped, join end to start,
1136              else:
1137                  # Not flipped, join start to start, end to end
1140                           reverse=False)
1142              p.closePath()
1143              return p
1144          else:
1145              # Cheat and just use a four sided polygon.
1146              # Calculate trig values for angle and coordinates
1147              inner_startcos, inner_startsin = cos(inner_startangle), sin(inner_startangle)
1148              inner_endcos, inner_endsin = cos(inner_endangle), sin(inner_endangle)
1149              outer_startcos, outer_startsin = cos(outer_startangle), sin(outer_startangle)
1150              outer_endcos, outer_endsin = cos(outer_endangle), sin(outer_endangle)
1151              x1, y1 = (x0 + inner_radius * inner_startsin, y0 + inner_radius * inner_startcos)
1152              x2, y2 = (x0 + inner_radius * inner_endsin, y0 + inner_radius * inner_endcos)
1153              x3, y3 = (x0 + outer_radius * outer_endsin, y0 + outer_radius * outer_endcos)
1154              x4, y4 = (x0 + outer_radius * outer_startsin, y0 + outer_radius * outer_startcos)
1155              return draw_polygon([(x1, y1), (x2, y2), (x3, y3), (x4, y4)], color, border,
1156                                  # default is mitre/miter which can stick out too much:
1157                                  strokeLineJoin=1,  # 1=round
1158                                  )
1159
1160 -    def _draw_sigil_cut_corner_box(self, bottom, center, top,
1161                            startangle, endangle, strand,
1162                            color, border=None, corner=0.5,
1163                            **kwargs):
1164          """Draw OCTO sigil, box with corners cut off."""
1165          if strand == 1:
1168          elif strand == -1:
1171          else:
1174
1175          strokecolor, color = _stroke_and_fill_colors(color, border)
1176
1177          startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1178          angle = float(endangle - startangle)
1179
1182
1183          corner_len = min(0.5 * boxheight, 0.5 * boxheight * corner)
1186
1187          cornerangle_delta = max(0.0, min(abs(boxheight) * 0.5 * corner / middle_radius, abs(angle * 0.5)))
1188          if angle < 0:
1189              cornerangle_delta *= -1  # reverse it
1190
1191          # Calculate trig values for angle and coordinates
1192          startcos, startsin = cos(startangle), sin(startangle)
1193          endcos, endsin = cos(endangle), sin(endangle)
1194          x0, y0 = self.xcenter, self.ycenter      # origin of the circle
1195          p = ArcPath(strokeColor=strokecolor,
1196                      fillColor=color,
1197                      strokeLineJoin=1,  # 1=round
1198                      strokewidth=0,
1199                      **kwargs)
1200          # Inner curved edge
1202                   90 - ((endangle - cornerangle_delta) * 180 / pi),
1203                   90 - ((startangle + cornerangle_delta) * 180 / pi),
1204                   moveTo=True)
1205          # Corner edge - straight lines assumes small angle!
1206          # TODO - Use self._draw_arc_line(p, ...) here if we expose corner setting
1209          # Outer curved edge
1211                   90 - ((endangle - cornerangle_delta) * 180 / pi),
1212                   90 - ((startangle + cornerangle_delta) * 180 / pi),
1213                   reverse=True)
1214          # Corner edges
1217          p.closePath()
1218          return p
1219
1220 -    def _draw_sigil_arrow(self, bottom, center, top,
1221                            startangle, endangle, strand,
1222                            **kwargs):
1223          """Draw ARROW sigil."""
1224          if strand == 1:
1227              orientation = "right"
1228          elif strand == -1:
1231              orientation = "left"
1232          else:
1235              orientation = "right"  # backwards compatibility
1237                                      orientation=orientation, **kwargs)
1238
1239 -    def _draw_sigil_big_arrow(self, bottom, center, top,
1240                                startangle, endangle, strand,
1241                                **kwargs):
1242          """Draw BIGARROW sigil, like ARROW but straddles the axis."""
1243          if strand == -1:
1244              orientation = "left"
1245          else:
1246              orientation = "right"
1247          return self._draw_arc_arrow(bottom, top, startangle, endangle,
1248                                      orientation=orientation, **kwargs)
1249
1251                    color, border=None,
1253                    colour=None, **kwargs):
1254          """Draw an arrow along an arc."""
1255          # Let the UK spelling (colour) override the USA spelling (color)
1256          if colour is not None:
1257              color = colour
1258
1259          strokecolor, color = _stroke_and_fill_colors(color, border)
1260
1261          # if orientation == 'right':
1262          #    startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1263          # elif orientation == 'left':
1264          #    startangle, endangle = max(startangle, endangle), min(startangle, endangle)
1265          # else:
1266          startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1267          if orientation != "left" and orientation != "right":
1268              raise ValueError("Invalid orientation %s, should be 'left' or 'right'"
1269                               % repr(orientation))
1270
1271          angle = float(endangle - startangle)    # angle subtended by arc
1274          shaft_height = boxheight * shaft_height_ratio
1278          if angle < 0:
1279              headangle_delta *= -1  # reverse it
1280          if orientation == "right":
1282          else:
1284          if startangle <= endangle:
1286          else:
1288          assert startangle <= headangle <= endangle \
1289              or endangle <= headangle <= startangle, \
1291
1292          # Calculate trig values for angle and coordinates
1293          startcos, startsin = cos(startangle), sin(startangle)
1295          endcos, endsin = cos(endangle), sin(endangle)
1296          x0, y0 = self.xcenter, self.ycenter      # origin of the circle
1297          if 0.5 >= abs(angle) and abs(headangle_delta) >= abs(angle):
1298              # If the angle is small, and the arrow is all head,
1299              # cheat and just use a triangle.
1300              if orientation == "right":
1301                  x1, y1 = (x0 + inner_radius * startsin, y0 + inner_radius * startcos)
1302                  x2, y2 = (x0 + outer_radius * startsin, y0 + outer_radius * startcos)
1303                  x3, y3 = (x0 + middle_radius * endsin, y0 + middle_radius * endcos)
1304              else:
1305                  x1, y1 = (x0 + inner_radius * endsin, y0 + inner_radius * endcos)
1306                  x2, y2 = (x0 + outer_radius * endsin, y0 + outer_radius * endcos)
1307                  x3, y3 = (x0 + middle_radius * startsin, y0 + middle_radius * startcos)
1308              # return draw_polygon([(x1,y1),(x2,y2),(x3,y3)], color, border,
1309              #                    stroke_line_join=1)
1310              return Polygon([x1, y1, x2, y2, x3, y3],
1311                             strokeColor=border or color,
1312                             fillColor=color,
1313                             strokeLineJoin=1,  # 1=round, not mitre!
1314                             strokewidth=0)
1315          elif orientation == "right":
1316              p = ArcPath(strokeColor=strokecolor,
1317                          fillColor=color,
1318                          # default is mitre/miter which can stick out too much:
1319                          strokeLineJoin=1,  # 1=round
1320                          strokewidth=0,
1321                          **kwargs)
1322              # Note reportlab counts angles anti-clockwise from the horizontal
1323              # (as in mathematics, e.g. complex numbers and polar coordinates)
1324              # but we use clockwise from the vertical.  Also reportlab uses
1325              # degrees, but we use radians.
1327                       90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1328                       moveTo=True)
1330                       90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1331                       reverse=True)
1332              if abs(angle) < 0.5:
1336              else:
1338                                      90 - (headangle * 180 / pi),
1339                                      90 - (endangle * 180 / pi))
1341                                      90 - (endangle * 180 / pi),
1342                                      90 - (headangle * 180 / pi))
1343              p.closePath()
1344              return p
1345          else:
1346              p = ArcPath(strokeColor=strokecolor,
1347                          fillColor=color,
1348                          # default is mitre/miter which can stick out too much:
1349                          strokeLineJoin=1,  # 1=round
1350                          strokewidth=0,
1351                          **kwargs)
1352              # Note reportlab counts angles anti-clockwise from the horizontal
1353              # (as in mathematics, e.g. complex numbers and polar coordinates)
1354              # but we use clockwise from the vertical.  Also reportlab uses
1355              # degrees, but we use radians.
1357                       90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1358                       moveTo=True, reverse=True)
1360                       90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1361                       reverse=False)
1362              # Note - two staight lines is only a good approximation for small
1363              # head angle, in general will need to curved lines here:
1364              if abs(angle) < 0.5:
1368              else:
1370                                      90 - (headangle * 180 / pi),
1371                                      90 - (startangle * 180 / pi))
1373                                      90 - (startangle * 180 / pi),
1374                                      90 - (headangle * 180 / pi))
1375              p.closePath()
1376              return p
1377
1378 -    def _draw_sigil_jaggy(self, bottom, center, top,
1379                            startangle, endangle, strand,
1380                            color, border=None,
1381                            **kwargs):
1382          """Draw JAGGY sigil.
1383
1384          Although we may in future expose the head/tail jaggy lengths, for now
1385          both the left and right edges are drawn jagged.
1386          """
1387          if strand == 1:
1390              teeth = 2
1391          elif strand == -1:
1394              teeth = 2
1395          else:
1398              teeth = 4
1399
1400          # TODO, expose these settings?
1401          tail_length_ratio = 1.0
1403
1404          strokecolor, color = _stroke_and_fill_colors(color, border)
1405
1406          startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1407          angle = float(endangle - startangle)    # angle subtended by arc
1409
1410          assert startangle <= endangle and angle >= 0
1412              headangle = max(endangle - min(height * head_length_ratio / (center * teeth), angle * 0.5), startangle)
1413              tailangle = min(startangle + min(height * tail_length_ratio / (center * teeth), angle * 0.5), endangle)
1414              # With very small features, can due to floating point calculations
1415              # violate the assertion below that start <= tail <= head <= end
1418              headangle = max(endangle - min(height * head_length_ratio / (center * teeth), angle), startangle)
1419              tailangle = startangle
1420          else:
1422              tailangle = min(startangle + min(height * tail_length_ratio / (center * teeth), angle), endangle)
1423
1424          assert startangle <= tailangle <= headangle <= endangle, \
1425              (startangle, tailangle, headangle, endangle, angle)
1426
1427          # Calculate trig values for angle and coordinates
1428          startcos, startsin = cos(startangle), sin(startangle)
1430          endcos, endsin = cos(endangle), sin(endangle)
1431          x0, y0 = self.xcenter, self.ycenter      # origin of the circle
1432
1433          p = ArcPath(strokeColor=strokecolor,
1434                      fillColor=color,
1435                      # default is mitre/miter which can stick out too much:
1436                      strokeLineJoin=1,  # 1=round
1437                      strokewidth=0,
1438                      **kwargs)
1439          # Note reportlab counts angles anti-clockwise from the horizontal
1440          # (as in mathematics, e.g. complex numbers and polar coordinates)
1441          # but we use clockwise from the vertical.  Also reportlab uses
1442          # degrees, but we use radians.
1444                   90 - (headangle * 180 / pi), 90 - (tailangle * 180 / pi),
1445                   moveTo=True)
1446          for i in range(0, teeth):
1448                       90 - (tailangle * 180 / pi), 90 - (startangle * 180 / pi))
1449              # Curved line needed when drawing long jaggies
1450              self._draw_arc_line(p,
1451                                  inner_radius + i * height / teeth,
1452                                  inner_radius + (i + 1) * height / teeth,
1453                                  90 - (startangle * 180 / pi),
1454                                  90 - (tailangle * 180 / pi))
1456                   90 - (headangle * 180 / pi), 90 - (tailangle * 180 / pi),
1457                   reverse=True)
1458          for i in range(0, teeth):
1460                       outer_radius - i * height / teeth,
1461                       90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1462                       reverse=True)
1463              # Curved line needed when drawing long jaggies
1464              self._draw_arc_line(p,
1465                                  outer_radius - i * height / teeth,
1466                                  outer_radius - (i + 1) * height / teeth,
1467                                  90 - (endangle * 180 / pi),
1468                                  90 - (headangle * 180 / pi))
1469          p.closePath()
1470          return p
1471
<!--
expandto(location.href);
// -->

```

 Generated by Epydoc 3.0.1 on Thu Aug 25 13:17:04 2016 http://epydoc.sourceforge.net