1 '''
2 A very simple 2D plotting package that outputs images.
3
4 @author: bolme
5 '''
6
7 import pyvision as pv
8 import numpy as np
9 import os.path
10 import PIL.ImageFont
11 import PIL.ImageDraw
12 random = np.random
13 import StringIO
14 import sys
15
16 arial_path = os.path.join(pv.__path__[0],'config','Arial.ttf')
17 huge_font = PIL.ImageFont.truetype(arial_path, 36)
18 large_font = PIL.ImageFont.truetype(arial_path, 18)
19 small_font = PIL.ImageFont.truetype(arial_path, 12)
20
21
22 -def drawLabel(plot_image, pt, label, size='small',align='center',rotate=False,color='black'):
23 '''
24 @param plot_image: a PIL image to use as a plot_image
25 '''
26 font = small_font
27 if size == 'large':
28 font = large_font
29 elif size == "huge":
30 font = huge_font
31
32
33 size = font.getsize(label)
34 im = PIL.Image.new("L",size,'black')
35 draw = PIL.ImageDraw.Draw(im)
36 draw.text((0,0),label,fill='white',font=font)
37 del draw
38
39
40 if rotate:
41 im = im.transpose(PIL.Image.ROTATE_90)
42
43
44 x,y = pt
45 w,h = im.size
46 if align == 'center':
47 x = x - w/2
48 y = y - h/2
49 elif align == 'left':
50 x = x - w
51 y = y - h/2
52 elif align == 'right':
53 x = x
54 y = y - h/2
55 elif align == 'above':
56 x = x - w/2
57 y = y - h
58 elif align == 'bellow':
59 x = x - w/2
60 y = y
61 else:
62 raise ValueError("Unknown alignment: %s"%align)
63 draw = PIL.ImageDraw.Draw(plot_image)
64 draw.bitmap((x,y),im,fill=color)
65 del draw
66
67
68
83
85 - def __init__(self,point,label,size='small',align='center',rotate=False,color='black'):
92
93 - def draw(self,plot,plot_image,bounds):
94 x,y = self.point
95 x = plot.x(x,bounds)
96 y = plot.y(y,bounds)
97 drawLabel(plot_image,[x,y],self.label,size=self.size,align=self.align,rotate=self.rotate,color=self.color)
98
100 sys.stderr.write("WARNING> Plot draw label is not implemented for R.\n")
101
106
107
108
110 - def __init__(self,points,graphic_type,color='black',shape=0,size=3,label=None,lty=None,width=1):
120
121
122 - def draw(self,plot,plot_image,bounds):
123 ''''''
124 points = [ (plot.x(x,bounds),plot.y(y,bounds)) for x,y in self.points]
125
126 if self.graphic_type == 'lines':
127 self.drawCurve(points,plot_image)
128 elif self.graphic_type == 'points':
129 self.drawPoints(points,plot_image)
130 elif self.graphic_type == 'polygon':
131 self.drawPolygon(points,plot_image)
132 else:
133 raise ValueError("unknown graphic_type: %s"%(self.graphic_type,))
134
136 ''''''
137 xpoints = [ float(x) for x,y in self.points]
138 ypoints = [ float(y) for x,y in self.points]
139
140 f.write("xpoints=c(%s)\n"%(dataToFormatedList(xpoints),))
141 f.write("\n")
142 f.write("ypoints=c(%s)\n"%(dataToFormatedList(ypoints),))
143 f.write("\n")
144
145 if self.graphic_type == 'lines':
146 f.write("lines(xpoints,ypoints,lty=%s,col='%s',lwd='%s')"%(self.lty,self.color,self.width))
147 elif self.graphic_type == 'points':
148 f.write("points(xpoints,ypoints,col='%s',pch=%s,cex=%s,lwd=%s)"%(self.color,repr(self.shape),self.size/3.0,self.width))
149 elif self.graphic_type == 'polygon':
150 xpoints.append(xpoints[0])
151 ypoints.append(ypoints[0])
152 f.write("lines(xpoints,ypoints,lty=%s,col='%s',lwd='%s')"%(self.lty,self.color,self.width))
153 else:
154 raise ValueError("unknown graphic_type: %s"%(self.graphic_type,))
155
157 draw = PIL.ImageDraw.Draw(plot_image)
158 prev_x,prev_y = points[0]
159 for x,y in points[1:]:
160 draw.line([prev_x,prev_y,x,y],fill=self.color,width=self.width)
161 prev_x = x
162 prev_y = y
163 del draw
164
171
173 ''' Render points on the plot '''
174 shape = self.shape
175 size = self.size
176 color = self.color
177
178 draw = PIL.ImageDraw.Draw(plot_image)
179
180 for i in range(len(points)):
181 x,y = points[i]
182
183 if isinstance(shape,int) and shape >= 0:
184 shape = (shape) % 21
185
186 if shape == '.':
187
188 draw.point((x,y),fill=color)
189 elif shape == 0:
190
191 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None)
192 elif shape == 1:
193
194 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=None)
195 elif shape == 2:
196
197 draw.polygon((x,y-size,x-size,y+size,x+size,y+size,x,y-size),outline=color,fill=None)
198 elif shape == 3:
199
200 draw.line((x-size,y,x+size,y),fill=color)
201 draw.line((x,y-size,x,y+size),fill=color)
202 elif shape == 4:
203
204 draw.line((x-size,y-size,x+size,y+size),fill=color)
205 draw.line((x-size,y+size,x+size,y-size),fill=color)
206 elif shape == 5:
207
208 draw.polygon((x-size,y,x,y-size,x+size,y,x,y+size,x-size,y),outline=color,fill=None)
209 elif shape == 6:
210
211 draw.polygon((x,y+size,x-size,y-size,x+size,y-size,x,y+size),outline=color,fill=None)
212 elif shape == 7:
213
214 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None)
215 draw.line((x-size,y-size,x+size,y+size),fill=color)
216 draw.line((x-size,y+size,x+size,y-size),fill=color)
217 elif shape == 8:
218
219 draw.line((x-size,y,x+size,y),fill=color)
220 draw.line((x,y-size,x,y+size),fill=color)
221 draw.line((x-size,y-size,x+size,y+size),fill=color)
222 draw.line((x-size,y+size,x+size,y-size),fill=color)
223 elif shape == 9:
224
225 draw.polygon((x-size,y,x,y-size,x+size,y,x,y+size,x-size,y),outline=color,fill=None)
226 draw.line((x-size,y,x+size,y),fill=color)
227 draw.line((x,y-size,x,y+size),fill=color)
228 elif shape == 10:
229
230 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=None)
231 draw.line((x-size,y,x+size,y),fill=color)
232 draw.line((x,y-size,x,y+size),fill=color)
233 elif shape == 11:
234
235 draw.polygon((x,y-size,x-size,y+0.707*size,x+size,y+0.707*size,x,y-size),outline=color,fill=None)
236 draw.polygon((x,y+size,x-size,y-0.707*size,x+size,y-0.707*size,x,y+size),outline=color,fill=None)
237 elif shape == 12:
238
239 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None)
240 draw.line((x-size,y,x+size,y),fill=color)
241 draw.line((x,y-size,x,y+size),fill=color)
242 elif shape == 13:
243
244 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=None)
245 draw.line((x-size,y-size,x+size,y+size),fill=color)
246 draw.line((x-size,y+size,x+size,y-size),fill=color)
247 elif shape == 14:
248
249 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None)
250 draw.polygon((x,y-size,x-size,y+size,x+size,y+size,x,y-size),outline=color,fill=None)
251 elif shape == 15:
252
253 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=color)
254 elif shape == 16:
255
256 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=color)
257 elif shape == 17:
258
259 draw.polygon((x,y-size,x-size,y+size,x+size,y+size,x,y-size),outline=color,fill=color)
260 elif shape == 18:
261
262 draw.polygon((x-size,y,x,y-size,x+size,y,x,y+size,x-size,y),outline=color,fill=color)
263 elif shape == 19:
264
265 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=color)
266 elif shape == 20:
267
268 draw.ellipse((x-0.707*size,y-0.707*size,x+0.707*size,y+0.707*size),outline=color,fill=color)
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 elif type(shape) == str:
286
287 if size < 6:
288 drawLabel(plot_image, (x,y), shape, size='small',align='center',rotate=False,color=color)
289 else:
290 drawLabel(plot_image, (x,y), shape, size='large',align='center',rotate=False,color=color)
291 else:
292
293 draw.point((x,y),fill=color)
294
295 del draw
296
298 points = self.points
299
300 points = np.array(points)
301
302
303 xmin,xmax,ymin,ymax = points[0][0],points[0][0],points[0][1],points[0][1]
304
305 for x,y in self.points:
306
307 xmin = np.min([xmin,x])
308 xmax = np.max([xmax,x])
309 ymin = np.min([ymin,y])
310 ymax = np.max([ymax,y])
311
312 return xmin,xmax,ymin,ymax
313
314
315
316
318 - def __init__(self,size=(600,600),x_range=None,y_range=None,title="No Title",ylabel="Y Axis",xlabel="X Axis",pad=True):
319
320 self.size = size
321 self.title = title
322 self.xlabel = xlabel
323 self.ylabel = ylabel
324 self.x_at = None
325 self.x_labels = None
326
327 self.x_range = x_range
328 if x_range != None:
329 self.x_range = (np.min(x_range),np.max(x_range))
330
331 self.y_range = y_range
332 if y_range != None:
333 self.y_range = (np.min(y_range),np.max(y_range))
334
335 self.top = 60
336 self.left = 60
337 self.bottom = 60
338 self.right = 30
339
340 self.pad = pad
341
342 self.graphics = []
343
344
345
346
348 w,h = self.size
349
350 fw = self.left+w+self.right
351 fh = self.top+h+self.bottom
352
353 pil = PIL.Image.new("RGB",(fw,fh),'white')
354
355 drawLabel(pil,(0.5*fw,0.5*self.top),self.title,align='center',size='huge')
356 drawLabel(pil,(5,self.top+0.5*h),self.ylabel,align='right',size='large',rotate=True)
357 drawLabel(pil,(self.left+0.5*w,fh-5),self.xlabel,align='above',size='large',rotate=False)
358
359 tmp_image = PIL.Image.new("RGB",(w,h),'white')
360 bounds = self.range()
361 for each in self.graphics:
362 each.draw(self,tmp_image,bounds)
363
364 pil.paste(tmp_image,(self.left,self.top))
365
366 draw = PIL.ImageDraw.Draw(pil)
367 draw.rectangle((self.left,self.top,self.left+w,self.top+h),outline='black',fill=None)
368 del draw
369
370
371 at,labels = self.xAxis()
372
373
374 draw = PIL.ImageDraw.Draw(pil)
375 for i in range(len(at)):
376 x = self.left+self.x(at[i],bounds)
377 y = self.top+h
378 draw.line((x,y,x,y+5),fill='black')
379 del draw
380
381
382 for i in range(len(at)):
383 x = self.left+self.x(at[i],bounds)
384 y = self.top+h
385 drawLabel(pil, (x,y+5), labels[i], size='large',align='bellow',rotate=False,color='black')
386
387
388 at,labels = self.yAxis()
389
390
391 draw = PIL.ImageDraw.Draw(pil)
392 for i in range(len(at)):
393 x = self.left
394 y = self.top+self.y(at[i],bounds)
395 draw.line((x,y,x-5,y),fill='black')
396 del draw
397
398
399 for i in range(len(at)):
400 x = self.left
401 y = self.top+self.y(at[i],bounds)
402 drawLabel(pil, (x-5,y), labels[i], size='large',align='left',rotate=True,color='black')
403
404 return pv.Image(pil)
405
407 at = np.array(at,np.float64)
408 if labels != None:
409 assert len(at) == len(labels)
410 labels = np.array(labels,np.str)
411 else:
412 labels = np.array(at,np.str)
413 self.x_at = at
414 self.x_labels = labels
415
416 - def x(self,x,bounds):
417 minx,maxx,_,_ = bounds
418 w,_ = self.size
419 return w*(x-minx)/float(maxx-minx)
420
421
422 - def y(self,y,bounds):
423 _,_,miny,maxy = bounds
424 _,h = self.size
425 return h-h*(y-miny)/float(maxy-miny)
426
428 xmin,xmax,ymin,ymax = -1.0,1.0,-1.0,1.0
429 if len(self.graphics) > 0:
430 xmin,xmax,ymin,ymax = self.graphics[0].range()
431 for each in self.graphics:
432 t1,t2,t3,t4 = each.range()
433 xmin = np.min([xmin,t1])
434 xmax = np.max([xmax,t2])
435 ymin = np.min([ymin,t3])
436 ymax = np.max([ymax,t4])
437
438
439 if self.x_range != None:
440 xmin,xmax = self.x_range
441
442
443 if self.y_range != None:
444 ymin,ymax = self.y_range
445
446
447
448 if self.pad:
449 rg = xmax-xmin
450 xmin -= 0.05*rg
451 xmax += 0.05*rg
452
453 rg = ymax-ymin
454 ymin -= 0.05*rg
455 ymax += 0.05*rg
456
457 return xmin,xmax,ymin,ymax
458
459
461 minx,maxx,_,_ = self.range()
462 w,_ = self.size
463 if self.x_at == None:
464 at,labels = self.autoAxis(minx,maxx,w)
465 else:
466 at,labels = self.x_at,self.x_labels
467 return at,labels
468
469
471 _,_,miny,maxy = self.range()
472 _,h = self.size
473 at,labels = self.autoAxis(miny,maxy,h)
474 return at,labels
475
477 split = (0.1,0.2,0.5,1,2,5)
478 if high == low:
479 high = high+1
480 low = low -1
481 rg = high - low
482 l10 = np.log(rg)/np.log(10)
483 f10 = np.floor(l10)
484 best_at = []
485 best_count = 500000000000000000000000
486 target_count = size/100
487 scale = 10**f10
488 low = low/scale
489 high = high/scale
490
491 for inc in split:
492 tmp = np.arange(np.floor(low),high+0.001,inc)
493 idx = (tmp >= low) & (tmp <= high)
494 tmp = tmp[idx]
495 count = len(tmp)
496 if np.abs(count-target_count) < np.abs(target_count - best_count):
497 best_count = count
498 best_at = tmp*scale
499
500 if f10 >= 1.0:
501 label_format = "%0.0f"
502 labels = [label_format%x for x in best_at]
503 else:
504 label_format = "%0." + "%d"%(-int(f10-1)) + "f"
505 labels = [label_format%x for x in best_at]
506
507 return best_at,labels
508
510 try:
511 tmp = []
512 for pt in points:
513 tmp.append((pt.X(),pt.Y()))
514 return tmp
515 except:
516 try:
517 tmp = []
518 for pt in points:
519
520 tmp.append((float(pt[0]),float(pt[1])))
521 return tmp
522 except:
523 raise
524
525
526 - def label(self,point,label,**kwargs):
530
531 - def points(self,points,color='black',shape=0,size=3,label=None,lty=None,width=1):
538
539 - def point(self,point,color='black',shape=0,size=3,label=None,lty=None,width=1):
544
545 - def lines(self,points,color='black',shape=None,size=3,label=None,lty=1,width=1):
552
553 - def polygon(self,points,color='black',shape=None,size=3,label=None,lty=1,width=1):
560
561 - def show(self,**kwargs):
563
564 - def asR(self,plot_pdf=os.getcwd()+"/out.pdf"):
565 '''
566 Generate an R script that will reproduce this plot.
567 '''
568
569 f = StringIO.StringIO()
570
571
572 f.write("# This is an R script that will generate a plot.\n")
573 f.write("# These first few lines are configuration options.\n")
574 f.write("filename='%s';\n"%plot_pdf)
575 f.write("plot_width=6; # width in inches\n")
576 f.write("plot_height=6; # height in inches\n")
577 f.write("title='%s'\n"%self.title)
578 f.write("xlabel='%s'\n"%self.xlabel)
579 f.write("ylabel='%s'\n"%self.ylabel)
580 f.write("xrange=c(%f,%f)\n"%self.range()[:2])
581 f.write("yrange=c(%f,%f)\n"%self.range()[2:])
582 f.write("\n")
583 f.write("\n")
584 f.write("\n")
585 f.write("\n")
586
587
588 f.write("pdf(file=filename,width=plot_width,height=plot_height)\n")
589 f.write("\n")
590 f.write("par(mai=c(0.5,0.5,0.5,0.1)) # minimal inner margin\n")
591 f.write("par(mgp=c(1.5,0.5,0.0)) # small axes label spacing\n")
592 f.write("\n")
593 f.write("plot(xrange,yrange,type='n',main=title,xlab=xlabel,ylab=ylabel)\n")
594 f.write("# Log scale...\n")
595 f.write("# Custom axis...\n")
596 f.write("\n")
597
598
599 f.write("\n")
600 f.write("# axis(1,at=c(1,3,4),labels =c(1,3,4)) # xaxis. Also add xaxt='n' to the plot command.\n")
601 f.write("# axis(2,at=c(1,3,4),labels =c(1,3,4)) # yaxis. Also add yaxt='n' to the plot command.\n")
602 f.write("# legend('topright',c('Label 1','Label 2')),fill=c('blue','green'))\n")
603 f.write("\n")
604
605
606 for each in self.graphics:
607 f.write("# DRAWING: %s\n"%(each.__class__))
608 each.drawR(f)
609 f.write("\n")
610
611
612
613 f.write("dev.off()\n")
614 f.write("\n")
615 f.write("\n")
616
617 f.flush()
618
619 p_in,p_out = os.popen2("R --no-save")
620 p_in.write(f.getvalue())
621 p_in.close()
622 print p_out.read()
623 p_out.close()
624
625 return f.getvalue()
626
627
628
629 import unittest
630
632
634 "Plot: Auto Axis"
635 plot = Plot()
636 plot.autoAxis(0.0,10.0,600)
637 plot.autoAxis(-1.0,100.0,600)
638 plot.autoAxis(-1.0,1.0,600)
639 plot.autoAxis(-10.0,20.,600)
640 plot.autoAxis(200.0,500.,600)
641 plot.autoAxis(.210,.500,600)
642
647
652
653
667