1
2
3
4
5
6
7
8
9
10
11
12 """CircularDrawer module for GenomeDiagram."""
13
14
15 from reportlab.graphics.shapes import *
16 from reportlab.lib import colors
17 from reportlab.pdfbase import _fontdata
18 from reportlab.graphics.shapes import ArcPath
19
20
21 from _AbstractDrawer import AbstractDrawer, draw_polygon, intermediate_points
22 from _AbstractDrawer import _stroke_and_fill_colors
23 from _FeatureSet import FeatureSet
24 from _GraphSet import GraphSet
25
26 from math import ceil, pi, cos, sin, asin
27
28
30 """Object for drawing circular diagrams.
31
32 o __init__(self, ...) Called on instantiation
33
34 o set_page_size(self, pagesize, orientation) Set the page size to the
35 passed size and orientation
36
37 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the
38 page
39
40 o set_bounds(self, start, end) Set the bounds for the elements to be
41 drawn
42
43 o is_in_bounds(self, value) Returns a boolean for whether the position
44 is actually to be drawn
45
46 o __len__(self) Returns the length of sequence that will be drawn
47
48
49 o draw(self) Place the drawing elements on the diagram
50
51 o init_fragments(self) Calculate information
52 about sequence fragment locations on the drawing
53
54 o set_track_heights(self) Calculate information about the offset of
55 each track from the fragment base
56
57 o draw_test_tracks(self) Add lines demarcating each track to the
58 drawing
59
60 o draw_track(self, track) Return the contents of the passed track as
61 drawing elements
62
63 o draw_scale(self, track) Return a scale for the passed track as
64 drawing elements
65
66 o draw_greytrack(self, track) Return a grey background and superposed
67 label for the passed track as drawing
68 elements
69
70 o draw_feature_set(self, set) Return the features in the passed set as
71 drawing elements
72
73 o draw_feature(self, feature) Return a single feature as drawing
74 elements
75
76 o get_feature_sigil(self, feature, x0, x1, fragment) Return a single
77 feature as its sigil in drawing elements
78
79 o draw_graph_set(self, set) Return the data in a set of graphs as
80 drawing elements
81
82 o draw_line_graph(self, graph) Return the data in a graph as a line
83 graph in drawing elements
84
85 o draw_heat_graph(self, graph) Return the data in a graph as a heat
86 graph in drawing elements
87
88 o draw_bar_graph(self, graph) Return the data in a graph as a bar
89 graph in drawing elements
90
91 o canvas_angle(self, base) Return the angle, and cos and sin of
92 that angle, subtended by the passed
93 base position at the diagram center
94
95 o draw_arc(self, inner_radius, outer_radius, startangle, endangle,
96 color) Return a drawable element describing an arc
97
98 Attributes:
99
100 o tracklines Boolean for whether to draw lines dilineating tracks
101
102 o pagesize Tuple describing the size of the page in pixels
103
104 o x0 Float X co-ord for leftmost point of drawable area
105
106 o xlim Float X co-ord for rightmost point of drawable area
107
108 o y0 Float Y co-ord for lowest point of drawable area
109
110 o ylim Float Y co-ord for topmost point of drawable area
111
112 o pagewidth Float pixel width of drawable area
113
114 o pageheight Float pixel height of drawable area
115
116 o xcenter Float X co-ord of center of drawable area
117
118 o ycenter Float Y co-ord of center of drawable area
119
120 o start Int, base to start drawing from
121
122 o end Int, base to stop drawing at
123
124 o length Size of sequence to be drawn
125
126 o track_size Float (0->1) the proportion of the track height to
127 draw in
128
129 o drawing Drawing canvas
130
131 o drawn_tracks List of ints denoting which tracks are to be drawn
132
133 o current_track_level Int denoting which track is currently being
134 drawn
135
136 o track_offsets Dictionary of number of pixels that each track top,
137 center and bottom is offset from the base of a
138 fragment, keyed by track
139
140 o sweep Float (0->1) the proportion of the circle circumference to
141 use for the diagram
142
143 o cross_track_links List of tuples each with four entries (track A,
144 feature A, track B, feature B) to be linked.
145 """
146 - def __init__(self, parent=None, pagesize='A3', orientation='landscape',
147 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
148 start=None, end=None, tracklines=0, track_size=0.75,
149 circular=1, circle_core=0.0, cross_track_links=None):
150 """Create CircularDrawer object.
151
152 o parent Diagram object containing the data that the drawer
153 draws
154
155 o pagesize String describing the ISO size of the image, or a tuple
156 of pixels
157
158 o orientation String describing the required orientation of the
159 final drawing ('landscape' or 'portrait')
160
161 o x Float (0->1) describing the relative size of the X
162 margins to the page
163
164 o y Float (0->1) describing the relative size of the Y
165 margins to the page
166
167 o xl Float (0->1) describing the relative size of the left X
168 margin to the page (overrides x)
169
170 o xl Float (0->1) describing the relative size of the left X
171 margin to the page (overrides x)
172
173 o xr Float (0->1) describing the relative size of the right X
174 margin to the page (overrides x)
175
176 o yt Float (0->1) describing the relative size of the top Y
177 margin to the page (overrides y)
178
179 o yb Float (0->1) describing the relative size of the lower Y
180 margin to the page (overrides y)
181
182 o start Int, the position to begin drawing the diagram at
183
184 o end Int, the position to stop drawing the diagram at
185
186 o tracklines Boolean flag to show (or not) lines delineating tracks
187 on the diagram
188
189 o track_size The proportion of the available track height that
190 should be taken up in drawing
191
192 o circular Boolean flaw to show whether the passed sequence is
193 circular or not
194
195 o circle_core The proportion of the available radius to leave
196 empty at the center of a circular diagram (0 to 1).
197
198 o cross_track_links List of tuples each with four entries (track A,
199 feature A, track B, feature B) to be linked.
200 """
201
202 AbstractDrawer.__init__(self, parent, pagesize, orientation,
203 x, y, xl, xr, yt, yb, start, end,
204 tracklines, cross_track_links)
205
206
207 self.track_size = track_size
208 self.circle_core = circle_core
209 if not circular:
210 self.sweep = 0.9
211 else:
212 self.sweep = 1
213
215 """Initialise track heights.
216
217 Since tracks may not be of identical heights, the bottom and top
218 radius for each track is stored in a dictionary - self.track_radii,
219 keyed by track number
220 """
221 bot_track = min(min(self.drawn_tracks), 1)
222 top_track = max(self.drawn_tracks)
223
224 trackunit_sum = 0
225 trackunits = {}
226 heightholder = 0
227 for track in range(bot_track, top_track+1):
228 try:
229 trackheight = self._parent[track].height
230 except:
231 trackheight = 1
232 trackunit_sum += trackheight
233 trackunits[track] = (heightholder, heightholder+trackheight)
234 heightholder += trackheight
235
236 max_radius = 0.5*min(self.pagewidth, self.pageheight)
237 trackunit_height = max_radius * (1 - self.circle_core) / trackunit_sum
238 track_core = max_radius * self.circle_core
239
240
241 self.track_radii = {}
242 track_crop = trackunit_height*(1-self.track_size)/2.
243 for track in trackunits:
244 top = trackunits[track][1]*trackunit_height - track_crop + track_core
245 btm = trackunits[track][0]*trackunit_height + track_crop + track_core
246 ctr = btm+(top-btm)/2.
247 self.track_radii[track] = (btm, ctr, top)
248
250 """Draw a circular diagram of the stored data."""
251
252 self.drawing = Drawing(self.pagesize[0], self.pagesize[1])
253
254 feature_elements = []
255 feature_labels = []
256 greytrack_bgs = []
257 greytrack_labels = []
258 scale_axes = []
259 scale_labels = []
260
261
262 self.drawn_tracks = self._parent.get_drawn_levels()
263 self.set_track_heights()
264
265
266
267 for track_level in self._parent.get_drawn_levels():
268 self.current_track_level = track_level
269 track = self._parent[track_level]
270 gbgs, glabels = self.draw_greytrack(track)
271 greytrack_bgs.append(gbgs)
272 greytrack_labels.append(glabels)
273 features, flabels = self.draw_track(track)
274 feature_elements.append(features)
275 feature_labels.append(flabels)
276 if track.scale:
277 axes, slabels = self.draw_scale(track)
278 scale_axes.append(axes)
279 scale_labels.append(slabels)
280
281 feature_cross_links = []
282 for cross_link_obj in self.cross_track_links:
283 cross_link_elements = self.draw_cross_link(cross_link_obj)
284 if cross_link_elements:
285 feature_cross_links.append(cross_link_elements)
286
287
288
289
290
291
292
293
294
295 element_groups = [greytrack_bgs, feature_cross_links,
296 feature_elements,
297 scale_axes, scale_labels,
298 feature_labels, greytrack_labels
299 ]
300 for element_group in element_groups:
301 for element_list in element_group:
302 [self.drawing.add(element) for element in element_list]
303
304 if self.tracklines:
305
306 self.draw_test_tracks()
307
323
338
340 """Returns list of feature elements and list of labels for them."""
341 feature_elements = []
342 label_elements = []
343
344 if feature.hide:
345 return feature_elements, label_elements
346
347 start, end = self._current_track_start_end()
348
349 for locstart, locend in feature.locations:
350 if locend < start:
351 continue
352 locstart = max(locstart, start)
353 if end < locstart:
354 continue
355 locend = min(locend, end)
356
357 feature_sigil, label = self.get_feature_sigil(feature, locstart, locend)
358 feature_elements.append(feature_sigil)
359 if label is not None:
360 label_elements.append(label)
361
362 return feature_elements, label_elements
363
365 """Returns graphics for feature, and any required label for it.
366
367 o feature Feature object
368
369 o locstart The start position of the feature
370
371 o locend The end position of the feature
372 """
373
374 btm, ctr, top = self.track_radii[self.current_track_level]
375
376 startangle, startcos, startsin = self.canvas_angle(locstart)
377 endangle, endcos, endsin = self.canvas_angle(locend)
378 midangle, midcos, midsin = self.canvas_angle(float(locend+locstart)/2)
379
380
381
382
383 draw_methods = {'BOX': self._draw_sigil_box,
384 'OCTO': self._draw_sigil_cut_corner_box,
385 'JAGGY': self._draw_sigil_jaggy,
386 'ARROW': self._draw_sigil_arrow,
387 'BIGARROW': self._draw_sigil_big_arrow,
388 }
389
390
391 method = draw_methods[feature.sigil]
392 kwargs['head_length_ratio'] = feature.arrowhead_length
393 kwargs['shaft_height_ratio'] = feature.arrowshaft_height
394
395
396
397 if hasattr(feature, "url") :
398 kwargs["hrefURL"] = feature.url
399 kwargs["hrefTitle"] = feature.name
400
401 sigil = method(btm, ctr, top, startangle, endangle, feature.strand,
402 color=feature.color, border=feature.border, **kwargs)
403
404 if feature.label:
405 label = String(0, 0, feature.name.strip(),
406 fontName=feature.label_font,
407 fontSize=feature.label_size,
408 fillColor=feature.label_color)
409 labelgroup = Group(label)
410 label_angle = startangle + 0.5 * pi
411 sinval, cosval = startsin, startcos
412 if feature.strand != -1:
413
414 if startangle < pi:
415 sinval, cosval = endsin, endcos
416 label_angle = endangle - 0.5 * pi
417 labelgroup.contents[0].textAnchor = 'end'
418 pos = self.xcenter+top*sinval
419 coslabel = cos(label_angle)
420 sinlabel = sin(label_angle)
421 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
422 pos, self.ycenter+top*cosval)
423 else:
424
425 if startangle < pi:
426 sinval, cosval = endsin, endcos
427 label_angle = endangle - 0.5 * pi
428 else:
429 labelgroup.contents[0].textAnchor = 'end'
430 pos = self.xcenter+btm*sinval
431 coslabel = cos(label_angle)
432 sinlabel = sin(label_angle)
433 labelgroup.transform = (coslabel,-sinlabel,sinlabel,coslabel,
434 pos, self.ycenter+btm*cosval)
435
436 else:
437 labelgroup = None
438
439
440
441 return sigil, labelgroup
442
444 startA = cross_link.startA
445 startB = cross_link.startB
446 endA = cross_link.endA
447 endB = cross_link.endB
448
449 if not self.is_in_bounds(startA) \
450 and not self.is_in_bounds(endA):
451 return None
452 if not self.is_in_bounds(startB) \
453 and not self.is_in_bounds(endB):
454 return None
455
456 if startA < self.start:
457 startA = self.start
458 if startB < self.start:
459 startB = self.start
460 if self.end < endA:
461 endA = self.end
462 if self.end < endB:
463 endB = self.end
464
465 trackobjA = cross_link._trackA(self._parent.tracks.values())
466 trackobjB = cross_link._trackB(self._parent.tracks.values())
467 assert trackobjA is not None
468 assert trackobjB is not None
469 if trackobjA == trackobjB:
470 raise NotImplementedError()
471
472 if trackobjA.start is not None:
473 if endA < trackobjA.start:
474 return
475 startA = max(startA, trackobjA.start)
476 if trackobjA.end is not None:
477 if trackobjA.end < startA:
478 return
479 endA = min(endA, trackobjA.end)
480 if trackobjB.start is not None:
481 if endB < trackobjB.start:
482 return
483 startB = max(startB, trackobjB.start)
484 if trackobjB.end is not None:
485 if trackobjB.end < startB:
486 return
487 endB = min(endB, trackobjB.end)
488
489 for track_level in self._parent.get_drawn_levels():
490 track = self._parent[track_level]
491 if track == trackobjA:
492 trackA = track_level
493 if track == trackobjB:
494 trackB = track_level
495 if trackA == trackB:
496 raise NotImplementedError()
497
498 startangleA, startcosA, startsinA = self.canvas_angle(startA)
499 startangleB, startcosB, startsinB = self.canvas_angle(startB)
500 endangleA, endcosA, endsinA = self.canvas_angle(endA)
501 endangleB, endcosB, endsinB = self.canvas_angle(endB)
502
503 btmA, ctrA, topA = self.track_radii[trackA]
504 btmB, ctrB, topB = self.track_radii[trackB]
505
506 if ctrA < ctrB:
507 return [self._draw_arc_poly(topA, btmB,
508 startangleA, endangleA,
509 startangleB, endangleB,
510 cross_link.color, cross_link.border, cross_link.flip)]
511 else:
512 return [self._draw_arc_poly(btmA, topB,
513 startangleA, endangleA,
514 startangleB, endangleB,
515 cross_link.color, cross_link.border, cross_link.flip)]
516
535
537 """Returns line graph as list of drawable elements.
538
539 o graph GraphData object
540 """
541 line_elements = []
542
543
544 data_quartiles = graph.quartiles()
545 minval, maxval = data_quartiles[0],data_quartiles[4]
546 btm, ctr, top = self.track_radii[self.current_track_level]
547 trackheight = 0.5*(top-btm)
548 datarange = maxval - minval
549 if datarange == 0:
550 datarange = trackheight
551
552 start, end = self._current_track_start_end()
553 data = graph[start:end]
554
555 if not data:
556 return []
557
558
559
560 if graph.center is None:
561 midval = (maxval + minval)/2.
562 else:
563 midval = graph.center
564
565
566
567 resolution = max((midval-minval), (maxval-midval))
568
569
570 pos, val = data[0]
571 lastangle, lastcos, lastsin = self.canvas_angle(pos)
572
573 posheight = trackheight*(val-midval)/resolution + ctr
574 lastx = self.xcenter+posheight*lastsin
575 lasty = self.ycenter+posheight*lastcos
576 for pos, val in data:
577 posangle, poscos, possin = self.canvas_angle(pos)
578 posheight = trackheight*(val-midval)/resolution + ctr
579 x = self.xcenter+posheight*possin
580 y = self.ycenter+posheight*poscos
581 line_elements.append(Line(lastx, lasty, x, y,
582 strokeColor = graph.poscolor,
583 strokeWidth = graph.linewidth))
584 lastx, lasty, = x, y
585 return line_elements
586
588 """Returns list of drawable elements for a bar graph.
589
590 o graph Graph object
591 """
592
593
594
595
596 bar_elements = []
597
598
599 data_quartiles = graph.quartiles()
600 minval, maxval = data_quartiles[0],data_quartiles[4]
601 btm, ctr, top = self.track_radii[self.current_track_level]
602 trackheight = 0.5*(top-btm)
603 datarange = maxval - minval
604 if datarange == 0:
605 datarange = trackheight
606 data = graph[self.start:self.end]
607
608
609 if graph.center is None:
610 midval = (maxval + minval)/2.
611 else:
612 midval = graph.center
613
614
615
616
617 start, end = self._current_track_start_end()
618 data = intermediate_points(start, end, graph[start:end])
619
620 if not data:
621 return []
622
623
624
625
626 resolution = max((midval-minval), (maxval-midval))
627 if resolution == 0:
628 resolution = trackheight
629
630
631 for pos0, pos1, val in data:
632 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
633 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
634
635 barval = trackheight*(val-midval)/resolution
636 if barval >=0:
637 barcolor = graph.poscolor
638 else:
639 barcolor = graph.negcolor
640
641
642 bar_elements.append(self._draw_arc(ctr, ctr+barval, pos0angle,
643 pos1angle, barcolor))
644 return bar_elements
645
647 """Returns list of drawable elements for the heat graph.
648
649 o graph Graph object
650 """
651
652
653
654
655 heat_elements = []
656
657
658 data_quartiles = graph.quartiles()
659 minval, maxval = data_quartiles[0],data_quartiles[4]
660 midval = (maxval + minval)/2.
661 btm, ctr, top = self.track_radii[self.current_track_level]
662 trackheight = (top-btm)
663
664 start, end = self._current_track_start_end()
665 data = intermediate_points(start, end, graph[start:end])
666
667
668
669
670 for pos0, pos1, val in data:
671 pos0angle, pos0cos, pos0sin = self.canvas_angle(pos0)
672 pos1angle, pos1cos, pos1sin = self.canvas_angle(pos1)
673
674
675
676 heat = colors.linearlyInterpolatedColor(graph.poscolor,
677 graph.negcolor,
678 maxval, minval, val)
679
680
681 heat_elements.append(self._draw_arc(btm, top, pos0angle, pos1angle,
682 heat, border=heat))
683 return heat_elements
684
686 """Returns list of elements in the scale and list of their labels.
687
688 o track Track object
689 """
690 scale_elements = []
691 scale_labels = []
692
693 if not track.scale:
694
695 return [], []
696
697
698 btm, ctr, top = self.track_radii[self.current_track_level]
699 trackheight = (top-ctr)
700
701
702 start, end = self._current_track_start_end()
703 if track.start is not None or track.end is not None:
704
705 p = ArcPath(strokeColor=track.scale_color, fillColor=None)
706 startangle, startcos, startsin = self.canvas_angle(start)
707 endangle, endcos, endsin = self.canvas_angle(end)
708 p.addArc(self.xcenter, self.ycenter, ctr,
709 90 - (endangle * 180 / pi),
710 90 - (startangle * 180 / pi))
711 scale_elements.append(p)
712 del p
713
714 x0, y0 = self.xcenter+btm*startsin, self.ycenter+btm*startcos
715 x1, y1 = self.xcenter+top*startsin, self.ycenter+top*startcos
716 scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
717
718 x0, y0 = self.xcenter+btm*endsin, self.ycenter+btm*endcos
719 x1, y1 = self.xcenter+top*endsin, self.ycenter+top*endcos
720 scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
721 elif self.sweep < 1:
722
723 p = ArcPath(strokeColor=track.scale_color, fillColor=None)
724
725
726
727 p.addArc(self.xcenter, self.ycenter, ctr,
728 startangledegrees=90-360*self.sweep,
729 endangledegrees=90)
730 scale_elements.append(p)
731 del p
732
733 x0, y0 = self.xcenter, self.ycenter+btm
734 x1, y1 = self.xcenter, self.ycenter+top
735 scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
736
737 alpha = 2*pi*self.sweep
738 x0, y0 = self.xcenter+btm*sin(alpha), self.ycenter+btm*cos(alpha)
739 x1, y1 = self.xcenter+top*sin(alpha), self.ycenter+top*cos(alpha)
740 scale_elements.append(Line(x0, y0, x1, y1, strokeColor=track.scale_color))
741 else:
742
743 scale_elements.append(Circle(self.xcenter, self.ycenter, ctr,
744 strokeColor=track.scale_color,
745 fillColor=None))
746
747 start, end = self._current_track_start_end()
748 if track.scale_ticks:
749
750
751
752
753
754 ticklen = track.scale_largeticks * trackheight
755 tickiterval = int(track.scale_largetick_interval)
756
757
758
759
760 for tickpos in range(tickiterval * (self.start//tickiterval),
761 int(self.end), tickiterval):
762 if tickpos <= start or end <= tickpos:
763 continue
764 tick, label = self.draw_tick(tickpos, ctr, ticklen,
765 track,
766 track.scale_largetick_labels)
767 scale_elements.append(tick)
768 if label is not None:
769 scale_labels.append(label)
770
771 ticklen = track.scale_smallticks * trackheight
772 tickiterval = int(track.scale_smalltick_interval)
773 for tickpos in range(tickiterval * (self.start//tickiterval),
774 int(self.end), tickiterval):
775 if tickpos <= start or end <= tickpos:
776 continue
777 tick, label = self.draw_tick(tickpos, ctr, ticklen,
778 track,
779 track.scale_smalltick_labels)
780 scale_elements.append(tick)
781 if label is not None:
782 scale_labels.append(label)
783
784
785
786
787 startangle, startcos, startsin = self.canvas_angle(start)
788 endangle, endcos, endsin = self.canvas_angle(end)
789 if track.axis_labels:
790 for set in track.get_sets():
791 if set.__class__ is GraphSet:
792
793 for n in xrange(7):
794 angle = n * 1.0471975511965976
795 if angle < startangle or endangle < angle:
796 continue
797 ticksin, tickcos = sin(angle), cos(angle)
798 x0, y0 = self.xcenter+btm*ticksin, self.ycenter+btm*tickcos
799 x1, y1 = self.xcenter+top*ticksin, self.ycenter+top*tickcos
800 scale_elements.append(Line(x0, y0, x1, y1,
801 strokeColor=track.scale_color))
802
803 graph_label_min = []
804 graph_label_max = []
805 graph_label_mid = []
806 for graph in set.get_graphs():
807 quartiles = graph.quartiles()
808 minval, maxval = quartiles[0], quartiles[4]
809 if graph.center is None:
810 midval = (maxval + minval)/2.
811 graph_label_min.append("%.3f" % minval)
812 graph_label_max.append("%.3f" % maxval)
813 graph_label_mid.append("%.3f" % midval)
814 else:
815 diff = max((graph.center-minval),
816 (maxval-graph.center))
817 minval = graph.center-diff
818 maxval = graph.center+diff
819 midval = graph.center
820 graph_label_mid.append("%.3f" % midval)
821 graph_label_min.append("%.3f" % minval)
822 graph_label_max.append("%.3f" % maxval)
823 xmid, ymid = (x0+x1)/2., (y0+y1)/2.
824 for limit, x, y, in [(graph_label_min, x0, y0),
825 (graph_label_max, x1, y1),
826 (graph_label_mid, xmid, ymid)]:
827 label = String(0, 0, ";".join(limit),
828 fontName=track.scale_font,
829 fontSize=track.scale_fontsize,
830 fillColor=track.scale_color)
831 label.textAnchor = 'middle'
832 labelgroup = Group(label)
833 labelgroup.transform = (tickcos, -ticksin,
834 ticksin, tickcos,
835 x, y)
836 scale_labels.append(labelgroup)
837
838 return scale_elements, scale_labels
839
840 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
841 """Returns drawing element for a tick on the scale.
842
843 o tickpos Int, position of the tick on the sequence
844
845 o ctr Float, Y co-ord of the center of the track
846
847 o ticklen How long to draw the tick
848
849 o track Track, the track the tick is drawn on
850
851 o draw_label Boolean, write the tick label?
852 """
853
854 tickangle, tickcos, ticksin = self.canvas_angle(tickpos)
855 x0, y0 = self.xcenter+ctr*ticksin, self.ycenter+ctr*tickcos
856 x1, y1 = self.xcenter+(ctr+ticklen)*ticksin, self.ycenter+(ctr+ticklen)*tickcos
857
858
859
860
861
862 tick = Line(x0, y0, x1, y1, strokeColor=track.scale_color)
863 if draw_label:
864
865 if track.scale_format == 'SInt':
866 if tickpos >= 1000000:
867 tickstring = str(tickpos//1000000) + " Mbp"
868 elif tickpos >= 1000:
869 tickstring = str(tickpos//1000) + " Kbp"
870 else:
871 tickstring = str(tickpos)
872 else:
873 tickstring = str(tickpos)
874 label = String(0, 0, tickstring,
875 fontName=track.scale_font,
876 fontSize=track.scale_fontsize,
877 fillColor=track.scale_color)
878 if tickangle > pi:
879 label.textAnchor = 'end'
880
881
882
883
884 labelgroup = Group(label)
885 labelgroup.transform = (1,0,0,1, x1, y1)
886 else:
887 labelgroup = None
888 return tick, labelgroup
889
891 """Draw blue test tracks with grene line down their center."""
892
893 for track in self.drawn_tracks:
894 btm, ctr, top = self.track_radii[track]
895 self.drawing.add(Circle(self.xcenter, self.ycenter, top,
896 strokeColor=colors.blue,
897 fillColor=None))
898 self.drawing.add(Circle(self.xcenter, self.ycenter, ctr,
899 strokeColor=colors.green,
900 fillColor=None))
901 self.drawing.add(Circle(self.xcenter, self.ycenter, btm,
902 strokeColor=colors.blue,
903 fillColor=None))
904
906 """Drawing element for grey background to passed track.
907
908 o track Track object
909 """
910 greytrack_bgs = []
911 greytrack_labels = []
912
913 if not track.greytrack:
914 return [], []
915
916
917 btm, ctr, top = self.track_radii[self.current_track_level]
918
919 start, end = self._current_track_start_end()
920 startangle, startcos, startsin = self.canvas_angle(start)
921 endangle, endcos, endsin = self.canvas_angle(end)
922
923
924 if track.start is not None or track.end is not None:
925
926 p = ArcPath(strokeColor=track.scale_color, fillColor=None)
927 greytrack_bgs.append(self._draw_arc(btm, top, startangle, endangle,
928 colors.Color(0.96, 0.96, 0.96)))
929 elif self.sweep < 1:
930
931
932 greytrack_bgs.append(self._draw_arc(btm, top, 0, 2*pi*self.sweep,
933 colors.Color(0.96, 0.96, 0.96)))
934 else:
935
936 greytrack_bgs.append(Circle(self.xcenter, self.ycenter, ctr,
937 strokeColor = colors.Color(0.96, 0.96, 0.96),
938 fillColor=None, strokeWidth=top-btm))
939
940 if track.greytrack_labels:
941
942 labelstep = self.length//track.greytrack_labels
943 for pos in range(self.start, self.end, labelstep):
944 label = String(0, 0, track.name,
945 fontName=track.greytrack_font,
946 fontSize=track.greytrack_fontsize,
947 fillColor=track.greytrack_fontcolor)
948 theta, costheta, sintheta = self.canvas_angle(pos)
949 if theta < startangle or endangle < theta:
950 continue
951 x,y = self.xcenter+btm*sintheta, self.ycenter+btm*costheta
952 labelgroup = Group(label)
953 labelangle = self.sweep*2*pi*(pos-self.start)/self.length - pi/2
954 if theta > pi:
955 label.textAnchor = 'end'
956 labelangle += pi
957 cosA, sinA = cos(labelangle), sin(labelangle)
958 labelgroup.transform = (cosA, -sinA, sinA,
959 cosA, x, y)
960 if not self.length-x <= labelstep:
961 greytrack_labels.append(labelgroup)
962
963 return greytrack_bgs, greytrack_labels
964
966 """Given base-pair position, return (angle, cosine, sin)."""
967 angle = self.sweep*2*pi*(base-self.start)/self.length
968 return (angle, cos(angle), sin(angle))
969
970 - def _draw_sigil_box(self, bottom, center, top,
971 startangle, endangle, strand,
972 **kwargs):
973 """Draw BOX sigil."""
974 if strand == 1:
975 inner_radius = center
976 outer_radius = top
977 elif strand == -1:
978 inner_radius = bottom
979 outer_radius = center
980 else:
981 inner_radius = bottom
982 outer_radius = top
983 return self._draw_arc(inner_radius, outer_radius, startangle, endangle, **kwargs)
984
985 - def _draw_arc(self, inner_radius, outer_radius, startangle, endangle,
986 color, border=None, colour=None, **kwargs):
987 """Returns close path describing an arc box.
988
989 o inner_radius Float distance of inside of arc from drawing center
990
991 o outer_radius Float distance of outside of arc from drawing center
992
993 o startangle Float angle subtended by start of arc at drawing center
994 (in radians)
995
996 o endangle Float angle subtended by end of arc at drawing center
997 (in radians)
998
999 o color colors.Color object for arc (overridden by backwards
1000 compatible argument with UK spelling, colour).
1001
1002 Returns a closed path object describing an arced box corresponding to
1003 the passed values. For very small angles, a simple four sided
1004 polygon is used.
1005 """
1006
1007 if colour is not None:
1008 color = colour
1009
1010 strokecolor, color = _stroke_and_fill_colors(color, border)
1011
1012 if abs(float(endangle - startangle))>.01:
1013
1014 p = ArcPath(strokeColor=strokecolor,
1015 fillColor=color,
1016 strokewidth=0)
1017
1018
1019
1020
1021 p.addArc(self.xcenter, self.ycenter, inner_radius,
1022 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
1023 moveTo=True)
1024 p.addArc(self.xcenter, self.ycenter, outer_radius,
1025 90 - (endangle * 180 / pi), 90 - (startangle * 180 / pi),
1026 reverse=True)
1027 p.closePath()
1028 return p
1029 else:
1030
1031
1032 startcos, startsin = cos(startangle), sin(startangle)
1033 endcos, endsin = cos(endangle), sin(endangle)
1034 x0,y0 = self.xcenter, self.ycenter
1035 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
1036 x2,y2 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
1037 x3,y3 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
1038 x4,y4 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
1039 return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border)
1040
1041 - def _draw_arc_line(self, path, start_radius, end_radius, start_angle, end_angle,
1042 move=False):
1043 """Adds a list of points to a path object.
1044
1045 Assumes angles given are in degrees!
1046
1047 Represents what would be a straight line on a linear diagram.
1048 """
1049 x0, y0 = self.xcenter, self.ycenter
1050 radius_diff = end_radius - start_radius
1051 angle_diff = end_angle - start_angle
1052 dx = 0.01
1053 a = start_angle*pi/180
1054 if move:
1055 path.moveTo(x0+start_radius*cos(a), y0+start_radius*sin(a))
1056 else:
1057 path.lineTo(x0+start_radius*cos(a), y0+start_radius*sin(a))
1058 x = dx
1059 if 0.01 <= abs(dx):
1060 while x < 1:
1061 r = start_radius + x*radius_diff
1062 a = (start_angle + x * (angle_diff)) * pi / 180
1063
1064 path.lineTo(x0+r*cos(a), y0+r*sin(a))
1065 x += dx
1066 a = end_angle*pi/180
1067 path.lineTo(x0+end_radius*cos(a), y0+end_radius*sin(a))
1068
1069 - def _draw_arc_poly(self, inner_radius, outer_radius,
1070 inner_startangle, inner_endangle,
1071 outer_startangle, outer_endangle,
1072 color, border=None, flip=False,
1073 **kwargs):
1074 """Returns polygon path describing an arc."""
1075 strokecolor, color = _stroke_and_fill_colors(color, border)
1076
1077 x0, y0 = self.xcenter, self.ycenter
1078 if abs(inner_endangle - outer_startangle)>0.01 \
1079 or abs(outer_endangle - inner_startangle)>0.01 \
1080 or abs(inner_startangle - outer_startangle)>0.01 \
1081 or abs(outer_startangle - outer_startangle)>0.01:
1082
1083 p = ArcPath(strokeColor=strokecolor,
1084 fillColor=color,
1085
1086 strokeLineJoin=1,
1087 strokewidth=0)
1088
1089
1090
1091
1092 i_start = 90 - (inner_startangle * 180 / pi)
1093 i_end = 90 - (inner_endangle * 180 / pi)
1094 o_start = 90 - (outer_startangle * 180 / pi)
1095 o_end = 90 - (outer_endangle * 180 / pi)
1096 p.addArc(x0, y0, inner_radius, i_end, i_start,
1097 moveTo=True, reverse=True)
1098 if flip:
1099
1100 self._draw_arc_line(p, inner_radius, outer_radius, i_end, o_start)
1101 p.addArc(x0, y0, outer_radius, o_end, o_start, reverse=True)
1102 self._draw_arc_line(p, outer_radius, inner_radius, o_end, i_start)
1103 else:
1104
1105 self._draw_arc_line(p, inner_radius, outer_radius, i_end, o_end)
1106 p.addArc(x0, y0, outer_radius, o_end, o_start,
1107 reverse=False)
1108 self._draw_arc_line(p, outer_radius, inner_radius, o_start, i_start)
1109 p.closePath()
1110 return p
1111 else:
1112
1113
1114 inner_startcos, inner_startsin = cos(inner_startangle), sin(inner_startangle)
1115 inner_endcos, inner_endsin = cos(inner_endangle), sin(inner_endangle)
1116 outer_startcos, outer_startsin = cos(outer_startangle), sin(outer_startangle)
1117 outer_endcos, outer_endsin = cos(outer_endangle), sin(outer_endangle)
1118 x1,y1 = (x0+inner_radius*inner_startsin, y0+inner_radius*inner_startcos)
1119 x2,y2 = (x0+inner_radius*inner_endsin, y0+inner_radius*inner_endcos)
1120 x3,y3 = (x0+outer_radius*outer_endsin, y0+outer_radius*outer_endcos)
1121 x4,y4 = (x0+outer_radius*outer_startsin, y0+outer_radius*outer_startcos)
1122 return draw_polygon([(x1,y1),(x2,y2),(x3,y3),(x4,y4)], color, border,
1123
1124 strokeLineJoin=1,
1125 )
1126
1127 - def _draw_sigil_cut_corner_box(self, bottom, center, top,
1128 startangle, endangle, strand,
1129 color, border=None, corner=0.5,
1130 **kwargs):
1131 """Draw OCTO sigil, box with corners cut off."""
1132 if strand == 1:
1133 inner_radius = center
1134 outer_radius = top
1135 elif strand == -1:
1136 inner_radius = bottom
1137 outer_radius = center
1138 else:
1139 inner_radius = bottom
1140 outer_radius = top
1141
1142 strokecolor, color = _stroke_and_fill_colors(color, border)
1143
1144 startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1145 angle = float(endangle - startangle)
1146
1147 middle_radius = 0.5*(inner_radius+outer_radius)
1148 boxheight = outer_radius - inner_radius
1149
1150 corner_len = min(0.5*boxheight, 0.5*boxheight*corner)
1151 shaft_inner_radius = inner_radius + corner_len
1152 shaft_outer_radius = outer_radius - corner_len
1153
1154 cornerangle_delta = max(0.0,min(abs(boxheight)*0.5*corner/middle_radius, abs(angle*0.5)))
1155 if angle < 0:
1156 cornerangle_delta *= -1
1157
1158
1159 startcos, startsin = cos(startangle), sin(startangle)
1160 endcos, endsin = cos(endangle), sin(endangle)
1161 x0, y0 = self.xcenter, self.ycenter
1162 p = ArcPath(strokeColor=strokecolor,
1163 fillColor=color,
1164 strokeLineJoin=1,
1165 strokewidth=0,
1166 **kwargs)
1167
1168 p.addArc(self.xcenter, self.ycenter, inner_radius,
1169 90 - ((endangle-cornerangle_delta) * 180 / pi),
1170 90 - ((startangle+cornerangle_delta) * 180 / pi),
1171 moveTo=True)
1172
1173
1174 p.lineTo(x0+shaft_inner_radius*startsin, y0+shaft_inner_radius*startcos)
1175 p.lineTo(x0+shaft_outer_radius*startsin, y0+shaft_outer_radius*startcos)
1176
1177 p.addArc(self.xcenter, self.ycenter, outer_radius,
1178 90 - ((endangle-cornerangle_delta) * 180 / pi),
1179 90 - ((startangle+cornerangle_delta) * 180 / pi),
1180 reverse=True)
1181
1182 p.lineTo(x0+shaft_outer_radius*endsin, y0+shaft_outer_radius*endcos)
1183 p.lineTo(x0+shaft_inner_radius*endsin, y0+shaft_inner_radius*endcos)
1184 p.closePath()
1185 return p
1186
1187 - def _draw_sigil_arrow(self, bottom, center, top,
1188 startangle, endangle, strand,
1189 **kwargs):
1190 """Draw ARROW sigil."""
1191 if strand == 1:
1192 inner_radius = center
1193 outer_radius = top
1194 orientation = "right"
1195 elif strand == -1:
1196 inner_radius = bottom
1197 outer_radius = center
1198 orientation = "left"
1199 else:
1200 inner_radius = bottom
1201 outer_radius = top
1202 orientation = "right"
1203 return self._draw_arc_arrow(inner_radius, outer_radius, startangle, endangle,
1204 orientation=orientation, **kwargs)
1205
1206 - def _draw_sigil_big_arrow(self, bottom, center, top,
1207 startangle, endangle, strand,
1208 **kwargs):
1209 """Draw BIGARROW sigil, like ARROW but straddles the axis."""
1210 if strand == -1:
1211 orientation = "left"
1212 else:
1213 orientation = "right"
1214 return self._draw_arc_arrow(bottom, top, startangle, endangle,
1215 orientation=orientation, **kwargs)
1216
1217 - def _draw_arc_arrow(self, inner_radius, outer_radius, startangle, endangle,
1218 color, border=None,
1219 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right',
1220 colour=None, **kwargs):
1221 """Draw an arrow along an arc."""
1222
1223 if colour is not None:
1224 color = colour
1225
1226 strokecolor, color = _stroke_and_fill_colors(color, border)
1227
1228
1229
1230
1231
1232
1233 startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1234 if orientation != "left" and orientation != "right":
1235 raise ValueError("Invalid orientation %s, should be 'left' or 'right'"
1236 % repr(orientation))
1237
1238 angle = float(endangle - startangle)
1239 middle_radius = 0.5*(inner_radius+outer_radius)
1240 boxheight = outer_radius - inner_radius
1241 shaft_height = boxheight*shaft_height_ratio
1242 shaft_inner_radius = middle_radius - 0.5*shaft_height
1243 shaft_outer_radius = middle_radius + 0.5*shaft_height
1244 headangle_delta = max(0.0,min(abs(boxheight)*head_length_ratio/middle_radius, abs(angle)))
1245 if angle < 0:
1246 headangle_delta *= -1
1247 if orientation=="right":
1248 headangle = endangle-headangle_delta
1249 else:
1250 headangle = startangle+headangle_delta
1251 if startangle <= endangle:
1252 headangle = max(min(headangle, endangle), startangle)
1253 else:
1254 headangle = max(min(headangle, startangle), endangle)
1255 assert startangle <= headangle <= endangle \
1256 or endangle <= headangle <= startangle, \
1257 (startangle, headangle, endangle, angle)
1258
1259
1260 startcos, startsin = cos(startangle), sin(startangle)
1261 headcos, headsin = cos(headangle), sin(headangle)
1262 endcos, endsin = cos(endangle), sin(endangle)
1263 x0,y0 = self.xcenter, self.ycenter
1264 if 0.5 >= abs(angle) and abs(headangle_delta) >= abs(angle):
1265
1266
1267 if orientation=="right":
1268 x1,y1 = (x0+inner_radius*startsin, y0+inner_radius*startcos)
1269 x2,y2 = (x0+outer_radius*startsin, y0+outer_radius*startcos)
1270 x3,y3 = (x0+middle_radius*endsin, y0+middle_radius*endcos)
1271 else:
1272 x1,y1 = (x0+inner_radius*endsin, y0+inner_radius*endcos)
1273 x2,y2 = (x0+outer_radius*endsin, y0+outer_radius*endcos)
1274 x3,y3 = (x0+middle_radius*startsin, y0+middle_radius*startcos)
1275
1276
1277 return Polygon([x1,y1,x2,y2,x3,y3],
1278 strokeColor=border or color,
1279 fillColor=color,
1280 strokeLineJoin=1,
1281 strokewidth=0)
1282 elif orientation=="right":
1283 p = ArcPath(strokeColor=strokecolor,
1284 fillColor=color,
1285
1286 strokeLineJoin=1,
1287 strokewidth=0,
1288 **kwargs)
1289
1290
1291
1292
1293 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
1294 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1295 moveTo=True)
1296 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
1297 90 - (headangle * 180 / pi), 90 - (startangle * 180 / pi),
1298 reverse=True)
1299 if abs(angle) < 0.5:
1300 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
1301 p.lineTo(x0+middle_radius*endsin, y0+middle_radius*endcos)
1302 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1303 else:
1304 self._draw_arc_line(p, outer_radius, middle_radius,
1305 90 - (headangle * 180 / pi),
1306 90 - (endangle * 180 / pi))
1307 self._draw_arc_line(p, middle_radius, inner_radius,
1308 90 - (endangle * 180 / pi),
1309 90 - (headangle * 180 / pi))
1310 p.closePath()
1311 return p
1312 else:
1313 p = ArcPath(strokeColor=strokecolor,
1314 fillColor=color,
1315
1316 strokeLineJoin=1,
1317 strokewidth=0,
1318 **kwargs)
1319
1320
1321
1322
1323 p.addArc(self.xcenter, self.ycenter, shaft_inner_radius,
1324 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1325 moveTo=True, reverse=True)
1326 p.addArc(self.xcenter, self.ycenter, shaft_outer_radius,
1327 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1328 reverse=False)
1329
1330
1331 if abs(angle) < 0.5:
1332 p.lineTo(x0+outer_radius*headsin, y0+outer_radius*headcos)
1333 p.lineTo(x0+middle_radius*startsin, y0+middle_radius*startcos)
1334 p.lineTo(x0+inner_radius*headsin, y0+inner_radius*headcos)
1335 else:
1336 self._draw_arc_line(p, outer_radius, middle_radius,
1337 90 - (headangle * 180 / pi),
1338 90 - (startangle * 180 / pi))
1339 self._draw_arc_line(p, middle_radius, inner_radius,
1340 90 - (startangle * 180 / pi),
1341 90 - (headangle * 180 / pi))
1342 p.closePath()
1343 return p
1344
1345 - def _draw_sigil_jaggy(self, bottom, center, top,
1346 startangle, endangle, strand,
1347 color, border=None,
1348 **kwargs):
1349 """Draw JAGGY sigil.
1350
1351 Although we may in future expose the head/tail jaggy lengths, for now
1352 both the left and right edges are drawn jagged.
1353 """
1354 if strand == 1:
1355 inner_radius = center
1356 outer_radius = top
1357 teeth = 2
1358 elif strand == -1:
1359 inner_radius = bottom
1360 outer_radius = center
1361 teeth = 2
1362 else:
1363 inner_radius = bottom
1364 outer_radius = top
1365 teeth = 4
1366
1367
1368 tail_length_ratio = 1.0
1369 head_length_ratio = 1.0
1370
1371 strokecolor, color = _stroke_and_fill_colors(color, border)
1372
1373 startangle, endangle = min(startangle, endangle), max(startangle, endangle)
1374 angle = float(endangle - startangle)
1375 height = outer_radius - inner_radius
1376
1377 assert startangle <= endangle and angle >= 0
1378 if head_length_ratio and tail_length_ratio:
1379 headangle = max(endangle - min(height*head_length_ratio/(center*teeth), angle*0.5), startangle)
1380 tailangle = min(startangle + min(height*tail_length_ratio/(center*teeth), angle*0.5), endangle)
1381 elif head_length_ratio:
1382 headangle = max(endangle - min(height*head_length_ratio/(center*teeth), angle), startangle)
1383 tailangle = startangle
1384 else:
1385 headangle = endangle
1386 tailangle = min(startangle + min(height*tail_length_ratio/(center*teeth), angle), endangle)
1387
1388 assert startangle <= tailangle <= headangle <= endangle, \
1389 (startangle, tailangle, headangle, endangle, angle)
1390
1391
1392 startcos, startsin = cos(startangle), sin(startangle)
1393 headcos, headsin = cos(headangle), sin(headangle)
1394 endcos, endsin = cos(endangle), sin(endangle)
1395 x0,y0 = self.xcenter, self.ycenter
1396
1397 p = ArcPath(strokeColor=strokecolor,
1398 fillColor=color,
1399
1400 strokeLineJoin=1,
1401 strokewidth=0,
1402 **kwargs)
1403
1404
1405
1406
1407 p.addArc(self.xcenter, self.ycenter, inner_radius,
1408 90 - (headangle * 180 / pi), 90 - (tailangle * 180 / pi),
1409 moveTo=True)
1410 for i in range(0, teeth):
1411 p.addArc(self.xcenter, self.ycenter, inner_radius+i*height/teeth,
1412 90 - (tailangle * 180 / pi), 90 - (startangle * 180 / pi))
1413
1414 self._draw_arc_line(p,
1415 inner_radius+i*height/teeth,
1416 inner_radius+(i+1)*height/teeth,
1417 90 - (startangle * 180 / pi),
1418 90 - (tailangle * 180 / pi))
1419 p.addArc(self.xcenter, self.ycenter, outer_radius,
1420 90 - (headangle * 180 / pi), 90 - (tailangle * 180 / pi),
1421 reverse=True)
1422 for i in range(0, teeth):
1423 p.addArc(self.xcenter, self.ycenter, outer_radius-i*height/teeth,
1424 90 - (endangle * 180 / pi), 90 - (headangle * 180 / pi),
1425 reverse=True)
1426
1427 self._draw_arc_line(p,
1428 outer_radius-i*height/teeth,
1429 outer_radius-(i+1)*height/teeth,
1430 90 - (endangle * 180 / pi),
1431 90 - (headangle * 180 / pi))
1432 p.closePath()
1433 return p
1434