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
341 """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...])
342
343 o set FeatureSet object
344
345 Returns a tuple (list of elements describing features, list of
346 labels for elements)
347 """
348
349 feature_elements = []
350 label_elements = []
351
352
353 for feature in set.get_features():
354 if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end):
355 features, labels = self.draw_feature(feature)
356 feature_elements += features
357 label_elements += labels
358
359 return feature_elements, label_elements
360
361
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(float(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 kwargs['head_length_ratio'] = feature.arrowhead_length
415 kwargs['shaft_height_ratio'] = feature.arrowshaft_height
416
417
418
419 if hasattr(feature, "url") :
420 kwargs["hrefURL"] = feature.url
421 kwargs["hrefTitle"] = feature.name
422
423 if feature.color == colors.white:
424 border = colors.black
425 else:
426 border = feature.color
427
428 if feature.strand == 1:
429 sigil = method(ctr, top, startangle, endangle, feature.color,
430 border, orientation='right', **kwargs)
431 elif feature.strand == -1:
432 sigil = method(btm, ctr, startangle, endangle, feature.color,
433 border, orientation='left', **kwargs)
434 else:
435 sigil = method(btm, top, startangle, endangle, feature.color,
436 border, **kwargs)
437
438 if feature.label:
439 label = String(0, 0, feature.name.strip(),
440 fontName=feature.label_font,
441 fontSize=feature.label_size,
442 fillColor=feature.label_color)
443 labelgroup = Group(label)
444 label_angle = startangle + 0.5 * pi
445 sinval, cosval = startsin, startcos
446 if feature.strand != -1:
447
448 if startangle < pi:
449 sinval, cosval = endsin, endcos
450 label_angle = endangle - 0.5 * pi
451 labelgroup.contents[0].textAnchor = 'end'
452 pos = self.xcenter+top*sinval
453 coslabel = cos(label_angle)
454 sinlabel = sin(label_angle)
455 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
456 pos, self.ycenter+top*cosval)
457 else:
458
459 if startangle < pi:
460 sinval, cosval = endsin, endcos
461 label_angle = endangle - 0.5 * pi
462 else:
463 labelgroup.contents[0].textAnchor = 'end'
464 pos = self.xcenter+btm*sinval
465 coslabel = cos(label_angle)
466 sinlabel = sin(label_angle)
467 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
468 pos, self.ycenter+btm*cosval)
469
470 else:
471 labelgroup = None
472
473
474
475 return sigil, labelgroup
476
477
478
480 """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...])
481
482 o set GraphSet object
483
484 Returns tuple (list of graph elements, list of graph labels)
485 """
486
487 elements = []
488
489
490 style_methods = {'line': self.draw_line_graph,
491 'heat': self.draw_heat_graph,
492 'bar': self.draw_bar_graph
493 }
494
495 for graph in set.get_graphs():
496
497 elements += style_methods[graph.style](graph)
498
499 return elements, []
500
501
503 """ draw_line_graph(self, graph, center) -> [element, element,...]
504
505 o graph GraphData object
506
507 Returns a line graph as a list of drawable elements
508 """
509
510 line_elements = []
511
512
513 data_quartiles = graph.quartiles()
514 minval, maxval = data_quartiles[0],data_quartiles[4]
515 btm, ctr, top = self.track_radii[self.current_track_level]
516 trackheight = 0.5*(top-btm)
517 datarange = maxval - minval
518 if datarange == 0:
519 datarange = trackheight
520 data = graph[self.start:self.end]
521
522
523
524 if graph.center is None:
525 midval = (maxval + minval)/2.
526 else:
527 midval = graph.center
528
529
530
531 resolution = max((midval-minval), (maxval-midval))
532
533
534 pos, val = data[0]
535 lastangle, lastcos, lastsin = self.canvas_angle(pos)
536
537 posheight = trackheight*(val-midval)/resolution + ctr
538 lastx = self.xcenter+posheight*lastsin
539 lasty = self.ycenter+posheight*lastcos
540 for pos, val in data:
541 posangle, poscos, possin = self.canvas_angle(pos)
542 posheight = trackheight*(val-midval)/resolution + ctr
543 x = self.xcenter+posheight*possin
544 y = self.ycenter+posheight*poscos
545 line_elements.append(Line(lastx, lasty, x, y,
546 strokeColor = graph.poscolor,
547 strokeWidth = graph.linewidth))
548 lastx, lasty, = x, y
549 return line_elements
550
551
553 """ draw_bar_graph(self, graph) -> [element, element,...]
554
555 o graph Graph object
556
557 Returns a list of drawable elements for a bar graph of the passed
558 Graph object
559 """
560
561
562
563
564
565 bar_elements = []
566
567
568 data_quartiles = graph.quartiles()
569 minval, maxval = data_quartiles[0],data_quartiles[4]
570 btm, ctr, top = self.track_radii[self.current_track_level]
571 trackheight = 0.5*(top-btm)
572 datarange = maxval - minval
573 if datarange == 0:
574 datarange = trackheight
575 data = graph[self.start:self.end]
576
577
578 if graph.center is None:
579 midval = (maxval + minval)/2.
580 else:
581 midval = graph.center
582
583
584
585
586 newdata = intermediate_points(self.start, self.end,
587 graph[self.start:self.end])
588
589
590
591
592 resolution = max((midval-minval), (maxval-midval))
593 if resolution == 0:
594 resolution = trackheight
595
596
597 for pos0, pos1, val in newdata:
598 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
599 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
600
601 barval = trackheight*(val-midval)/resolution
602 if barval >=0:
603 barcolor = graph.poscolor
604 else:
605 barcolor = graph.negcolor
606
607
608 bar_elements.append(self._draw_arc(ctr, ctr+barval, pos0angle,
609 pos1angle, barcolor))
610 return bar_elements
611
612
613
614
616 """ draw_heat_graph(self, graph) -> [element, element,...]
617
618 o graph Graph object
619
620 Returns a list of drawable elements for the heat graph
621 """
622
623
624
625
626
627 heat_elements = []
628
629
630 data_quartiles = graph.quartiles()
631 minval, maxval = data_quartiles[0],data_quartiles[4]
632 midval = (maxval + minval)/2.
633 btm, ctr, top = self.track_radii[self.current_track_level]
634 trackheight = (top-btm)
635 newdata = intermediate_points(self.start, self.end,
636 graph[self.start:self.end])
637
638
639
640
641 for pos0, pos1, val in newdata:
642 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
643 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
644
645
646
647 heat = colors.linearlyInterpolatedColor(graph.poscolor,
648 graph.negcolor,
649 maxval, minval, val)
650
651
652 heat_elements.append(self._draw_arc(btm, top, pos0angle, pos1angle,
653 heat, border=heat))
654 return heat_elements
655
656
658 """ draw_scale(self, track) -> ([element, element,...], [element, element,...])
659
660 o track Track object
661
662 Returns a tuple of (list of elements in the scale, list of labels
663 in the scale)
664 """
665 scale_elements = []
666 scale_labels = []
667
668 if not track.scale:
669 return [], []
670
671
672 btm, ctr, top = self.track_radii[self.current_track_level]
673 trackheight = (top-ctr)
674
675
676 if self.sweep < 1:
677
678 p = ArcPath(strokeColor=track.scale_color, fillColor=None)
679
680
681
682 p.addArc(self.xcenter, self.ycenter, ctr,
683 startangledegrees=90-360*self.sweep,
684 endangledegrees=90)
685 scale_elements.append(p)
686 del p
687 else:
688
689 scale_elements.append(Circle(self.xcenter, self.ycenter, ctr,
690 strokeColor=track.scale_color,
691 fillColor=None))
692
693 if track.scale_ticks:
694
695
696
697
698
699 ticklen = track.scale_largeticks * trackheight
700 tickiterval = int(track.scale_largetick_interval)
701
702
703
704
705 largeticks = [pos for pos \
706 in range(tickiterval * (self.start//tickiterval),
707 int(self.end),
708 tickiterval) \
709 if pos >= self.start]
710 for tickpos in largeticks:
711 tick, label = self.draw_tick(tickpos, ctr, ticklen,
712 track,
713 track.scale_largetick_labels)
714 scale_elements.append(tick)
715 if label is not None:
716 scale_labels.append(label)
717
718 ticklen = track.scale_smallticks * trackheight
719 tickiterval = int(track.scale_smalltick_interval)
720 smallticks = [pos for pos \
721 in range(tickiterval * (self.start//tickiterval),
722 int(self.end),
723 tickiterval) \
724 if pos >= self.start]
725 for tickpos in smallticks:
726 tick, label = self.draw_tick(tickpos, ctr, ticklen,
727 track,
728 track.scale_smalltick_labels)
729 scale_elements.append(tick)
730 if label is not None:
731 scale_labels.append(label)
732
733
734
735
736 if track.axis_labels:
737 for set in track.get_sets():
738 if set.__class__ is GraphSet:
739
740 for n in xrange(7):
741 angle = n * 1.0471975511965976
742 ticksin, tickcos = sin(angle), cos(angle)
743 x0, y0 = self.xcenter+btm*ticksin, self.ycenter+btm*tickcos
744 x1, y1 = self.xcenter+top*ticksin, self.ycenter+top*tickcos
745 scale_elements.append(Line(x0, y0, x1, y1,
746 strokeColor=track.scale_color))
747
748 graph_label_min = []
749 graph_label_max = []
750 graph_label_mid = []
751 for graph in set.get_graphs():
752 quartiles = graph.quartiles()
753 minval, maxval = quartiles[0], quartiles[4]
754 if graph.center is None:
755 midval = (maxval + minval)/2.
756 graph_label_min.append("%.3f" % minval)
757 graph_label_max.append("%.3f" % maxval)
758 graph_label_mid.append("%.3f" % midval)
759 else:
760 diff = max((graph.center-minval),
761 (maxval-graph.center))
762 minval = graph.center-diff
763 maxval = graph.center+diff
764 midval = graph.center
765 graph_label_mid.append("%.3f" % midval)
766 graph_label_min.append("%.3f" % minval)
767 graph_label_max.append("%.3f" % maxval)
768 xmid, ymid = (x0+x1)/2., (y0+y1)/2.
769 for limit, x, y, in [(graph_label_min, x0, y0),
770 (graph_label_max, x1, y1),
771 (graph_label_mid, xmid, ymid)]:
772 label = String(0, 0, ";".join(limit),
773 fontName=track.scale_font,
774 fontSize=track.scale_fontsize,
775 fillColor=track.scale_color)
776 label.textAnchor = 'middle'
777 labelgroup = Group(label)
778 labelgroup.transform = (tickcos, -ticksin,
779 ticksin, tickcos,
780 x, y)
781 scale_labels.append(labelgroup)
782
783 return scale_elements, scale_labels
784
785
786 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
787 """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element)
788
789 o tickpos Int, position of the tick on the sequence
790
791 o ctr Float, Y co-ord of the center of the track
792
793 o ticklen How long to draw the tick
794
795 o track Track, the track the tick is drawn on
796
797 o draw_label Boolean, write the tick label?
798
799 Returns a drawing element that is the tick on the scale
800 """
801
802 tickangle, tickcos, ticksin = self.canvas_angle(tickpos)
803 x0, y0 = self.xcenter+ctr*ticksin, self.ycenter+ctr*tickcos
804 x1, y1 = self.xcenter+(ctr+ticklen)*ticksin, self.ycenter+(ctr+ticklen)*tickcos
805
806
807
808
809
810 tick = Line(x0, y0, x1, y1, strokeColor=track.scale_color)
811 if draw_label:
812 if track.scale_format == 'SInt':
813 if tickpos >= 1000000:
814 tickstring = str(tickpos//1000000) + " Mbp"
815 elif tickpos >= 1000:
816 tickstring = str(tickpos//1000) + " Kbp"
817 else:
818 tickstring = str(tickpos)
819 else:
820 tickstring = str(tickpos)
821 label = String(0, 0, tickstring,
822 fontName=track.scale_font,
823 fontSize=track.scale_fontsize,
824 fillColor=track.scale_color)
825 if tickangle > pi:
826 label.textAnchor = 'end'
827
828
829
830
831 labelgroup = Group(label)
832 labelgroup.transform = (1,0,0,1, x1, y1)
833 else:
834 labelgroup = None
835 return tick, labelgroup
836
837
839 """ draw_test_tracks(self)
840
841 Draw blue ones indicating tracks to be drawn, with a green line
842 down the center.
843 """
844
845
846 for track in self.drawn_tracks:
847 btm, ctr, top = self.track_radii[track]
848 self.drawing.add(Circle(self.xcenter, self.ycenter, top,
849 strokeColor=colors.blue,
850 fillColor=None))
851 self.drawing.add(Circle(self.xcenter, self.ycenter, ctr,
852 strokeColor=colors.green,
853 fillColor=None))
854 self.drawing.add(Circle(self.xcenter, self.ycenter, btm,
855 strokeColor=colors.blue,
856 fillColor=None))
857
858
860 """ draw_greytrack(self)
861
862 o track Track object
863
864 Put in a grey background to the current track, if the track
865 specifies that we should
866 """
867 greytrack_bgs = []
868 greytrack_labels = []
869
870 if not track.greytrack:
871 return [], []
872
873
874 btm, ctr, top = self.track_radii[self.current_track_level]
875
876
877 if self.sweep < 1:
878
879
880 bg = self._draw_arc(btm, top, 0, 2*pi*self.sweep,
881 colors.Color(0.96, 0.96, 0.96))
882 else:
883
884 bg = Circle(self.xcenter, self.ycenter, ctr,
885 strokeColor = colors.Color(0.96, 0.96, 0.96),
886 fillColor=None, strokeWidth=top-btm)
887 greytrack_bgs.append(bg)
888
889 if track.greytrack_labels:
890 labelstep = self.length//track.greytrack_labels
891 for pos in range(self.start, self.end, labelstep):
892 label = String(0, 0, track.name,
893 fontName=track.greytrack_font,
894 fontSize=track.greytrack_fontsize,
895 fillColor=track.greytrack_fontcolor)
896 theta, costheta, sintheta = self.canvas_angle(pos)
897 x,y = self.xcenter+btm*sintheta, self.ycenter+btm*costheta
898 labelgroup = Group(label)
899 labelangle = self.sweep*2*pi*(pos-self.start)/self.length - pi/2
900 if theta > pi:
901 label.textAnchor = 'end'
902 labelangle += pi
903 cosA, sinA = cos(labelangle), sin(labelangle)
904 labelgroup.transform = (cosA, -sinA, sinA,
905 cosA, x, y)
906 if not self.length-x <= labelstep:
907 greytrack_labels.append(labelgroup)
908
909 return greytrack_bgs, greytrack_labels
910
911
917
918
919 - def _draw_arc(self, inner_radius, outer_radius, startangle, endangle,
920 color, border=None, colour=None, **kwargs):
921 """ draw_arc(self, inner_radius, outer_radius, startangle, endangle, color)
922 -> Group
923
924 o inner_radius Float distance of inside of arc from drawing center
925
926 o outer_radius Float distance of outside of arc from drawing center
927
928 o startangle Float angle subtended by start of arc at drawing center
929 (in radians)
930
931 o endangle Float angle subtended by end of arc at drawing center
932 (in radians)
933
934 o color colors.Color object for arc (overridden by backwards
935 compatible argument with UK spelling, colour).
936
937 Returns a closed path object describing an arced box corresponding to
938 the passed values. For very small angles, a simple four sided
939 polygon is used.
940 """
941
942 if colour is not None:
943 color = colour
944
945 if border is None:
946 border = color
947
948 if color is None:
949 color = colour
950 if color == colors.white and border is None:
951 strokecolor = colors.black
952 elif border is None:
953 strokecolor = color
954 elif border is not None:
955 strokecolor = border
956
957 if abs(float(endangle - startangle))>.01:
958
959 p = ArcPath(strokeColor=strokecolor,
960 fillColor=color,
961 strokewidth=0)
962
963
964
965
966 p.addArc(self.xcenter, self.ycenter, inner_radius,
967 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
968 moveTo=True)
969 p.addArc(self.xcenter, self.ycenter, outer_radius,
970 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
971 reverse=True)
972 p.closePath()
973 return p
974 else:
975
976
977 startcos, startsin = cos(startangle), sin(startangle)
978 endcos, endsin = cos(endangle), sin(endangle)
979 x0,y0 = self.xcenter, self.ycenter
980 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
981 x2,y2 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
982 x3,y3 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
983 x4,y4 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
984 return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border)
985
986 - def _draw_arc_arrow(self, inner_radius, outer_radius, startangle, endangle,
987 color, border=None,
988 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right',
989 colour=None, **kwargs):
990 """Draw an arrow along an arc."""
991
992 if colour is not None:
993 color = colour
994
995 if border is None:
996 border = color
997
998 if color is None:
999 color = colour
1000 if color == colors.white and border is None:
1001 strokecolor = colors.black
1002 elif border is None:
1003 strokecolor = color
1004 elif border is not None:
1005 strokecolor = border
1006
1007
1008
1009
1010
1011
1012 startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1013 if orientation != "left" and orientation != "right":
1014 raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \
1015 % repr(orientation))
1016
1017 angle = float(endangle - startangle)
1018 middle_radius = 0.5*(inner_radius+outer_radius)
1019 boxheight = outer_radius - inner_radius
1020 shaft_height = boxheight*shaft_height_ratio
1021 shaft_inner_radius = middle_radius - 0.5*shaft_height
1022 shaft_outer_radius = middle_radius + 0.5*shaft_height
1023 headangle_delta = max(0.0,min(abs(boxheight)*head_length_ratio/middle_radius, abs(angle)))
1024 if angle < 0:
1025 headangle_delta *= -1
1026 if orientation=="right":
1027 headangle = endangle-headangle_delta
1028 else:
1029 headangle = startangle+headangle_delta
1030 if startangle <= endangle:
1031 headangle = max(min(headangle, endangle), startangle)
1032 else:
1033 headangle = max(min(headangle, startangle), endangle)
1034 assert startangle <= headangle <= endangle \
1035 or endangle <= headangle <= startangle, \
1036 (startangle, headangle, endangle, angle)
1037
1038
1039
1040 startcos, startsin = cos(startangle), sin(startangle)
1041 headcos, headsin = cos(headangle), sin(headangle)
1042 endcos, endsin = cos(endangle), sin(endangle)
1043 x0,y0 = self.xcenter, self.ycenter
1044 if 0.5 >= abs(angle) and abs(headangle_delta) >= abs(angle):
1045
1046
1047 if orientation=="right":
1048 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
1049 x2,y2 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
1050 x3,y3 = (x0+middle_radius*endsin, y0+middle_radius*endcos)
1051 else:
1052 x1,y1 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
1053 x2,y2 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
1054 x3,y3 = (x0+middle_radius*startsin, y0+middle_radius*startcos)
1055
1056
1057 return Polygon([x1,y1,x2,y2,x3,y3],
1058 strokeColor=border or color,
1059 fillColor=color,
1060 strokeLineJoin=1,
1061 strokewidth=0)
1062 elif orientation=="right":
1063 p = ArcPath(strokeColor=strokecolor,
1064 fillColor=color,
1065
1066 strokeLineJoin=1,
1067 strokewidth=0,
1068 **kwargs)
1069
1070
1071
1072
1073 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
1074 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1075 moveTo=True)
1076 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
1077 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1078 reverse=True)
1079 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
1080 if abs(angle) < 0.5:
1081 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos)
1082 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1083 else:
1084 dx = min(0.1, abs(angle)/50.0)
1085 x = dx
1086 while x < 1:
1087 r = outer_radius - x*(outer_radius-middle_radius)
1088 a = headangle + x*(endangle-headangle)
1089 p.lineTo(x0+r*sin(a), y0+r*cos(a))
1090 x += dx
1091 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos)
1092 x = dx
1093 while x < 1:
1094 r = middle_radius - x*(middle_radius-inner_radius)
1095 a = headangle + (1-x)*(endangle-headangle)
1096 p.lineTo(x0+r*sin(a), y0+r*cos(a))
1097 x += dx
1098 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1099 p.closePath()
1100 return p
1101 else:
1102 p = ArcPath(strokeColor=strokecolor,
1103 fillColor=color,
1104
1105 strokeLineJoin=1,
1106 strokewidth=0,
1107 **kwargs)
1108
1109
1110
1111
1112 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
1113 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1114 moveTo=True, reverse=True)
1115 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
1116 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1117 reverse=False)
1118 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
1119
1120
1121 if abs(angle) < 0.5:
1122 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos)
1123 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1124 else:
1125 dx = min(0.1, abs(angle)/50.0)
1126 x = dx
1127 while x < 1:
1128 r = outer_radius - x*(outer_radius-middle_radius)
1129 a = headangle + x*(startangle-headangle)
1130 p.lineTo(x0+r*sin(a), y0+r*cos(a))
1131 x += dx
1132 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos)
1133 x = dx
1134 while x < 1:
1135 r = middle_radius - x*(middle_radius-inner_radius)
1136 a = headangle + (1-x)*(startangle-headangle)
1137 p.lineTo(x0+r*sin(a), y0+r*cos(a))
1138 x += dx
1139 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1140 p.closePath()
1141 return p
1142