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 -def page_sizes(size):
54 """ page_sizes(size)
55
56 o size A string representing a standard page size
57
58 Returns a ReportLab pagesize when passed a valid size string
59 """
60 sizes = {'A0': pagesizes.A0,
61 'A1': pagesizes.A1,
62 'A2': pagesizes.A2,
63 'A3': pagesizes.A3,
64 'A4': pagesizes.A4,
65 'A5': pagesizes.A5,
66 'A6': pagesizes.A6,
67 'B0': pagesizes.B0,
68 'B1': pagesizes.B1,
69 'B2': pagesizes.B2,
70 'B3': pagesizes.B3,
71 'B4': pagesizes.B4,
72 'B5': pagesizes.B5,
73 'B6': pagesizes.B6,
74 'ELEVENSEVENTEEN': pagesizes.ELEVENSEVENTEEN,
75 'LEGAL': pagesizes.LEGAL,
76 'LETTER': pagesizes.LETTER
77 }
78 try:
79 return sizes[size]
80 except:
81 raise ValueError, "%s not in list of page sizes" % size
82
83
84 -def draw_box(point1, point2,
85 color=colors.lightgreen, border=None, colour=None,
86 **kwargs):
87 """ draw_box(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4),
88 color=colors.lightgreen)
89
90 o point1, point2 Co-ordinates for opposite corners of the box
91 (x,y tuples)
92
93 o color /colour The color for the box
94 (colour takes priority over color)
95
96 o border Border color for the box
97
98 Returns a closed path object, beginning at (x1,y1) going round
99 the four points in order, and filling with the passed color.
100 """
101 x1, y1 = point1
102 x2, y2 = point2
103
104
105 if colour is not None:
106 color = colour
107 del colour
108
109 if not isinstance(color, colors.Color):
110 raise ValueError("Invalid color %s" % repr(color))
111
112 if color == colors.white and border is None:
113 strokecolor = colors.black
114 elif border is None:
115 strokecolor = color
116 elif border is not None:
117 if not isinstance(border, colors.Color):
118 raise ValueError("Invalid border color %s" % repr(border))
119 strokecolor = border
120
121 x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)
122 return Polygon([x1, y1, x2, y1, x2, y2, x1, y2],
123 strokeColor=strokecolor,
124 fillColor=color,
125 strokewidth=0,
126 **kwargs)
127
128
129 -def draw_polygon(list_of_points,
130 color=colors.lightgreen, border=None, colour=None,
131 **kwargs):
132 """ draw_polygon(self, (x1, y1), (x2, y2), (x3, y3), (x4, y4)
133 colour=colors.lightgreen)
134
135 o list_of_point = list of (x,y) tuples for the corner coordinates
136
137 o colour The colour for the box
138
139 Returns a closed path object, beginning at (x1,y1) going round
140 the four points in order, and filling with the passed colour.
141 """
142
143 if colour is not None:
144 color = colour
145 del colour
146
147 if color == colors.white and border is None:
148 strokecolor = colors.black
149 elif border is None:
150 strokecolor = color
151 elif border is not None:
152 strokecolor = border
153
154 xy_list = []
155 for (x,y) in list_of_points:
156 xy_list.append(x)
157 xy_list.append(y)
158
159 return Polygon(xy_list,
160 strokeColor=strokecolor,
161 fillColor=color,
162 strokewidth=0,
163 **kwargs)
164
165
166 -def draw_arrow(point1, point2, color=colors.lightgreen, border=None,
167 shaft_height_ratio=0.4, head_length_ratio=0.5, orientation='right',
168 colour=None, **kwargs):
169 """ Returns a closed path object representing an arrow enclosed by the
170 box with corners at {point1=(x1,y1), point2=(x2,y2)}, a shaft height
171 given by shaft_height_ratio (relative to box height), a head length
172 given by head_length_ratio (also relative to box height), and
173 an orientation that may be 'left' or 'right'.
174 """
175 x1, y1 = point1
176 x2, y2 = point2
177
178 if shaft_height_ratio < 0 or 1 < shaft_height_ratio:
179 raise ValueError("Arrow shaft height ratio should be in range 0 to 1")
180 if head_length_ratio < 0:
181 raise ValueError("Arrow head length ratio should be positive")
182
183
184 if colour is not None:
185 color = colour
186 del colour
187
188 if color == colors.white and border is None:
189 strokecolor = colors.black
190 elif border is None:
191 strokecolor = color
192 elif border is not None:
193 strokecolor = border
194
195
196
197
198 xmin, ymin = min(x1, x2), min(y1, y2)
199 xmax, ymax = max(x1, x2), max(y1, y2)
200 if orientation == 'right':
201 x1, x2, y1, y2 = xmin, xmax, ymin, ymax
202 elif orientation == 'left':
203 x1, x2, y1, y2 = xmax, xmin, ymin, ymax
204 else:
205 raise ValueError("Invalid orientation %s, should be 'left' or 'right'" \
206 % repr(orientation))
207
208
209
210
211 boxheight = y2-y1
212 boxwidth = x2-x1
213 shaftheight = boxheight*shaft_height_ratio
214 headlength = min(abs(boxheight)*head_length_ratio, abs(boxwidth))
215 if boxwidth < 0:
216 headlength *= -1
217
218
219 shafttop = 0.5*(boxheight+shaftheight)
220 shaftbase = boxheight-shafttop
221 headbase = boxwidth-headlength
222 midheight = 0.5*boxheight
223 return Polygon([x1, y1+shafttop,
224 x1+headbase, y1+shafttop,
225 x1+headbase, y2,
226 x2, y1+midheight,
227 x1+headbase, y1,
228 x1+headbase, y1+shaftbase,
229 x1, y1+shaftbase],
230 strokeColor=strokecolor,
231
232 strokeWidth=1,
233
234 strokeLineJoin=1,
235 fillColor=color,
236 **kwargs)
237
239 """ angle2trig(angle)
240
241 o theta Angle in degrees, counter clockwise from horizontal
242
243 Returns a representation of the passed angle in a format suitable
244 for ReportLab rotations (i.e. cos(theta), sin(theta), -sin(theta),
245 cos(theta) tuple)
246 """
247 c = cos(theta * pi / 180)
248 s = sin(theta * pi / 180)
249 return(c, s, -s, c)
250
251
282
283
284
285
286
288 """ AbstractDrawer
289
290 Provides:
291
292 Methods:
293
294 o __init__(self, parent, pagesize='A3', orientation='landscape',
295 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
296 start=None, end=None, tracklines=0) Called on instantiation
297
298 o set_page_size(self, pagesize, orientation) Set the page size to the
299 passed size and orientation
300
301 o set_margins(self, x, y, xl, xr, yt, yb) Set the drawable area of the
302 page
303
304 o set_bounds(self, start, end) Set the bounds for the elements to be
305 drawn
306
307 o is_in_bounds(self, value) Returns a boolean for whether the position
308 is actually to be drawn
309
310 o __len__(self) Returns the length of sequence that will be drawn
311
312 Attributes:
313
314 o tracklines Boolean for whether to draw lines dilineating tracks
315
316 o pagesize Tuple describing the size of the page in pixels
317
318 o x0 Float X co-ord for leftmost point of drawable area
319
320 o xlim Float X co-ord for rightmost point of drawable area
321
322 o y0 Float Y co-ord for lowest point of drawable area
323
324 o ylim Float Y co-ord for topmost point of drawable area
325
326 o pagewidth Float pixel width of drawable area
327
328 o pageheight Float pixel height of drawable area
329
330 o xcenter Float X co-ord of center of drawable area
331
332 o ycenter Float Y co-ord of center of drawable area
333
334 o start Int, base to start drawing from
335
336 o end Int, base to stop drawing at
337
338 o length Size of sequence to be drawn
339
340 """
341 - def __init__(self, parent, pagesize='A3', orientation='landscape',
342 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
343 start=None, end=None, tracklines=0):
344 """ __init__(self, parent, pagesize='A3', orientation='landscape',
345 x=0.05, y=0.05, xl=None, xr=None, yt=None, yb=None,
346 start=None, end=None, tracklines=0)
347
348 o parent Diagram object containing the data that the drawer
349 draws
350
351 o pagesize String describing the ISO size of the image, or a tuple
352 of pixels
353
354 o orientation String describing the required orientation of the
355 final drawing ('landscape' or 'portrait')
356
357 o x Float (0->1) describing the relative size of the X
358 margins to the page
359
360 o y Float (0->1) describing the relative size of the Y
361 margins to the page
362
363 o xl Float (0->1) describing the relative size of the left X
364 margin to the page (overrides x)
365
366 o xl Float (0->1) describing the relative size of the left X
367 margin to the page (overrides x)
368
369 o xr Float (0->1) describing the relative size of the right X
370 margin to the page (overrides x)
371
372 o yt Float (0->1) describing the relative size of the top Y
373 margin to the page (overrides y)
374
375 o yb Float (0->1) describing the relative size of the lower Y
376 margin to the page (overrides y)
377
378 o start Int, the position to begin drawing the diagram at
379
380 o end Int, the position to stop drawing the diagram at
381
382 o tracklines Boolean flag to show (or not) lines delineating tracks
383 on the diagram
384 """
385 self._parent = parent
386
387
388 self.set_page_size(pagesize, orientation)
389 self.set_margins(x, y, xl, xr, yt, yb)
390 self.set_bounds(start, end)
391 self.tracklines = tracklines
392
394 import warnings
395 import Bio
396 warnings.warn("The _set_xcentre method and .xcentre attribute are deprecated; please use the .xcenter attribute instead", Bio.BiopythonDeprecationWarning)
397 self.xcenter = value
398 xcentre = property(fget = lambda self : self.xcenter,
399 fset = _set_xcentre,
400 doc="Backwards compatible alias for xcenter (DEPRECATED)")
401
403 import warnings
404 import Bio
405 warnings.warn("The _set_ycentre method and .xcentre attribute are deprecated; please use the .ycenter attribute instead", Bio.BiopythonDeprecationWarning)
406 self.ycenter = value
407 ycentre = property(fget = lambda self : self.ycenter,
408 fset = _set_ycentre,
409 doc="Backwards compatible alias for ycenter (DEPRECATED)")
410
411 - def set_page_size(self, pagesize, orientation):
412 """ set_page_size(self, pagesize, orientation)
413
414 o pagesize Size of the output image, a tuple of pixels (width,
415 height, or a string in the reportlab.lib.pagesizes
416 set of ISO sizes.
417
418 o orientation String: 'landscape' or 'portrait'
419
420 Set the size of the drawing
421 """
422 if type(pagesize) == type('a'):
423 pagesize = page_sizes(pagesize)
424 elif type(pagesize) == type((1,2)):
425 pagesize = pagesize
426 else:
427 raise ValueError, "Page size %s not recognised" % pagesize
428 shortside, longside = min(pagesize), max(pagesize)
429
430 orientation = orientation.lower()
431 if orientation not in ('landscape', 'portrait'):
432 raise ValueError, "Orientation %s not recognised" % orientation
433 if orientation == 'landscape':
434 self.pagesize = (longside, shortside)
435 else:
436 self.pagesize = (shortside, longside)
437
438
440 """ set_margins(self, x, y, xl, xr, yt, yb)
441
442 o x Float(0->1), Absolute X margin as % of page
443
444 o y Float(0->1), Absolute Y margin as % of page
445
446 o xl Float(0->1), Left X margin as % of page
447
448 o xr Float(0->1), Right X margin as % of page
449
450 o yt Float(0->1), Top Y margin as % of page
451
452 o yb Float(0->1), Bottom Y margin as % of page
453
454 Set the page margins as proportions of the page 0->1, and also
455 set the page limits x0, y0 and xlim, ylim, and page center
456 xorigin, yorigin, as well as overall page width and height
457 """
458
459 xmargin_l = xl or x
460 xmargin_r = xr or x
461 ymargin_top = yt or y
462 ymargin_btm = yb or y
463
464
465 self.x0, self.y0 = self.pagesize[0]*xmargin_l, self.pagesize[1]*ymargin_btm
466 self.xlim, self.ylim = self.pagesize[0]*(1-xmargin_r), self.pagesize[1]*(1-ymargin_top)
467 self.pagewidth = self.xlim-self.x0
468 self.pageheight = self.ylim-self.y0
469 self.xcenter, self.ycenter = self.x0+self.pagewidth/2., self.y0+self.pageheight/2.
470
471
473 """ set_bounds(self, start, end)
474
475 o start The first base (or feature mark) to draw from
476
477 o end The last base (or feature mark) to draw to
478
479 Sets start and end points for the drawing as a whole
480 """
481 low, high = self._parent.range()
482
483 if start is not None and end is not None and start > end:
484 start, end = end, start
485
486 if start is None or start < 1:
487 start = 1
488 if end is None or end < 1:
489 end = high + 1
490
491 self.start, self.end = int(start), int(end)
492 self.length = self.end - self.start + 1
493
494
496 """ is_in_bounds(self, value)
497
498 o value A base position
499
500 Returns 1 if the value is within the region selected for drawing
501 """
502 if value >= self.start and value <= self.end:
503 return 1
504 return 0
505
506
508 """ __len__(self)
509
510 Returns the length of the region to be drawn
511 """
512 return self.length
513