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