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