1
2
3
4
5
6
7
8
9
10
11
12 """ AbstractDrawer module (considered to be a private module, the API may change!)
13
14 Provides:
15
16 o AbstractDrawer - Superclass for methods common to *Drawer objects
17
18 o page_sizes - Method that returns a ReportLab pagesize when passed
19 a valid ISO size
20
21 o draw_box - Method that returns a closed path object when passed
22 the proper co-ordinates. For HORIZONTAL boxes only.
23
24 o angle2trig - Method that returns a tuple of values that are the
25 vector for rotating a point through a passed angle,
26 about an origin
27
28 o intermediate_points - Method that returns a list of values intermediate
29 between the points in a passed dataset
30
31 For drawing capabilities, this module uses reportlab to draw and write
32 the diagram:
33
34 http://www.reportlab.com
35
36 For dealing with biological information, the package expects BioPython
37 objects:
38
39 http://www.biopython.org
40 """
41
42
43 from reportlab.lib import pagesizes
44 from reportlab.lib import colors
45 from reportlab.graphics.shapes import *
46
47 from math import pi
48
49
50
51
52
53
54 -def page_sizes(size):
55 """ page_sizes(size)
56
57 o size A string representing a standard page size
58
59 Returns a ReportLab pagesize when passed a valid size string
60 """
61 sizes = {'A0': pagesizes.A0,
62 'A1': pagesizes.A1,
63 'A2': pagesizes.A2,
64 'A3': pagesizes.A3,
65 'A4': pagesizes.A4,
66 'A5': pagesizes.A5,
67 'A6': pagesizes.A6,
68 'B0': pagesizes.B0,
69 'B1': pagesizes.B1,
70 'B2': pagesizes.B2,
71 'B3': pagesizes.B3,
72 'B4': pagesizes.B4,
73 'B5': pagesizes.B5,
74 'B6': pagesizes.B6,
75 'ELEVENSEVENTEEN': pagesizes.ELEVENSEVENTEEN,
76 'LEGAL': pagesizes.LEGAL,
77 'LETTER': pagesizes.LETTER
78 }
79 try:
80 return sizes[size]
81 except:
82 raise ValueError("%s not in list of page sizes" % size)
83
86 """Helper function handle border and fill colors (PRIVATE)."""
87 if not isinstance(color, colors.Color):
88 raise ValueError("Invalid color %r" % color)
89
90 if color == colors.white and border is None:
91 strokecolor = colors.black
92 elif border is None:
93 strokecolor = color
94 elif border:
95 if not isinstance(border, colors.Color):
96 raise ValueError("Invalid border color %r" % border)
97 strokecolor = border
98 else:
99
100 strokecolor = None
101
102 return strokecolor, color
103
104
105 -def draw_box(point1, point2,
106 color=colors.lightgreen, border=None, colour=None,
107 **kwargs):
108 """ draw_box(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4),
109 color=colors.lightgreen)
110
111 o point1, point2 Co-ordinates for opposite corners of the box
112 (x,y tuples)
113
114 o color /colour The color for the box
115 (colour takes priority over color)
116
117 o border Border color for the box
118
119 Returns a closed path object, beginning at (x1,y1) going round
120 the four points in order, and filling with the passed color.
121 """
122 x1, y1 = point1
123 x2, y2 = point2
124
125
126 if colour is not None:
127 color = colour
128 del colour
129
130 strokecolor, color = _stroke_and_fill_colors(color, border)
131
132 x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)
133 return Polygon([x1, y1, x2, y1, x2, y2, x1, y2],
134 strokeColor=strokecolor,
135 fillColor=color,
136 strokewidth=0,
137 **kwargs)
138
139
140 -def draw_cut_corner_box(point1, point2, corner=0.5,
141 color=colors.lightgreen, border=None, **kwargs):
142 """Draw a box with the corners cut off."""
143 x1, y1 = point1
144 x2, y2 = point2
145
146 if not corner:
147 return draw_box(point1, point2, color, border)
148 elif corner < 0:
149 raise ValueError("Arrow head length ratio should be positive")
150
151 strokecolor, color = _stroke_and_fill_colors(color, border)
152
153 boxheight = y2-y1
154 boxwidth = x2-x1
155 corner = min(boxheight*0.5, boxheight*0.5*corner)
156
157 return Polygon([x1, y1+corner,
158 x1, y2-corner,
159 x1+corner, y2,
160 x2-corner, y2,
161 x2, y2-corner,
162 x2, y1+corner,
163 x2-corner, y1,
164 x1+corner, y1],
165 strokeColor=strokecolor,
166 strokeWidth=1,
167 strokeLineJoin=1,
168 fillColor=color,
169 **kwargs)
170
171
172 -def draw_polygon(list_of_points,
173 color=colors.lightgreen, border=None, colour=None,
174 **kwargs):
175 """ draw_polygon(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4)
176 colour=colors.lightgreen)
177
178 o list_of_point = list of (x,y) tuples for the corner coordinates
179
180 o colour The colour for the box
181
182 Returns a closed path object, beginning at (x1,y1) going round
183 the four points in order, and filling with the passed colour.
184 """
185
186 if colour is not None:
187 color = colour
188 del colour
189
190 strokecolor, color = _stroke_and_fill_colors(color, border)
191
192 xy_list = []
193 for (x,y) in list_of_points:
194 xy_list.append(x)
195 xy_list.append(y)
196
197 return Polygon(xy_list,
198 strokeColor=strokecolor,
199 fillColor=color,
200 strokewidth=0,
201 **kwargs)
202
203
204 -def draw_arrow(point1, point2, color=colors.lightgreen, border=None,
205 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right',
206 colour=None, **kwargs):
207 """ Returns a closed path object representing an arrow enclosed by the
208 box with corners at {point1=(x1,y1), point2=(x2,y2)}, a shaft height
209 given by shaft_height_ratio (relative to box height), a head length
210 given by head_length_ratio (also relative to box height), and
211 an orientation that may be 'left' or 'right'.
212 """
213 x1, y1 = point1
214 x2, y2 = point2
215
216 if shaft_height_ratio < 0 or 1 < shaft_height_ratio:
217 raise ValueError("Arrow shaft height ratio should be in range 0 to 1")
218 if head_length_ratio < 0:
219 raise ValueError("Arrow head length ratio should be positive")
220
221
222 if colour is not None:
223 color = colour
224 del colour
225
226 strokecolor, color = _stroke_and_fill_colors(color, border)
227
228
229
230
231 xmin, ymin = min(x1, x2), min(y1, y2)
232 xmax, ymax = max(x1, x2), max(y1, y2)
233 if orientation == 'right':
234 x1, x2, y1, y2 = xmin, xmax, ymin, ymax
235 elif orientation == 'left':
236 x1, x2, y1, y2 = xmax, xmin, ymin, ymax
237 else:
238 raise ValueError("Invalid orientation %s, should be 'left' or 'right'"
239 % repr(orientation))
240
241
242
243
244 boxheight = y2-y1
245 boxwidth = x2-x1
246 shaftheight = boxheight*shaft_height_ratio
247 headlength = min(abs(boxheight)*head_length_ratio, abs(boxwidth))
248 if boxwidth < 0:
249 headlength *= -1
250
251 shafttop = 0.5*(boxheight+shaftheight)
252 shaftbase = boxheight-shafttop
253 headbase = boxwidth-headlength
254 midheight = 0.5*boxheight
255 return Polygon([x1, y1+shafttop,
256 x1+headbase, y1+shafttop,
257 x1+headbase, y2,
258 x2, y1+midheight,
259 x1+headbase, y1,
260 x1+headbase, y1+shaftbase,
261 x1, y1+shaftbase],
262 strokeColor=strokecolor,
263
264 strokeWidth=1,
265
266 strokeLineJoin=1,
267 fillColor=color,
268 **kwargs)
269
272 """ angle2trig(angle)
273
274 o theta Angle in degrees, counter clockwise from horizontal
275
276 Returns a representation of the passed angle in a format suitable
277 for ReportLab rotations (i.e. cos(theta), sin(theta), -sin(theta),
278 cos(theta) tuple)
279 """
280 c = cos(theta * pi / 180)
281 s = sin(theta * pi / 180)
282 return(c, s, -s, c)
283
315
322 """ AbstractDrawer
323
324 Provides:
325
326 Methods:
327
328 o __init__(self, parent, pagesize='A3', orientation='landscape',
329 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
330 start=None, end=None, tracklines=0) Called on instantiation
331
332 o set_page_size(self, pagesize, orientation) Set the page size to the
333 passed size and orientation
334
335 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the
336 page
337
338 o set_bounds(self, start, end) Set the bounds for the elements to be
339 drawn
340
341 o is_in_bounds(self, value) Returns a boolean for whether the position
342 is actually to be drawn
343
344 o __len__(self) Returns the length of sequence that will be drawn
345
346 Attributes:
347
348 o tracklines Boolean for whether to draw lines dilineating tracks
349
350 o pagesize Tuple describing the size of the page in pixels
351
352 o x0 Float X co-ord for leftmost point of drawable area
353
354 o xlim Float X co-ord for rightmost point of drawable area
355
356 o y0 Float Y co-ord for lowest point of drawable area
357
358 o ylim Float Y co-ord for topmost point of drawable area
359
360 o pagewidth Float pixel width of drawable area
361
362 o pageheight Float pixel height of drawable area
363
364 o xcenter Float X co-ord of center of drawable area
365
366 o ycenter Float Y co-ord of center of drawable area
367
368 o start Int, base to start drawing from
369
370 o end Int, base to stop drawing at
371
372 o length Size of sequence to be drawn
373
374 o cross_track_links List of tuples each with four entries (track A,
375 feature A, track B, feature B) to be linked.
376 """
377 - def __init__(self, parent, pagesize='A3', orientation='landscape',
378 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
379 start=None, end=None, tracklines=0, cross_track_links=None):
380 """ __init__(self, parent, pagesize='A3', orientation='landscape',
381 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
382 start=None, end=None, tracklines=0)
383
384 o parent Diagram object containing the data that the drawer
385 draws
386
387 o pagesize String describing the ISO size of the image, or a tuple
388 of pixels
389
390 o orientation String describing the required orientation of the
391 final drawing ('landscape' or 'portrait')
392
393 o x Float (0->1) describing the relative size of the X
394 margins to the page
395
396 o y Float (0->1) describing the relative size of the Y
397 margins to the page
398
399 o xl Float (0->1) describing the relative size of the left X
400 margin to the page (overrides x)
401
402 o xl Float (0->1) describing the relative size of the left X
403 margin to the page (overrides x)
404
405 o xr Float (0->1) describing the relative size of the right X
406 margin to the page (overrides x)
407
408 o yt Float (0->1) describing the relative size of the top Y
409 margin to the page (overrides y)
410
411 o yb Float (0->1) describing the relative size of the lower Y
412 margin to the page (overrides y)
413
414 o start Int, the position to begin drawing the diagram at
415
416 o end Int, the position to stop drawing the diagram at
417
418 o tracklines Boolean flag to show (or not) lines delineating tracks
419 on the diagram
420
421 o cross_track_links List of tuples each with four entries (track A,
422 feature A, track B, feature B) to be linked.
423 """
424 self._parent = parent
425
426
427 self.set_page_size(pagesize, orientation)
428 self.set_margins(x, y, xl, xr, yt, yb)
429 self.set_bounds(start, end)
430 self.tracklines = tracklines
431 if cross_track_links is None:
432 cross_track_links = []
433 else:
434 self.cross_track_links = cross_track_links
435
436 @property
438 """Backwards compatible alias for xcenter (DEPRECATED)"""
439 warnings.warn("The .xcentre attribute is deprecated, use .xcenter instead",
440 Bio.BiopythonDeprecationWarning)
441 return self.xcenter
442
443 @property
445 """Backwards compatible alias for ycenter (DEPRECATED)"""
446 warnings.warn("The .ycentre attribute is deprecated, use .ycenter instead",
447 Bio.BiopythonDeprecationWarning)
448 return self.ycenter
449
450 - def set_page_size(self, pagesize, orientation):
451 """ set_page_size(self, pagesize, orientation)
452
453 o pagesize Size of the output image, a tuple of pixels (width,
454 height, or a string in the reportlab.lib.pagesizes
455 set of ISO sizes.
456
457 o orientation String: 'landscape' or 'portrait'
458
459 Set the size of the drawing
460 """
461 if isinstance(pagesize, str):
462 pagesize = page_sizes(pagesize)
463 elif isinstance(pagesize, tuple):
464 pagesize = pagesize
465 else:
466 raise ValueError("Page size %s not recognised" % pagesize)
467 shortside, longside = min(pagesize), max(pagesize)
468
469 orientation = orientation.lower()
470 if orientation not in ('landscape', 'portrait'):
471 raise ValueError("Orientation %s not recognised" % orientation)
472 if orientation == 'landscape':
473 self.pagesize = (longside, shortside)
474 else:
475 self.pagesize = (shortside, longside)
476
478 """ set_margins(self, x, y, xl, xr, yt, yb)
479
480 o x Float(0->1), Absolute X margin as % of page
481
482 o y Float(0->1), Absolute Y margin as % of page
483
484 o xl Float(0->1), Left X margin as % of page
485
486 o xr Float(0->1), Right X margin as % of page
487
488 o yt Float(0->1), Top Y margin as % of page
489
490 o yb Float(0->1), Bottom Y margin as % of page
491
492 Set the page margins as proportions of the page 0->1, and also
493 set the page limits x0, y0 and xlim, ylim, and page center
494 xorigin, yorigin, as well as overall page width and height
495 """
496
497 xmargin_l = xl or x
498 xmargin_r = xr or x
499 ymargin_top = yt or y
500 ymargin_btm = yb or y
501
502
503 self.x0, self.y0 = self.pagesize[0]*xmargin_l, self.pagesize[1]*ymargin_btm
504 self.xlim, self.ylim = self.pagesize[0]*(1-xmargin_r), self.pagesize[1]*(1-ymargin_top)
505 self.pagewidth = self.xlim-self.x0
506 self.pageheight = self.ylim-self.y0
507 self.xcenter, self.ycenter = self.x0+self.pagewidth/2., self.y0+self.pageheight/2.
508
510 """ set_bounds(self, start, end)
511
512 o start The first base (or feature mark) to draw from
513
514 o end The last base (or feature mark) to draw to
515
516 Sets start and end points for the drawing as a whole
517 """
518 low, high = self._parent.range()
519
520 if start is not None and end is not None and start > end:
521 start, end = end, start
522
523 if start is None or start < 0:
524 start = 0
525 if end is None or end < 0:
526 end = high + 1
527
528 self.start, self.end = int(start), int(end)
529 self.length = self.end - self.start + 1
530
532 """ is_in_bounds(self, value)
533
534 o value A base position
535
536 Returns 1 if the value is within the region selected for drawing
537 """
538 if value >= self.start and value <= self.end:
539 return 1
540 return 0
541
543 """ __len__(self)
544
545 Returns the length of the region to be drawn
546 """
547 return self.length
548
560