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