1
2
3
4
5
6
7
8
9
10
11
12 """ LinearDrawer module
13
14 Provides:
15
16 o LinearDrawer - Drawing object for linear 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
33
34 from _AbstractDrawer import AbstractDrawer, draw_box, draw_arrow
35 from _AbstractDrawer import draw_cut_corner_box, _stroke_and_fill_colors
36 from _AbstractDrawer import intermediate_points, angle2trig
37 from _FeatureSet import FeatureSet
38 from _GraphSet import GraphSet
39
40 from math import ceil
41
42
44 """ LinearDrawer(AbstractDrawer)
45
46 Inherits from:
47
48 o AbstractDrawer
49
50 Provides:
51
52 Methods:
53
54 o __init__(self, ...) Called on instantiation
55
56 o set_page_size(self, pagesize, orientation) Set the page size to the
57 passed size and orientation
58
59 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the
60 page
61
62 o set_bounds(self, start, end) Set the bounds for the elements to be
63 drawn
64
65 o is_in_bounds(self, value) Returns a boolean for whether the position
66 is actually to be drawn
67
68 o __len__(self) Returns the length of sequence that will be drawn
69
70
71 o draw(self) Place the drawing elements on the diagram
72
73 o init_fragments(self) Calculate information
74 about sequence fragment locations on the drawing
75
76 o set_track_heights(self) Calculate information about the offset of
77 each track from the fragment base
78
79 o draw_test_tracks(self) Add lines demarcating each track to the
80 drawing
81
82 o draw_track(self, track) Return the contents of the passed track as
83 drawing elements
84
85 o draw_scale(self, track) Return a scale for the passed track as
86 drawing elements
87
88 o draw_tick(self, tickpos, ctr, ticklen, track, draw_label) Return a
89 tick line and possibly a label
90
91 o draw_greytrack(self, track) Return a grey background and superposed
92 label for the passed track as drawing
93 elements
94
95 o draw_feature_set(self, set) Return the features in the passed set as
96 drawing elements
97
98 o draw_feature(self, feature) Return a single feature as drawing
99 elements
100
101 o get_feature_sigil(self, feature, x0, x1, fragment) Return a single
102 feature as its sigil in drawing elements
103
104 o draw_graph_set(self, set) Return the data in a set of graphs as
105 drawing elements
106
107 o draw_line_graph(self, graph) Return the data in a graph as a line
108 graph in drawing elements
109
110 o draw_heat_graph(self, graph) Return the data in a graph as a heat
111 graph in drawing elements
112
113 o draw_bar_graph(self, graph) Return the data in a graph as a bar
114 graph in drawing elements
115
116 o canvas_location(self, base) Return the fragment, and the offset from
117 the left margin, of a passed position
118 in the sequence, on the diagram.
119
120 Attributes:
121
122 o tracklines Boolean for whether to draw lines dilineating tracks
123
124 o pagesize Tuple describing the size of the page in pixels
125
126 o x0 Float X co-ord for leftmost point of drawable area
127
128 o xlim Float X co-ord for rightmost point of drawable area
129
130 o y0 Float Y co-ord for lowest point of drawable area
131
132 o ylim Float Y co-ord for topmost point of drawable area
133
134 o pagewidth Float pixel width of drawable area
135
136 o pageheight Float pixel height of drawable area
137
138 o xcenter Float X co-ord of center of drawable area
139
140 o ycenter Float Y co-ord of center of drawable area
141
142 o start Int, base to start drawing from
143
144 o end Int, base to stop drawing at
145
146 o length Int, size of sequence to be drawn
147
148 o fragments Int, number of fragments into which to divide the
149 drawn sequence
150
151 o fragment_size Float (0->1) the proportion of the fragment height to
152 draw in
153
154 o track_size Float (0->1) the proportion of the track height to
155 draw in
156
157 o drawing Drawing canvas
158
159 o drawn_tracks List of ints denoting which tracks are to be drawn
160
161 o current_track_level Int denoting which track is currently being
162 drawn
163
164 o fragment_height Float total fragment height in pixels
165
166 o fragment_bases Int total fragment length in bases
167
168 o fragment_lines Dictionary of top and bottom y-coords of fragment,
169 keyed by fragment number
170
171 o fragment_limits Dictionary of start and end bases of each fragment,
172 keyed by fragment number
173
174 o track_offsets Dictionary of number of pixels that each track top,
175 center and bottom is offset from the base of a
176 fragment, keyed by track
177
178 o cross_track_links List of tuples each with four entries (track A,
179 feature A, track B, feature B) to be linked.
180
181 """
182 - def __init__(self, parent=None, pagesize='A3', orientation='landscape',
183 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
184 start=None, end=None, tracklines=0, fragments=10,
185 fragment_size=0.9, track_size=0.75, cross_track_links=None):
186 """ __init__(self, parent, pagesize='A3', orientation='landscape',
187 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
188 start=None, end=None, tracklines=0, fragments=10,
189 fragment_size=0.9, track_size=0.75)
190
191 o parent Diagram object containing the data that the drawer
192 draws
193
194 o pagesize String describing the ISO size of the image, or a tuple
195 of pixels
196
197 o orientation String describing the required orientation of the
198 final drawing ('landscape' or 'portrait')
199
200 o x Float (0->1) describing the relative size of the X
201 margins to the page
202
203 o y Float (0->1) describing the relative size of the Y
204 margins to the page
205
206 o xl Float (0->1) describing the relative size of the left X
207 margin to the page (overrides x)
208
209 o xl Float (0->1) describing the relative size of the left X
210 margin to the page (overrides x)
211
212 o xr Float (0->1) describing the relative size of the right X
213 margin to the page (overrides x)
214
215 o yt Float (0->1) describing the relative size of the top Y
216 margin to the page (overrides y)
217
218 o yb Float (0->1) describing the relative size of the lower Y
219 margin to the page (overrides y)
220
221 o start Int, the position to begin drawing the diagram at
222
223 o end Int, the position to stop drawing the diagram at
224
225 o tracklines Boolean flag to show (or not) lines delineating tracks
226 on the diagram
227
228 o fragments Int, the number of equal fragments into which the
229 sequence should be divided for drawing
230
231 o fragment_size Float(0->1) The proportion of the available height
232 for the fragment that should be taken up in drawing
233
234 o track_size The proportion of the available track height that
235 should be taken up in drawing
236
237 o cross_track_links List of tuples each with four entries (track A,
238 feature A, track B, feature B) to be linked.
239 """
240
241 AbstractDrawer.__init__(self, parent, pagesize, orientation,
242 x, y, xl, xr, yt, yb, start, end,
243 tracklines, cross_track_links)
244
245
246 self.fragments = fragments
247 self.fragment_size = fragment_size
248 self.track_size = track_size
249
251 """ draw(self)
252
253 Draw a linear diagram of the data in the parent Diagram object
254 """
255
256 self.drawing = Drawing(self.pagesize[0], self.pagesize[1])
257
258 feature_elements = []
259 feature_labels = []
260 greytrack_bgs = []
261 greytrack_labels = []
262 scale_axes = []
263 scale_labels = []
264
265
266 self.drawn_tracks = self._parent.get_drawn_levels()
267
268
269 self.init_fragments()
270 self.set_track_heights()
271
272
273
274 for track_level in self.drawn_tracks:
275 self.current_track_level = track_level
276 track = self._parent[track_level]
277 gbgs, glabels = self.draw_greytrack(track)
278 greytrack_bgs.append(gbgs)
279 greytrack_labels.append(glabels)
280 features, flabels = self.draw_track(track)
281 feature_elements.append(features)
282 feature_labels.append(flabels)
283 if track.scale:
284 axes, slabels = self.draw_scale(track)
285 scale_axes.append(axes)
286 scale_labels.append(slabels)
287
288 feature_cross_links = []
289 for cross_link_obj in self.cross_track_links:
290 cross_link_elements = self.draw_cross_link(cross_link_obj)
291 if cross_link_elements:
292 feature_cross_links.append(cross_link_elements)
293
294
295
296
297
298
299
300
301
302 element_groups = [greytrack_bgs, feature_cross_links,
303 feature_elements, scale_axes,
304 scale_labels, feature_labels, greytrack_labels]
305 for element_group in element_groups:
306 for element_list in element_group:
307 [self.drawing.add(element) for element in element_list]
308
309 if self.tracklines:
310 self.draw_test_tracks()
311
313 """ init_fragments(self)
314
315 Initialises useful values for calculating the positioning of
316 diagram elements
317 """
318
319 self.fragment_height = 1.*self.pageheight/self.fragments
320 self.fragment_bases = ceil(1.*self.length/self.fragments)
321
322
323 self.fragment_lines = {}
324 fragment_crop = (1-self.fragment_size)/2
325 fragy = self.ylim
326 for fragment in range(self.fragments):
327 fragtop = fragy-fragment_crop * self.fragment_height
328 fragbtm = fragy-(1-fragment_crop) * self.fragment_height
329 self.fragment_lines[fragment] = (fragbtm, fragtop)
330 fragy -= self.fragment_height
331
332
333 self.fragment_limits = {}
334 fragment_step = self.fragment_bases
335 fragment_count = 0
336
337 for marker in range(int(self.start), int(self.end), int(fragment_step)):
338 self.fragment_limits[fragment_count] = (marker, marker+fragment_step)
339 fragment_count += 1
340
342 """ set_track_heights(self)
343
344 Since tracks may not be of identical heights, the bottom and top
345 offsets of each track relative to the fragment top and bottom is
346 stored in a dictionary - self.track_offsets, keyed by track number
347 """
348 bot_track = min(min(self.drawn_tracks), 1)
349 top_track = max(self.drawn_tracks)
350
351 trackunit_sum = 0
352 trackunits = {}
353 heightholder = 0
354 for track in range(bot_track, top_track+1):
355 try:
356 trackheight = self._parent[track].height
357 except:
358 trackheight = 1
359 trackunit_sum += trackheight
360 trackunits[track] = (heightholder, heightholder+trackheight)
361 heightholder += trackheight
362 trackunit_height = 1.*self.fragment_height*self.fragment_size/trackunit_sum
363
364
365
366 track_offsets = {}
367 track_crop = trackunit_height*(1-self.track_size)/2.
368 assert track_crop >= 0
369 for track in trackunits:
370 top = trackunits[track][1]*trackunit_height - track_crop
371 btm = trackunits[track][0]*trackunit_height + track_crop
372 ctr = btm+(top-btm)/2.
373 track_offsets[track] = (btm, ctr, top)
374 self.track_offsets = track_offsets
375
377 """ draw_test_tracks(self)
378
379 Draw red lines indicating the top and bottom of each fragment,
380 and blue ones indicating tracks to be drawn.
381 """
382
383 for fbtm, ftop in self.fragment_lines.values():
384 self.drawing.add(Line(self.x0, ftop, self.xlim, ftop,
385 strokeColor=colors.red))
386 self.drawing.add(Line(self.x0, fbtm, self.xlim, fbtm,
387 strokeColor=colors.red))
388
389
390 for track in self.drawn_tracks:
391 trackbtm = fbtm + self.track_offsets[track][0]
392 trackctr = fbtm + self.track_offsets[track][1]
393 tracktop = fbtm + self.track_offsets[track][2]
394 self.drawing.add(Line(self.x0, tracktop, self.xlim, tracktop,
395 strokeColor=colors.blue))
396 self.drawing.add(Line(self.x0, trackctr, self.xlim, trackctr,
397 strokeColor=colors.green))
398 self.drawing.add(Line(self.x0, trackbtm, self.xlim, trackbtm,
399 strokeColor=colors.blue))
400
402 """ draw_track(self, track) -> ([element, element,...], [element, element,...])
403
404 o track Track object
405
406 Returns a tuple (list of elements in the track, list of labels in
407 the track)
408 """
409 track_elements = []
410 track_labels = []
411
412
413 set_methods = {FeatureSet: self.draw_feature_set,
414 GraphSet: self.draw_graph_set
415 }
416
417 for set in track.get_sets():
418 elements, labels = set_methods[set.__class__](set)
419 track_elements += elements
420 track_labels += labels
421 return track_elements, track_labels
422
423 - def draw_tick(self, tickpos, ctr, ticklen, track, draw_label):
424 """ draw_tick(self, tickpos, ctr, ticklen) -> (element, element)
425
426 o tickpos Int, position of the tick on the sequence
427
428 o ctr Float, Y co-ord of the center of the track
429
430 o ticklen How long to draw the tick
431
432 o track Track, the track the tick is drawn on
433
434 o draw_label Boolean, write the tick label?
435
436 Returns a drawing element that is the tick on the scale
437 """
438 assert self.start <= tickpos and tickpos <= self.end, \
439 "Tick at %i, but showing %i to %i" \
440 % (tickpos, self.start, self.end)
441 assert (track.start is None or track.start <= tickpos) and \
442 (track.end is None or tickpos <= track.end), \
443 "Tick at %i, but showing %r to %r for track" \
444 % (tickpos, track.start, track.end)
445 fragment, tickx = self.canvas_location(tickpos)
446 assert fragment >=0, \
447 "Fragment %i, tickpos %i" % (fragment, tickpos)
448 tctr = ctr + self.fragment_lines[fragment][0]
449 tickx += self.x0
450 ticktop = tctr + ticklen
451 tick = Line(tickx, tctr, tickx, ticktop, strokeColor=track.scale_color)
452 if draw_label:
453 if track.scale_format == 'SInt':
454 if tickpos >= 1000000:
455 tickstring = str(tickpos//1000000) + " Mbp"
456 elif tickpos >= 1000:
457 tickstring = str(tickpos//1000) + " Kbp"
458 else:
459 tickstring = str(tickpos)
460 else:
461 tickstring = str(tickpos)
462 label = String(0, 0, tickstring,
463 fontName=track.scale_font,
464 fontSize=track.scale_fontsize,
465 fillColor=track.scale_color)
466 labelgroup = Group(label)
467 rotation = angle2trig(track.scale_fontangle)
468 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
469 rotation[3], tickx, ticktop)
470 else:
471 labelgroup = None
472 return tick, labelgroup
473
475 """ draw_scale(self, track) -> ([element, element,...], [element, element,...])
476
477 o track Track object
478
479 Returns a tuple of (list of elements in the scale, list of labels
480 in the scale)
481 """
482 scale_elements = []
483 scale_labels = []
484
485 if not track.scale:
486 return [], []
487
488
489 btm, ctr, top = self.track_offsets[self.current_track_level]
490 trackheight = (top-ctr)
491
492
493 start, end = self._current_track_start_end()
494 start_f, start_x = self.canvas_location(start)
495 end_f, end_x = self.canvas_location(end)
496
497 for fragment in range(start_f, end_f+1):
498 tbtm = btm + self.fragment_lines[fragment][0]
499 tctr = ctr + self.fragment_lines[fragment][0]
500 ttop = top + self.fragment_lines[fragment][0]
501
502 if fragment == start_f:
503 x_left = start_x
504 else:
505 x_left = 0
506 if fragment == end_f:
507 x_right = end_x
508
509 scale_elements.append(Line(self.x0+x_right, tbtm, self.x0+x_right, ttop,
510 strokeColor=track.scale_color))
511 else:
512 x_right = self.xlim - self.x0
513 scale_elements.append(Line(self.x0+x_left, tctr, self.x0+x_right, tctr,
514 strokeColor=track.scale_color))
515
516 scale_elements.append(Line(self.x0+x_left, tbtm, self.x0+x_left, ttop,
517 strokeColor=track.scale_color))
518
519 start, end = self._current_track_start_end()
520 if track.scale_ticks:
521
522
523
524
525
526 ticklen = track.scale_largeticks * trackheight
527 tickiterval = int(track.scale_largetick_interval)
528
529
530
531
532 for tickpos in range(tickiterval * (self.start//tickiterval),
533 int(self.end), tickiterval):
534 if tickpos <= start or end <= tickpos:
535 continue
536 tick, label = self.draw_tick(tickpos, ctr, ticklen,
537 track,
538 track.scale_largetick_labels)
539 scale_elements.append(tick)
540 if label is not None:
541 scale_labels.append(label)
542
543 ticklen = track.scale_smallticks * trackheight
544 tickiterval = int(track.scale_smalltick_interval)
545 for tickpos in range(tickiterval * (self.start//tickiterval),
546 int(self.end), tickiterval):
547 if tickpos <= start or end <= tickpos:
548 continue
549 tick, label = self.draw_tick(tickpos, ctr, ticklen,
550 track,
551 track.scale_smalltick_labels)
552 scale_elements.append(tick)
553 if label is not None:
554 scale_labels.append(label)
555
556
557
558 if track.axis_labels:
559 for set in track.get_sets():
560 if set.__class__ is GraphSet:
561 graph_label_min = []
562 graph_label_mid = []
563 graph_label_max = []
564 for graph in set.get_graphs():
565 quartiles = graph.quartiles()
566 minval, maxval = quartiles[0], quartiles[4]
567 if graph.center is None:
568 midval = (maxval + minval)/2.
569 graph_label_min.append("%.3f" % minval)
570 graph_label_max.append("%.3f" % maxval)
571 else:
572 diff = max((graph.center-minval),
573 (maxval-graph.center))
574 minval = graph.center-diff
575 maxval = graph.center+diff
576 midval = graph.center
577 graph_label_mid.append("%.3f" % midval)
578 graph_label_min.append("%.3f" % minval)
579 graph_label_max.append("%.3f" % maxval)
580 for fragment in range(start_f, end_f+1):
581 tbtm = btm + self.fragment_lines[fragment][0]
582 tctr = ctr + self.fragment_lines[fragment][0]
583 ttop = top + self.fragment_lines[fragment][0]
584 if fragment == start_f:
585 x_left = start_x
586 else:
587 x_left = 0
588 for val, pos in [(";".join(graph_label_min), tbtm),
589 (";".join(graph_label_max), ttop),
590 (";".join(graph_label_mid), tctr)]:
591 label = String(0, 0, val,
592 fontName=track.scale_font,
593 fontSize=track.scale_fontsize,
594 fillColor=track.scale_color)
595 labelgroup = Group(label)
596 rotation = angle2trig(track.scale_fontangle)
597 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
598 rotation[3], self.x0 + x_left, pos)
599 scale_labels.append(labelgroup)
600
601 return scale_elements, scale_labels
602
604 """ draw_greytrack(self) -> ([element, element,...], [element, element,...])
605
606 o track Track object
607
608 Put in a grey background to the current track in all fragments,
609 if track specifies that we should
610 """
611 greytrack_bgs = []
612 greytrack_labels = []
613
614 if not track.greytrack:
615 return [], []
616
617
618 btm, ctr, top = self.track_offsets[self.current_track_level]
619
620 start, end = self._current_track_start_end()
621 start_fragment, start_offset = self.canvas_location(start)
622 end_fragment, end_offset = self.canvas_location(end)
623
624
625 for fragment in range(start_fragment, end_fragment+1):
626 tbtm = btm + self.fragment_lines[fragment][0]
627 tctr = ctr + self.fragment_lines[fragment][0]
628 ttop = top + self.fragment_lines[fragment][0]
629 if fragment == start_fragment:
630 x1 = self.x0 + start_offset
631 else:
632 x1 = self.x0
633 if fragment == end_fragment:
634 x2 = self.x0 + end_offset
635 else:
636 x2 = self.xlim
637 box = draw_box((x1, tbtm), (x2, ttop),
638 colors.Color(0.96,0.96, 0.96))
639 greytrack_bgs.append(box)
640
641 if track.greytrack_labels:
642 labelstep = (self.pagewidth)/track.greytrack_labels
643 label = String(0, 0, track.name,
644 fontName=track.greytrack_font,
645 fontSize=track.greytrack_fontsize,
646 fillColor=track.greytrack_fontcolor)
647
648 for x in range(int(self.x0), int(self.xlim), int(labelstep)):
649 if fragment == start_fragment and x < start_offset:
650 continue
651 if fragment == end_fragment and end_offset < x + label.getBounds()[2]:
652 continue
653 labelgroup = Group(label)
654 rotation = angle2trig(track.greytrack_font_rotation)
655 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
656 rotation[3], x, tbtm)
657 if not self.xlim-x <= labelstep:
658 greytrack_labels.append(labelgroup)
659
660 return greytrack_bgs, greytrack_labels
661
663 """ draw_feature_set(self, set) -> ([element, element,...], [element, element,...])
664
665 o set FeatureSet object
666
667 Returns a tuple (list of elements describing features, list of
668 labels for elements)
669 """
670
671 feature_elements = []
672 label_elements = []
673
674
675 for feature in set.get_features():
676 if self.is_in_bounds(feature.start) or self.is_in_bounds(feature.end):
677 features, labels = self.draw_feature(feature)
678 feature_elements += features
679 label_elements += labels
680
681 return feature_elements, label_elements
682
684 """ draw_feature(self, feature, parent_feature=None) -> ([element, element,...], [element, element,...])
685
686 o feature Feature containing location info
687
688 Returns tuple of (list of elements describing single feature, list
689 of labels for those elements)
690 """
691 if feature.hide:
692 return [], []
693
694 feature_elements = []
695 label_elements = []
696
697 start, end = self._current_track_start_end()
698
699 for locstart, locend in feature.locations:
700 if locend < start:
701 continue
702 locstart = max(locstart, start)
703 if end < locstart:
704 continue
705 locend = min(locend, end)
706 feature_boxes = self.draw_feature_location(feature, locstart, locend)
707 for box, label in feature_boxes:
708 feature_elements.append(box)
709 if label is not None:
710 label_elements.append(label)
711
712 return feature_elements, label_elements
713
715 feature_boxes = []
716
717 start_fragment, start_offset = self.canvas_location(locstart)
718 end_fragment, end_offset = self.canvas_location(locend)
719
720
721
722
723
724
725
726
727 allowed_fragments = self.fragment_limits.keys()
728 if start_fragment in allowed_fragments and end_fragment in allowed_fragments:
729
730 if start_fragment == end_fragment:
731 feature_box, label = self.get_feature_sigil(feature, start_offset,
732 end_offset, start_fragment)
733 feature_boxes.append((feature_box, label))
734
735
736
737 else:
738 fragment = start_fragment
739 start = start_offset
740
741
742 while self.fragment_limits[fragment][1] < locend:
743
744 feature_box, label = self.get_feature_sigil(feature, start,
745 self.pagewidth,
746 fragment)
747
748 fragment += 1
749 start = 0
750 feature_boxes.append((feature_box, label))
751
752
753
754
755
756
757 feature_box, label = self.get_feature_sigil(feature, 0,
758 end_offset, fragment)
759 feature_boxes.append((feature_box, label))
760
761
762 return feature_boxes
763
765 startA = cross_link.startA
766 startB = cross_link.startB
767 endA = cross_link.endA
768 endB = cross_link.endB
769
770 if not self.is_in_bounds(startA) \
771 and not self.is_in_bounds(endA):
772 return None
773 if not self.is_in_bounds(startB) \
774 and not self.is_in_bounds(endB):
775 return None
776
777 if startA < self.start:
778 startA = self.start
779 if startB < self.start:
780 startB = self.start
781 if self.end < endA:
782 endA = self.end
783 if self.end < endB:
784 endB = self.end
785
786 trackobjA = cross_link._trackA(self._parent.tracks.values())
787 trackobjB = cross_link._trackB(self._parent.tracks.values())
788 assert trackobjA is not None
789 assert trackobjB is not None
790 if trackobjA == trackobjB:
791 raise NotImplementedError()
792
793 if trackobjA.start is not None:
794 if endA < trackobjA.start:
795 return
796 startA = max(startA, trackobjA.start)
797 if trackobjA.end is not None:
798 if trackobjA.end < startA:
799 return
800 endA = min(endA, trackobjA.end)
801 if trackobjB.start is not None:
802 if endB < trackobjB.start:
803 return
804 startB = max(startB, trackobjB.start)
805 if trackobjB.end is not None:
806 if trackobjB.end < startB:
807 return
808 endB = min(endB, trackobjB.end)
809
810 for track_level in self._parent.get_drawn_levels():
811 track = self._parent[track_level]
812 if track == trackobjA:
813 trackA = track_level
814 if track == trackobjB:
815 trackB = track_level
816 if trackA == trackB:
817 raise NotImplementedError()
818
819 strokecolor, fillcolor = _stroke_and_fill_colors(cross_link.color, cross_link.border)
820
821 allowed_fragments = self.fragment_limits.keys()
822
823 start_fragmentA, start_offsetA = self.canvas_location(startA)
824 end_fragmentA, end_offsetA = self.canvas_location(endA)
825 if start_fragmentA not in allowed_fragments \
826 or end_fragmentA not in allowed_fragments:
827 return
828
829 start_fragmentB, start_offsetB = self.canvas_location(startB)
830 end_fragmentB, end_offsetB = self.canvas_location(endB)
831 if start_fragmentB not in allowed_fragments \
832 or end_fragmentB not in allowed_fragments:
833 return
834
835
836
837 answer = []
838 for fragment in range(min(start_fragmentA, start_fragmentB),
839 max(end_fragmentA, end_fragmentB)+1):
840 btmA, ctrA, topA = self.track_offsets[trackA]
841 btmA += self.fragment_lines[fragment][0]
842 ctrA += self.fragment_lines[fragment][0]
843 topA += self.fragment_lines[fragment][0]
844
845 btmB, ctrB, topB = self.track_offsets[trackB]
846 btmB += self.fragment_lines[fragment][0]
847 ctrB += self.fragment_lines[fragment][0]
848 topB += self.fragment_lines[fragment][0]
849
850 if self.fragment_limits[fragment][1] < endA:
851 xAe = self.x0 + self.pagewidth
852 crop_rightA = True
853 else:
854 xAe = self.x0 + end_offsetA
855 crop_rightA = False
856 if self.fragment_limits[fragment][1] < endB:
857 xBe = self.x0 + self.pagewidth
858 crop_rightB = True
859 else:
860 xBe = self.x0 + end_offsetB
861 crop_rightB = False
862
863 if fragment < start_fragmentA:
864 xAs = self.x0 + self.pagewidth
865 xAe = xAs
866 crop_leftA = False
867 elif fragment == start_fragmentA:
868 xAs = self.x0 + start_offsetA
869 crop_leftA = False
870 else:
871 xAs = self.x0
872 crop_leftA = True
873
874 if fragment < start_fragmentB:
875 xBs = self.x0 + self.pagewidth
876 xBe = xBs
877 crop_leftB = False
878 elif fragment == start_fragmentB:
879 xBs = self.x0 + start_offsetB
880 crop_leftB = False
881 else:
882 xBs = self.x0
883 crop_leftB = True
884
885 if ctrA < ctrB:
886 yA = topA
887 yB = btmB
888 else:
889 yA = btmA
890 yB = topB
891
892 if fragment < start_fragmentB or end_fragmentB < fragment:
893 if cross_link.flip:
894
895 if fragment < start_fragmentB:
896 extra = [self.x0 + self.pagewidth, 0.5 * (yA + yB)]
897 else:
898 extra = [self.x0 , 0.5 * (yA + yB)]
899 else:
900 if fragment < start_fragmentB:
901 extra = [self.x0 + self.pagewidth, 0.7*yA + 0.3*yB,
902 self.x0 + self.pagewidth, 0.3*yA + 0.7*yB]
903 else:
904 extra = [self.x0 , 0.3*yA + 0.7*yB,
905 self.x0 , 0.7*yA + 0.3*yB]
906 answer.append(Polygon([xAs, yA, xAe, yA] + extra,
907 strokeColor=strokecolor,
908 fillColor=fillcolor,
909
910 strokeLineJoin=1,
911 strokewidth=0))
912 elif fragment < start_fragmentA or end_fragmentA < fragment:
913 if cross_link.flip:
914
915 if fragment < start_fragmentA:
916 extra = [self.x0 + self.pagewidth, 0.5 * (yA + yB)]
917 else:
918 extra = [self.x0 , 0.5 * (yA + yB)]
919 else:
920 if fragment < start_fragmentA:
921 extra = [self.x0 + self.pagewidth, 0.3*yA + 0.7*yB,
922 self.x0 + self.pagewidth, 0.7*yA + 0.3*yB]
923 else:
924 extra = [self.x0 , 0.7*yA + 0.3*yB,
925 self.x0 , 0.3*yA + 0.7*yB]
926 answer.append(Polygon([xBs, yB, xBe, yB] + extra,
927 strokeColor=strokecolor,
928 fillColor=fillcolor,
929
930 strokeLineJoin=1,
931 strokewidth=0))
932 elif cross_link.flip and ((crop_leftA and not crop_rightA) or
933 (crop_leftB and not crop_rightB)):
934
935 answer.append(Polygon([xAs, yA, xAe, yA,
936 self.x0, 0.5 * (yA + yB),
937 xBe, yB, xBs, yB],
938 strokeColor=strokecolor,
939 fillColor=fillcolor,
940
941 strokeLineJoin=1,
942 strokewidth=0))
943 elif cross_link.flip and ((crop_rightA and not crop_leftA) or
944 (crop_rightB and not crop_leftB)):
945
946 answer.append(Polygon([xAs, yA, xAe, yA,
947 xBe, yB, xBs, yB,
948 self.x0 + self.pagewidth, 0.5 * (yA + yB)],
949 strokeColor=strokecolor,
950 fillColor=fillcolor,
951
952 strokeLineJoin=1,
953 strokewidth=0))
954 elif cross_link.flip:
955 answer.append(Polygon([xAs, yA, xAe, yA, xBs, yB, xBe, yB],
956 strokeColor=strokecolor,
957 fillColor=fillcolor,
958
959 strokeLineJoin=1,
960 strokewidth=0))
961 else:
962 answer.append(Polygon([xAs, yA, xAe, yA, xBe, yB, xBs, yB],
963 strokeColor=strokecolor,
964 fillColor=fillcolor,
965
966 strokeLineJoin=1,
967 strokewidth=0))
968 return answer
969
971 """ get_feature_sigil(self, feature, x0, x1, fragment) -> (element, element, element)
972
973 o feature Feature object
974
975 o x0 Start X co-ordinate on diagram
976
977 o x1 End X co-ordinate on diagram
978
979 o fragment The fragment on which the feature appears
980
981 Returns a drawable indicator of the feature, and any required label
982 for it
983 """
984
985 x0, x1 = self.x0 + x0, self.x0 + x1
986 btm, ctr, top = self.track_offsets[self.current_track_level]
987 try:
988 btm += self.fragment_lines[fragment][0]
989 ctr += self.fragment_lines[fragment][0]
990 top += self.fragment_lines[fragment][0]
991 except:
992 print "We've got a screw-up"
993 print self.start, self.end
994 print self.fragment_bases
995 print x0, x1
996 for locstart, locend in feature.locations:
997 print self.canvas_location(locstart)
998 print self.canvas_location(locend)
999 print 'FEATURE\n', feature
1000 1/0
1001
1002
1003 draw_methods = {'BOX': self._draw_sigil_box,
1004 'ARROW': self._draw_sigil_arrow,
1005 'BIGARROW': self._draw_sigil_big_arrow,
1006 'OCTO': self._draw_sigil_octo,
1007 'JAGGY': self._draw_sigil_jaggy,
1008 }
1009
1010 method = draw_methods[feature.sigil]
1011 kwargs['head_length_ratio'] = feature.arrowhead_length
1012 kwargs['shaft_height_ratio'] = feature.arrowshaft_height
1013
1014
1015
1016 if hasattr(feature, "url") :
1017 kwargs["hrefURL"] = feature.url
1018 kwargs["hrefTitle"] = feature.name
1019
1020
1021
1022 sigil = method(btm, ctr, top, x0, x1, strand=feature.strand,
1023 color=feature.color, border=feature.border,
1024 **kwargs)
1025 if feature.label:
1026 label = String(0, 0, feature.name,
1027 fontName=feature.label_font,
1028 fontSize=feature.label_size,
1029 fillColor=feature.label_color)
1030 labelgroup = Group(label)
1031
1032
1033 if feature.strand in (0, 1):
1034 rotation = angle2trig(feature.label_angle)
1035 if feature.label_position in ('start', "5'", 'left'):
1036 pos = x0
1037 elif feature.label_position in ('middle', 'center', 'centre'):
1038 pos = (x1 + x0)/2.
1039 else:
1040 pos = x1
1041 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
1042 rotation[3], pos, top)
1043 else:
1044 rotation = angle2trig(feature.label_angle + 180)
1045 if feature.label_position in ('start', "5'", 'left'):
1046 pos = x1
1047 elif feature.label_position in ('middle', 'center', 'centre'):
1048 pos = (x1 + x0)/2.
1049 else:
1050 pos = x0
1051 labelgroup.transform = (rotation[0], rotation[1], rotation[2],
1052 rotation[3], pos, btm)
1053 else:
1054 labelgroup = None
1055 return sigil, labelgroup
1056
1058 """ draw_graph_set(self, set) -> ([element, element,...], [element, element,...])
1059
1060 o set GraphSet object
1061
1062 Returns tuple (list of graph elements, list of graph labels)
1063 """
1064
1065 elements = []
1066
1067
1068 style_methods = {'line': self.draw_line_graph,
1069 'heat': self.draw_heat_graph,
1070 'bar': self.draw_bar_graph
1071 }
1072
1073 for graph in set.get_graphs():
1074 elements += style_methods[graph.style](graph)
1075
1076 return elements, []
1077
1079 """ draw_line_graph(self, graph) -> [element, element,...]
1080
1081 o graph Graph object
1082
1083 Returns a line graph as a list of drawable elements
1084 """
1085
1086 line_elements = []
1087
1088
1089 data_quartiles = graph.quartiles()
1090 minval, maxval = data_quartiles[0],data_quartiles[4]
1091 btm, ctr, top = self.track_offsets[self.current_track_level]
1092 trackheight = 0.5*(top-btm)
1093 datarange = maxval - minval
1094 if datarange == 0:
1095 datarange = trackheight
1096
1097 start, end = self._current_track_start_end()
1098 data = graph[start:end]
1099
1100
1101
1102 if graph.center is None:
1103 midval = (maxval + minval)/2.
1104 else:
1105 midval = graph.center
1106
1107
1108
1109 resolution = max((midval-minval), (maxval-midval))
1110
1111
1112 pos, val = data[0]
1113 lastfrag, lastx = self.canvas_location(pos)
1114 lastx += self.x0
1115 lasty = trackheight*(val-midval)/resolution + \
1116 self.fragment_lines[lastfrag][0] + ctr
1117 lastval = val
1118
1119 for pos, val in data:
1120 frag, x = self.canvas_location(pos)
1121 x += self.x0
1122 y = trackheight*(val-midval)/resolution + \
1123 self.fragment_lines[frag][0] + ctr
1124 if frag == lastfrag:
1125 line_elements.append(Line(lastx, lasty, x, y,
1126 strokeColor = graph.poscolor,
1127 strokeWidth = graph.linewidth))
1128 else:
1129 tempval = 1.*(val-lastval)/(x-lastx)
1130 tempy = trackheight*(val-midval)/resolution + \
1131 self.fragment_lines[lastfrag][0] + ctr
1132 line_elements.append(Line(lastx, lasty, self.xlim, tempy,
1133 strokeColor = graph.poscolor,
1134 strokeWidth = graph.linewidth))
1135 tempy = trackheight*(val-midval)/resolution + \
1136 self.fragment_lines[frag][0] + ctr
1137 line_elements.append(Line(self.x0, tempy, x, y,
1138 strokeColor = graph.poscolor,
1139 strokeWidth = graph.linewidth))
1140 lastfrag, lastx, lasty, lastval = frag, x, y, val
1141
1142 return line_elements
1143
1145 """ draw_heat_graph(self, graph) -> [element, element,...]
1146
1147 o graph Graph object
1148
1149 Returns a list of drawable elements for the heat graph
1150 """
1151
1152
1153
1154
1155
1156 heat_elements = []
1157
1158
1159 data_quartiles = graph.quartiles()
1160 minval, maxval = data_quartiles[0],data_quartiles[4]
1161 midval = (maxval + minval)/2.
1162 btm, ctr, top = self.track_offsets[self.current_track_level]
1163 trackheight = (top-btm)
1164
1165 start, end = self._current_track_start_end()
1166 data = intermediate_points(start, end, graph[start:end])
1167
1168 if not data:
1169 return []
1170
1171
1172
1173
1174 for pos0, pos1, val in data:
1175
1176 fragment0, x0 = self.canvas_location(pos0)
1177 fragment1, x1 = self.canvas_location(pos1)
1178 x0, x1 = self.x0 + x0, self.x0 + x1
1179
1180
1181
1182
1183 heat = colors.linearlyInterpolatedColor(graph.poscolor,
1184 graph.negcolor,
1185 maxval, minval, val)
1186
1187
1188 if fragment0 == fragment1:
1189 if pos1 >= self.fragment_limits[fragment0][1]:
1190 x1 = self.xlim
1191 ttop = top + self.fragment_lines[fragment0][0]
1192 tbtm = btm + self.fragment_lines[fragment0][0]
1193
1194
1195 heat_elements.append(draw_box((x0, tbtm), (x1, ttop),
1196 color=heat, border=None))
1197 else:
1198
1199
1200 fragment = fragment0
1201 start_x = x0
1202 while self.fragment_limits[fragment][1] <= pos1:
1203
1204 ttop = top + self.fragment_lines[fragment][0]
1205 tbtm = btm + self.fragment_lines[fragment][0]
1206 heat_elements.append(draw_box((start_x, tbtm),
1207 (self.xlim, ttop),
1208 color=heat,
1209 border=None))
1210 fragment += 1
1211 start_x = self.x0
1212 ttop = top + self.fragment_lines[fragment][0]
1213 tbtm = btm + self.fragment_lines[fragment][0]
1214
1215
1216 heat_elements.append(draw_box((self.x0, tbtm), (x1, ttop),
1217 color=heat, border=None))
1218
1219 return heat_elements
1220
1222 """ draw_bar_graph(self, graph) -> [element, element,...]
1223
1224 o graph Graph object
1225
1226 Returns a list of drawable elements for a bar graph of the passed
1227 Graph object
1228 """
1229
1230
1231
1232
1233
1234 bar_elements = []
1235
1236
1237 data_quartiles = graph.quartiles()
1238 minval, maxval = data_quartiles[0],data_quartiles[4]
1239 btm, ctr, top = self.track_offsets[self.current_track_level]
1240 trackheight = 0.5*(top-btm)
1241 datarange = maxval - minval
1242 if datarange == 0:
1243 datarange = trackheight
1244 data = graph[self.start:self.end]
1245
1246
1247 if graph.center is None:
1248 midval = (maxval + minval)/2.
1249 else:
1250 midval = graph.center
1251
1252
1253
1254
1255 start, end = self._current_track_start_end()
1256 data = intermediate_points(start, end, graph[start:end])
1257
1258 if not data:
1259 return []
1260
1261
1262
1263
1264 resolution = max((midval-minval), (maxval-midval))
1265 if resolution == 0:
1266 resolution = trackheight
1267
1268
1269 for pos0, pos1, val in data:
1270 fragment0, x0 = self.canvas_location(pos0)
1271 fragment1, x1 = self.canvas_location(pos1)
1272 x0, x1 = self.x0 + x0, self.x0 + x1
1273 barval = trackheight*(val-midval)/resolution
1274 if barval >=0:
1275 barcolor = graph.poscolor
1276 else:
1277 barcolor = graph.negcolor
1278
1279
1280 if fragment0 == fragment1:
1281 if pos1 >= self.fragment_limits[fragment0][1]:
1282 x1 = self.xlim
1283 tctr = ctr + self.fragment_lines[fragment0][0]
1284 barval += tctr
1285 bar_elements.append(draw_box((x0, tctr), (x1, barval),
1286 color=barcolor))
1287 else:
1288 fragment = fragment0
1289
1290
1291 start = x0
1292 while self.fragment_limits[fragment][1] < pos1:
1293 tctr = ctr + self.fragment_lines[fragment][0]
1294 thisbarval = barval + tctr
1295 bar_elements.append(draw_box((start, tctr),
1296 (self.xlim, thisbarval),
1297 color=barcolor))
1298 fragment += 1
1299 start = self.x0
1300 tctr = ctr + self.fragment_lines[fragment1][0]
1301 barval += tctr
1302
1303 bar_elements.append(draw_box((self.x0, tctr), (x1, barval),
1304 color=barcolor))
1305
1306 return bar_elements
1307
1309 """ canvas_location(self, base) -> (int, float)
1310
1311 o base The base number on the genome sequence
1312
1313 Returns the x-coordinate and fragment number of a base on the
1314 genome sequence, in the context of the current drawing setup
1315 """
1316 base = int(base - self.start)
1317 fragment = int(base / self.fragment_bases)
1318 if fragment < 1:
1319 base_offset = base
1320 fragment = 0
1321 elif fragment >= self.fragments:
1322 fragment = self.fragments-1
1323 base_offset = self.fragment_bases
1324 else:
1325 base_offset = base % self.fragment_bases
1326 assert fragment < self.fragments, (base, self.start, self.end, self.length, self.fragment_bases)
1327
1328 x_offset = 1. * self.pagewidth * base_offset / self.fragment_bases
1329 return fragment, x_offset
1330
1331 - def _draw_sigil_box(self, bottom, center, top, x1, x2, strand, **kwargs):
1332 """Draw BOX sigil."""
1333 if strand == 1:
1334 y1 = center
1335 y2 = top
1336 elif strand == -1:
1337 y1 = bottom
1338 y2 = center
1339 else:
1340 y1 = bottom
1341 y2 = top
1342 return draw_box((x1,y1), (x2,y2), **kwargs)
1343
1345 """Draw OCTO sigil, a box with the corners cut off."""
1346 if strand == 1:
1347 y1 = center
1348 y2 = top
1349 elif strand == -1:
1350 y1 = bottom
1351 y2 = center
1352 else:
1353 y1 = bottom
1354 y2 = top
1355 return draw_cut_corner_box((x1,y1), (x2,y2), **kwargs)
1356
1357 - def _draw_sigil_jaggy(self, bottom, center, top, x1, x2, strand,
1358 color, border=None, **kwargs):
1359 """Draw JAGGY sigil.
1360
1361 Although we may in future expose the head/tail jaggy lengths, for now
1362 both the left and right edges are drawn jagged.
1363 """
1364 if strand == 1:
1365 y1 = center
1366 y2 = top
1367 teeth = 2
1368 elif strand == -1:
1369 y1 = bottom
1370 y2 = center
1371 teeth = 2
1372 else:
1373 y1 = bottom
1374 y2 = top
1375 teeth = 4
1376
1377 xmin = min(x1, x2)
1378 xmax = max(x1, x2)
1379 height = y2 - y1
1380 boxwidth = x2 - x1
1381 tooth_length = min(height/teeth, boxwidth*0.5)
1382
1383 headlength = tooth_length
1384 taillength = tooth_length
1385
1386 strokecolor, color = _stroke_and_fill_colors(color, border)
1387
1388 points = []
1389 for i in range(teeth):
1390 points.extend((xmin, y1+i*height/teeth,
1391 xmin+taillength, y1+(i+1)*height/teeth))
1392 for i in range(teeth):
1393 points.extend((xmax, y1+(teeth-i)*height/teeth,
1394 xmax-headlength, y1+(teeth-i-1)*height/teeth))
1395
1396 return Polygon(points,
1397 strokeColor=strokecolor,
1398 strokeWidth=1,
1399 strokeLineJoin=1,
1400 fillColor=color,
1401 **kwargs)
1402
1404 """Draw ARROW sigil."""
1405 if strand == 1:
1406 y1 = center
1407 y2 = top
1408 orientation = "right"
1409 elif strand== -1:
1410 y1 = bottom
1411 y2 = center
1412 orientation = "left"
1413 else:
1414 y1 = bottom
1415 y2 = top
1416 orientation = "right"
1417 return draw_arrow((x1,y1), (x2,y2), orientation=orientation, **kwargs)
1418
1420 """Draw BIGARROW sigil, like ARROW but straddles the axis."""
1421 if strand == -1:
1422 orientation = "left"
1423 else:
1424 orientation = "right"
1425 return draw_arrow((x1,bottom), (x2,top), orientation=orientation, **kwargs)
1426