1
2
3
4
5
6
7
8
9
10
11
12 """ CircularDrawer module
13
14 Provides:
15
16 o CircularDrawer - Drawing object for circular diagrams
17
18 For drawing capabilities, this module uses reportlab to draw and write
19 the diagram:
20
21 http://www.reportlab.com
22
23 For dealing with biological information, the package expects BioPython
24 objects:
25
26 http://www.biopython.org
27 """
28
29
30 from reportlab.graphics.shapes import *
31 from reportlab.lib import colors
32 from reportlab.pdfbase import _fontdata
33 from reportlab.graphics.shapes import ArcPath
34
35
36 from _AbstractDrawer import AbstractDrawer, draw_polygon, intermediate_points
37 from _FeatureSet import FeatureSet
38 from _GraphSet import GraphSet
39
40 from math import ceil, pi, cos, sin, asin
41
43 """ CircularDrawer(AbstractDrawer)
44
45 Inherits from:
46
47 o AbstractDrawer
48
49 Provides:
50
51 Methods:
52
53 o __init__(self, parent=None, pagesize='A3', orientation='landscape',
54 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
55 start=None, end=None, tracklines=0, track_size=0.75,
56 circular=1) Called on instantiation
57
58 o set_page_size(self, pagesize, orientation) Set the page size to the
59 passed size and orientation
60
61 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the
62 page
63
64 o set_bounds(self, start, end) Set the bounds for the elements to be
65 drawn
66
67 o is_in_bounds(self, value) Returns a boolean for whether the position
68 is actually to be drawn
69
70 o __len__(self) Returns the length of sequence that will be drawn
71
72
73 o draw(self) Place the drawing elements on the diagram
74
75 o init_fragments(self) Calculate information
76 about sequence fragment locations on the drawing
77
78 o set_track_heights(self) Calculate information about the offset of
79 each track from the fragment base
80
81 o draw_test_tracks(self) Add lines demarcating each track to the
82 drawing
83
84 o draw_track(self, track) Return the contents of the passed track as
85 drawing elements
86
87 o draw_scale(self, track) Return a scale for the passed track as
88 drawing elements
89
90 o draw_greytrack(self, track) Return a grey background and superposed
91 label for the passed track as drawing
92 elements
93
94 o draw_feature_set(self, set) Return the features in the passed set as
95 drawing elements
96
97 o draw_feature(self, feature) Return a single feature as drawing
98 elements
99
100 o get_feature_sigil(self, feature, x0, x1, fragment) Return a single
101 feature as its sigil in drawing elements
102
103 o draw_graph_set(self, set) Return the data in a set of graphs as
104 drawing elements
105
106 o draw_line_graph(self, graph) Return the data in a graph as a line
107 graph in drawing elements
108
109 o draw_heat_graph(self, graph) Return the data in a graph as a heat
110 graph in drawing elements
111
112 o draw_bar_graph(self, graph) Return the data in a graph as a bar
113 graph in drawing elements
114
115 o canvas_angle(self, base) Return the angle, and cos and sin of
116 that angle, subtended by the passed
117 base position at the diagram center
118
119 o draw_arc(self, inner_radius, outer_radius, startangle, endangle,
120 color) Return a drawable element describing an arc
121
122 Attributes:
123
124 o tracklines Boolean for whether to draw lines dilineating tracks
125
126 o pagesize Tuple describing the size of the page in pixels
127
128 o x0 Float X co-ord for leftmost point of drawable area
129
130 o xlim Float X co-ord for rightmost point of drawable area
131
132 o y0 Float Y co-ord for lowest point of drawable area
133
134 o ylim Float Y co-ord for topmost point of drawable area
135
136 o pagewidth Float pixel width of drawable area
137
138 o pageheight Float pixel height of drawable area
139
140 o xcenter Float X co-ord of center of drawable area
141
142 o ycenter Float Y co-ord of center of drawable area
143
144 o start Int, base to start drawing from
145
146 o end Int, base to stop drawing at
147
148 o length Size of sequence to be drawn
149
150 o track_size Float (0->1) the proportion of the track height to
151 draw in
152
153 o drawing Drawing canvas
154
155 o drawn_tracks List of ints denoting which tracks are to be drawn
156
157 o current_track_level Int denoting which track is currently being
158 drawn
159
160 o track_offsets Dictionary of number of pixels that each track top,
161 center and bottom is offset from the base of a
162 fragment, keyed by track
163
164 o sweep Float (0->1) the proportion of the circle circumference to
165 use for the diagram
166
167 """
168 - def __init__(self, parent=None, pagesize='A3', orientation='landscape',
169 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
170 start=None, end=None, tracklines=0, track_size=0.75,
171 circular=1):
172 """ __init__(self, parent, pagesize='A3', orientation='landscape',
173 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
174 start=None, end=None, tracklines=0, track_size=0.75,
175 circular=1)
176
177 o parent Diagram object containing the data that the drawer
178 draws
179
180 o pagesize String describing the ISO size of the image, or a tuple
181 of pixels
182
183 o orientation String describing the required orientation of the
184 final drawing ('landscape' or 'portrait')
185
186 o x Float (0->1) describing the relative size of the X
187 margins to the page
188
189 o y Float (0->1) describing the relative size of the Y
190 margins to the page
191
192 o xl Float (0->1) describing the relative size of the left X
193 margin to the page (overrides x)
194
195 o xl Float (0->1) describing the relative size of the left X
196 margin to the page (overrides x)
197
198 o xr Float (0->1) describing the relative size of the right X
199 margin to the page (overrides x)
200
201 o yt Float (0->1) describing the relative size of the top Y
202 margin to the page (overrides y)
203
204 o yb Float (0->1) describing the relative size of the lower Y
205 margin to the page (overrides y)
206
207 o start Int, the position to begin drawing the diagram at
208
209 o end Int, the position to stop drawing the diagram at
210
211 o tracklines Boolean flag to show (or not) lines delineating tracks
212 on the diagram
213
214 o track_size The proportion of the available track height that
215 should be taken up in drawing
216
217 o circular Boolean flaw to show whether the passed sequence is
218 circular or not
219 """
220
221 AbstractDrawer.__init__(self, parent, pagesize, orientation,
222 x, y, xl, xr, yt, yb, start, end,
223 tracklines)
224
225
226 self.track_size = track_size
227 if circular == False:
228 self.sweep = 0.9
229 else:
230 self.sweep = 1
231
232
234 """ set_track_heights(self)
235
236 Since tracks may not be of identical heights, the bottom and top
237 radius for each track is stored in a dictionary - self.track_radii,
238 keyed by track number
239 """
240 top_track = max(self.drawn_tracks)
241
242 trackunit_sum = 0
243 trackunits = {}
244 heightholder = 0
245 for track in range(1, top_track+1):
246 try:
247 trackheight = self._parent[track].height
248 except:
249 trackheight = 1
250 trackunit_sum += trackheight
251 trackunits[track] = (heightholder, heightholder+trackheight)
252 heightholder += trackheight
253 trackunit_height = 0.5*min(self.pagewidth, self.pageheight)/trackunit_sum
254
255
256 self.track_radii = {}
257 track_crop = trackunit_height*(1-self.track_size)/2.
258 for track in trackunits:
259 top = trackunits[track][1]*trackunit_height-track_crop
260 btm = trackunits[track][0]*trackunit_height+track_crop
261 ctr = btm+(top-btm)/2.
262 self.track_radii[track] = (btm, ctr, top)
263
265 """ draw(self)
266
267 Draw a circular diagram of the stored data
268 """
269
270 self.drawing = Drawing(self.pagesize[0], self.pagesize[1])
271
272 feature_elements = []
273 feature_labels = []
274 greytrack_bgs = []
275 greytrack_labels = []
276 scale_axes = []
277 scale_labels = []
278
279
280 self.drawn_tracks = self._parent.get_drawn_levels()
281 self.set_track_heights()
282
283
284
285 for track_level in self._parent.get_drawn_levels():
286 self.current_track_level = track_level
287 track = self._parent[track_level]
288 gbgs, glabels = self.draw_greytrack(track)
289 greytrack_bgs.append(gbgs)
290 greytrack_labels.append(glabels)
291 features, flabels = self.draw_track(track)
292 feature_elements.append(features)
293 feature_labels.append(flabels)
294 if track.scale:
295 axes, slabels = self.draw_scale(track)
296 scale_axes.append(axes)
297 scale_labels.append(slabels)
298
299
300
301
302
303
304
305
306 element_groups = [greytrack_bgs, feature_elements,
307 scale_axes, scale_labels,
308 feature_labels, greytrack_labels
309 ]
310 for element_group in element_groups:
311 for element_list in element_group:
312 [self.drawing.add(element) for element in element_list]
313
314 if self.tracklines:
315 self.draw_test_tracks()
316
317
319 """ draw_track(self, track) -> ([element, element,...], [element, element,...])
320
321 o track Track object
322
323 Return tuple of (list of track elements, list of track labels)
324 """
325 track_elements = []
326 track_labels = []
327
328
329 set_methods = {FeatureSet: self.draw_feature_set,
330 GraphSet: self.draw_graph_set
331 }
332
333 for set in track.get_sets():
334 elements, labels = set_methods[set.__class__](set)
335 track_elements += elements
336 track_labels += labels
337 return track_elements, track_labels
338
339
360
361
363 """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...])
364
365 o feature Feature containing location info
366
367 Returns tuple of (list of elements describing single feature, list
368 of labels for those elements)
369 """
370 feature_elements = []
371 label_elements = []
372
373 if feature.hide:
374 return feature_elements, label_elements
375
376
377 for locstart, locend in feature.locations:
378
379 feature_sigil, label = self.get_feature_sigil(feature, locstart, locend)
380 feature_elements.append(feature_sigil)
381 if label is not None:
382 label_elements.append(label)
383
384 return feature_elements, label_elements
385
386
388 """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element)
389
390 o feature Feature object
391
392 o locstart The start position of the feature
393
394 o locend The end position of the feature
395
396 Returns a drawable indicator of the feature, and any required label
397 for it
398 """
399
400 btm, ctr, top = self.track_radii[self.current_track_level]
401 startangle, startcos, startsin = self.canvas_angle(locstart)
402 endangle, endcos, endsin = self.canvas_angle(locend)
403 midangle, midcos, midsin = self.canvas_angle(locend+locstart/2)
404
405
406
407
408 draw_methods = {'BOX': self._draw_arc,
409 'ARROW': self._draw_arc_arrow,
410 }
411
412
413 method = draw_methods[feature.sigil]
414 if feature.color == colors.white:
415 border = colors.black
416 else:
417 border = feature.color
418 if feature.strand == 0:
419 sigil = method(btm, top, startangle, endangle, feature.color,
420 border)
421 if feature.strand == 1:
422 sigil = method(ctr, top, startangle, endangle, feature.color,
423 border, orientation='right')
424 if feature.strand == -1:
425 sigil = method(btm, ctr, startangle, endangle, feature.color,
426 border, orientation='left')
427 if feature.label:
428 label = String(0, 0, feature.name.strip(),
429 fontName=feature.label_font,
430 fontSize=feature.label_size,
431 fillColor=feature.label_color)
432 labelgroup = Group(label)
433 label_angle = startangle + 0.5 * pi
434 sinval, cosval = startsin, startcos
435 if feature.strand == 1:
436 if startangle < pi:
437 sinval, cosval = endsin, endcos
438 label_angle = endangle - 0.5 * pi
439 labelgroup.contents[0].textAnchor = 'end'
440 pos = self.xcenter+top*sinval
441 coslabel = cos(label_angle)
442 sinlabel = sin(label_angle)
443 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
444 pos, self.ycenter+top*cosval)
445 elif feature.strand == -1:
446 if startangle > pi:
447 labelgroup.contents[0].textAnchor='end'
448 else:
449 sinval, cosval = endsin, endcos
450 label_angle += pi
451 pos = self.xcenter+btm*sinval
452 coslabel = cos(label_angle)
453 sinlabel = sin(label_angle)
454
455
456 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
457 pos, self.ycenter+btm*cosval)
458 else:
459 if startangle > pi:
460 labelgroup.contents[0].textAnchor='end'
461 sinval, cosval = endsin, endcos
462 else:
463 label_angle += pi
464 pos = self.xcenter+btm*sinval
465 coslabel = cos(label_angle)
466 sinlabel = sin(label_angle)
467 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
468 pos, self.ycenter+btm*cosval)
469 else:
470 labelgroup = None
471
472
473
474 return sigil, labelgroup
475
476
477
479 """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...])
480
481 o set GraphSet object
482
483 Returns tuple (list of graph elements, list of graph labels)
484 """
485
486 elements = []
487
488
489 style_methods = {'line': self.draw_line_graph,
490 'heat': self.draw_heat_graph,
491 'bar': self.draw_bar_graph
492 }
493
494 for graph in set.get_graphs():
495
496 elements += style_methods[graph.style](graph)
497
498 return elements, []
499
500
502 """ draw_line_graph(self, graph, center) -> [element, element,...]
503
504 o graph GraphData object
505
506 Returns a line graph as a list of drawable elements
507 """
508
509 line_elements = []
510
511
512 data_quartiles = graph.quartiles()
513 minval, maxval = data_quartiles[0],data_quartiles[4]
514 btm, ctr, top = self.track_radii[self.current_track_level]
515 trackheight = 0.5*(top-btm)
516 datarange = maxval - minval
517 if datarange == 0:
518 datarange = trackheight
519 data = graph[self.start:self.end]
520
521
522
523 if graph.center is None:
524 midval = (maxval + minval)/2.
525 else:
526 midval = graph.center
527
528
529
530 resolution = max((midval-minval), (maxval-midval))
531
532
533 pos, val = data[0]
534 lastangle, lastcos, lastsin = self.canvas_angle(pos)
535
536 posheight = trackheight*(val-midval)/resolution + ctr
537 lastx = self.xcenter+posheight*lastsin
538 lasty = self.ycenter+posheight*lastcos
539 for pos, val in data:
540 posangle, poscos, possin = self.canvas_angle(pos)
541 posheight = trackheight*(val-midval)/resolution + ctr
542 x = self.xcenter+posheight*possin
543 y = self.ycenter+posheight*poscos
544 line_elements.append(Line(lastx, lasty, x, y,
545 strokeColor = graph.poscolor,
546 strokeWidth = graph.linewidth))
547 lastx, lasty, = x, y
548 return line_elements
549
550
552 """ draw_bar_graph(self, graph) -> [element, element,...]
553
554 o graph Graph object
555
556 Returns a list of drawable elements for a bar graph of the passed
557 Graph object
558 """
559
560
561
562
563
564 bar_elements = []
565
566
567 data_quartiles = graph.quartiles()
568 minval, maxval = data_quartiles[0],data_quartiles[4]
569 btm, ctr, top = self.track_radii[self.current_track_level]
570 trackheight = 0.5*(top-btm)
571 datarange = maxval - minval
572 if datarange == 0:
573 datarange = trackheight
574 data = graph[self.start:self.end]
575
576
577 if graph.center is None:
578 midval = (maxval + minval)/2.
579 else:
580 midval = graph.center
581
582
583
584
585 newdata = intermediate_points(self.start, self.end,
586 graph[self.start:self.end])
587
588
589
590
591 resolution = max((midval-minval), (maxval-midval))
592 if resolution == 0:
593 resolution = trackheight
594
595
596 for pos0, pos1, val in newdata:
597 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
598 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
599
600 barval = trackheight*(val-midval)/resolution
601 if barval >=0:
602 barcolor = graph.poscolor
603 else:
604 barcolor = graph.negcolor
605
606
607 bar_elements.append(self._draw_arc(ctr, ctr+barval, pos0angle,
608 pos1angle, barcolor))
609 return bar_elements
610
611
612
613
615 """ draw_heat_graph(self, graph) -> [element, element,...]
616
617 o graph Graph object
618
619 Returns a list of drawable elements for the heat graph
620 """
621
622
623
624
625
626 heat_elements = []
627
628
629 data_quartiles = graph.quartiles()
630 minval, maxval = data_quartiles[0],data_quartiles[4]
631 midval = (maxval + minval)/2.
632 btm, ctr, top = self.track_radii[self.current_track_level]
633 trackheight = (top-btm)
634 newdata = intermediate_points(self.start, self.end,
635 graph[self.start:self.end])
636
637
638
639
640 for pos0, pos1, val in newdata:
641 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
642 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
643
644
645
646 heat = colors.linearlyInterpolatedColor(graph.poscolor,
647 graph.negcolor,
648 maxval, minval, val)
649
650
651 heat_elements.append(self._draw_arc(btm, top, pos0angle, pos1angle,
652 heat, border=heat))
653 return heat_elements
654
655
657 """ draw_scale(self, track) -> ([element, element,...], [element, element,...])
658
659 o track Track object
660
661 Returns a tuple of (list of elements in the scale, list of labels
662 in the scale)
663 """
664 scale_elements = []
665 scale_labels = []
666
667 if not track.scale:
668 return [], []
669
670
671 btm, ctr, top = self.track_radii[self.current_track_level]
672 trackheight = (top-ctr)
673
674
675 if self.sweep < 1 :
676
677 p = ArcPath(strokeColor=track.scale_color, fillColor=None)
678
679
680
681 p.addArc(self.xcenter, self.ycenter, ctr,
682 startangledegrees=90-360*self.sweep,
683 endangledegrees=90)
684 scale_elements.append(p)
685 del p
686 else :
687
688 scale_elements.append(Circle(self.xcenter, self.ycenter, ctr,
689 strokeColor=track.scale_color,
690 fillColor=None))
691
692 if track.scale_ticks:
693
694
695
696
697
698 ticklen = track.scale_largeticks * trackheight
699 tickiterval = int(track.scale_largetick_interval)
700
701
702
703
704 largeticks = [pos for pos \
705 in range(tickiterval * (self.start/tickiterval),
706 int(self.end),
707 tickiterval) \
708 if pos >= self.start]
709 for tickpos in largeticks:
710 tick, label = self.draw_tick(tickpos, ctr, ticklen,
711 track,
712 track.scale_largetick_labels)
713 scale_elements.append(tick)
714 if label is not None:
715 scale_labels.append(label)
716
717 ticklen = track.scale_smallticks * trackheight
718 tickiterval = int(track.scale_smalltick_interval)
719 smallticks = [pos for pos \
720 in range(tickiterval * (self.start/tickiterval),
721 int(self.end),
722 tickiterval) \
723 if pos >= self.start]
724 for tickpos in smallticks:
725 tick, label = self.draw_tick(tickpos, ctr, ticklen,
726 track,
727 track.scale_smalltick_labels)
728 scale_elements.append(tick)
729 if label is not None:
730 scale_labels.append(label)
731
732
733
734
735 if track.axis_labels:
736 for set in track.get_sets():
737 if set.__class__ is GraphSet:
738
739 for n in xrange(7):
740 angle = n * 1.0471975511965976
741 ticksin, tickcos = sin(angle), cos(angle)
742 x0, y0 = self.xcenter+btm*ticksin, self.ycenter+btm*tickcos
743 x1, y1 = self.xcenter+top*ticksin, self.ycenter+top*tickcos
744 scale_elements.append(Line(x0, y0, x1, y1,
745 strokeColor=track.scale_color))
746
747 graph_label_min = []
748 graph_label_max = []
749 graph_label_mid = []
750 for graph in set.get_graphs():
751 quartiles = graph.quartiles()
752 minval, maxval = quartiles[0], quartiles[4]
753 if graph.center is None:
754 midval = (maxval + minval)/2.
755 graph_label_min.append("%.3f" % minval)
756 graph_label_max.append("%.3f" % maxval)
757 graph_label_mid.append("%.3f" % midval)
758 else:
759 diff = max((graph.center-minval),
760 (maxval-graph.center))
761 minval = graph.center-diff
762 maxval = graph.center+diff
763 midval = graph.center
764 graph_label_mid.append("%.3f" % midval)
765 graph_label_min.append("%.3f" % minval)
766 graph_label_max.append("%.3f" % maxval)
767 xmid, ymid = (x0+x1)/2., (y0+y1)/2.
768 for limit, x, y, in [(graph_label_min, x0, y0),
769 (graph_label_max, x1, y1),
770 (graph_label_mid, xmid, ymid)]:
771 label = String(0, 0, ";".join(limit),
772 fontName=track.scale_font,
773 fontSize=track.scale_fontsize,
774 fillColor=track.scale_color)
775 label.textAnchor = 'middle'
776 labelgroup = Group(label)
777 labelgroup.transform = (tickcos, -ticksin,
778 ticksin, tickcos,
779 x, y)
780 scale_labels.append(labelgroup)
781
782 return scale_elements, scale_labels
783
784
785 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
786 """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element)
787
788 o tickpos Int, position of the tick on the sequence
789
790 o ctr Float, Y co-ord of the center of the track
791
792 o ticklen How long to draw the tick
793
794 o track Track, the track the tick is drawn on
795
796 o draw_label Boolean, write the tick label?
797
798 Returns a drawing element that is the tick on the scale
799 """
800
801 tickangle, tickcos, ticksin = self.canvas_angle(tickpos)
802 x0, y0 = self.xcenter+ctr*ticksin, self.ycenter+ctr*tickcos
803 x1, y1 = self.xcenter+(ctr+ticklen)*ticksin, self.ycenter+(ctr+ticklen)*tickcos
804
805
806
807
808
809 tick = Line(x0, y0, x1, y1, strokeColor=track.scale_color)
810 if draw_label:
811 if track.scale_format == 'SInt':
812 if tickpos >= 1000000:
813 tickstring = str(tickpos/1000000) + " Mbp"
814 elif tickpos >= 1000:
815 tickstring = str(tickpos/1000) + " Kbp"
816 else:
817 tickstring = str(tickpos)
818 else:
819 tickstring = str(tickpos)
820 label = String(0, 0, tickstring,
821 fontName=track.scale_font,
822 fontSize=track.scale_fontsize,
823 fillColor=track.scale_color)
824 if tickangle > pi:
825 label.textAnchor = 'end'
826
827
828
829
830 labelgroup = Group(label)
831 labelgroup.transform = (1,0,0,1, x1, y1)
832 else:
833 labelgroup = None
834 return tick, labelgroup
835
836
838 """ draw_test_tracks(self)
839
840 Draw blue ones indicating tracks to be drawn, with a green line
841 down the center.
842 """
843
844
845 for track in self.drawn_tracks:
846 btm, ctr, top = self.track_radii[track]
847 self.drawing.add(Circle(self.xcenter, self.ycenter, top,
848 strokeColor=colors.blue,
849 fillColor=None))
850 self.drawing.add(Circle(self.xcenter, self.ycenter, ctr,
851 strokeColor=colors.green,
852 fillColor=None))
853 self.drawing.add(Circle(self.xcenter, self.ycenter, btm,
854 strokeColor=colors.blue,
855 fillColor=None))
856
857
859 """ draw_greytrack(self)
860
861 o track Track object
862
863 Put in a grey background to the current track, if the track
864 specifies that we should
865 """
866 greytrack_bgs = []
867 greytrack_labels = []
868
869 if not track.greytrack:
870 return [], []
871
872
873 btm, ctr, top = self.track_radii[self.current_track_level]
874
875
876 if self.sweep < 1 :
877
878
879 bg = self._draw_arc(btm, top, 0, 2*pi*self.sweep,
880 colors.Color(0.98, 0.98, 0.98))
881 else :
882
883 bg = Circle(self.xcenter, self.ycenter, ctr,
884 strokeColor = colors.Color(0.98, 0.98, 0.98),
885 fillColor=None, strokeWidth=top-btm)
886 greytrack_bgs.append(bg)
887
888 if track.greytrack_labels:
889 labelstep = self.length/track.greytrack_labels
890 for pos in range(self.start, self.end, int(labelstep)):
891 label = String(0, 0, track.name,
892 fontName=track.greytrack_font,
893 fontSize=track.greytrack_fontsize,
894 fillColor=track.greytrack_fontcolor)
895 theta, costheta, sintheta = self.canvas_angle(pos)
896 x,y = self.xcenter+btm*sintheta, self.ycenter+btm*costheta
897 labelgroup = Group(label)
898 labelangle = self.sweep*2*pi*(pos-self.start)/self.length - pi/2
899 if theta > pi:
900 label.textAnchor = 'end'
901 labelangle += pi
902 cosA, sinA = cos(labelangle), sin(labelangle)
903 labelgroup.transform = (cosA, -sinA, sinA,
904 cosA, x, y)
905 if not self.length-x <= labelstep:
906 greytrack_labels.append(labelgroup)
907
908 return greytrack_bgs, greytrack_labels
909
910
916
917
918 - def _draw_arc(self, inner_radius, outer_radius, startangle, endangle,
919 color, border=None, colour=None, **kwargs):
920 """ draw_arc(self, inner_radius, outer_radius, startangle, endangle, color)
921 -> Group
922
923 o inner_radius Float distance of inside of arc from drawing center
924
925 o outer_radius Float distance of outside of arc from drawing center
926
927 o startangle Float angle subtended by start of arc at drawing center
928 (in radians)
929
930 o endangle Float angle subtended by end of arc at drawing center
931 (in radians)
932
933 o color colors.Color object for arc (overridden by backwards
934 compatible argument with UK spelling, colour).
935
936 Returns a closed path object describing an arced box corresponding to
937 the passed values. For very small angles, a simple four sided
938 polygon is used.
939 """
940
941 if colour is not None:
942 color = colour
943
944 if border is None:
945 border = color
946
947 if color is None:
948 color = colour
949 if color == colors.white and border is None:
950 strokecolor = colors.black
951 elif border is None:
952 strokecolor = color
953 elif border is not None:
954 strokecolor = border
955
956 angle = float(endangle - startangle)
957 if angle>.01:
958 p = ArcPath(strokeColor=strokecolor,
959 fillColor=color,
960 strokewidth=0)
961
962
963
964
965 p.addArc(self.xcenter, self.ycenter, inner_radius,
966 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
967 moveTo=True)
968 p.addArc(self.xcenter, self.ycenter, outer_radius,
969 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
970 reverse=True)
971 p.closePath()
972 return p
973 else :
974
975
976 startcos, startsin = cos(startangle), sin(startangle)
977 endcos, endsin = cos(endangle), sin(endangle)
978 x0,y0 = self.xcenter, self.ycenter
979 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
980 x2,y2 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
981 x3,y3 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
982 x4,y4 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
983 return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border)
984
985 - def _draw_arc_arrow(self, inner_radius, outer_radius, startangle, endangle,
986 color, border=None,
987 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right',
988 colour=None, **kwargs):
989 """Draw an arrow along an arc."""
990
991 if colour is not None:
992 color = colour
993
994 if border is None:
995 border = color
996
997 if color is None:
998 color = colour
999 if color == colors.white and border is None:
1000 strokecolor = colors.black
1001 elif border is None:
1002 strokecolor = color
1003 elif border is not None:
1004 strokecolor = border
1005
1006
1007
1008
1009
1010
1011 startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1012 if orientation <> "left" and orientation <> "right" :
1013 raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \
1014 % repr(orientation))
1015
1016 angle = float(endangle - startangle)
1017 middle_radius = 0.5*(inner_radius+outer_radius)
1018 boxheight = outer_radius - inner_radius
1019 shaft_height = boxheight*shaft_height_ratio
1020 shaft_inner_radius = middle_radius - 0.5*shaft_height
1021 shaft_outer_radius = middle_radius + 0.5*shaft_height
1022 headangle_delta = min(abs(asin(boxheight/middle_radius)*head_length_ratio), abs(angle))
1023 if angle < 0 :
1024 headangle_delta *= -1
1025 if orientation=="right" :
1026 headangle = endangle-headangle_delta
1027 else :
1028 headangle = startangle+headangle_delta
1029 if startangle <= endangle :
1030 headangle = max(min(headangle, endangle), startangle)
1031 else :
1032 headangle = max(min(headangle, startangle), endangle)
1033 assert startangle <= headangle <= endangle \
1034 or endangle <= headangle <= startangle
1035
1036
1037
1038 startcos, startsin = cos(startangle), sin(startangle)
1039 headcos, headsin = cos(headangle), sin(headangle)
1040 endcos, endsin = cos(endangle), sin(endangle)
1041 x0,y0 = self.xcenter, self.ycenter
1042 if abs(headangle_delta) >= abs(angle) :
1043
1044 if orientation=="right" :
1045 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
1046 x2,y2 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
1047 x3,y3 = (x0+middle_radius*endsin, y0+middle_radius*endcos)
1048 else :
1049 x1,y1 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
1050 x2,y2 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
1051 x3,y3 = (x0+middle_radius*startsin, y0+middle_radius*startcos)
1052 return draw_polygon([(x1,y1),(x2,y2),(x3,y3)], color, border)
1053 elif orientation=="right" :
1054 p = ArcPath(strokeColor=strokecolor,
1055 fillColor=color,
1056
1057 strokeLineJoin=1,
1058 strokewidth=0)
1059
1060
1061
1062
1063 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
1064 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1065 moveTo=True)
1066 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
1067 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1068 reverse=True)
1069 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
1070 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos)
1071 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1072 p.closePath()
1073 return p
1074 else :
1075 p = ArcPath(strokeColor=strokecolor,
1076 fillColor=color,
1077
1078 strokeLineJoin=1,
1079 strokewidth=0)
1080
1081
1082
1083
1084 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
1085 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1086 moveTo=True, reverse=True)
1087 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
1088 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1089 reverse=False)
1090 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
1091 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos)
1092 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1093 p.closePath()
1094 return p
1095