1 """
2 PolPlot2D is a panel for 4 cross-section 2D polarized reflectometry data.
3 """
4
5 import wx,numpy,os
6 from math import log10, pow
7
8 import matplotlib as mpl
9 mpl.interactive(False)
10
11 mpl.use('WXAgg')
12
13 from copy import deepcopy
14 import matplotlib.cm
15 import matplotlib.colors
16
17 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
18 from matplotlib.backend_bases import LocationEvent
19 from matplotlib.font_manager import FontProperties
20 from matplotlib.colors import Normalize,LogNorm
21 from cmapmenu import CMapMenu
22
23 import ticker
24
25
26 -def _rescale(lo,hi,step,pt=None,bal=None,scale='linear'):
27 """
28 Rescale (lo,hi) by step, returning the new (lo,hi)
29 The scaling is centered on pt, with positive values of step
30 driving lo/hi away from pt and negative values pulling them in.
31 If bal is given instead of point, it is already in [0,1] coordinates.
32
33 This is a helper function for step-based zooming.
34 """
35
36
37 if scale=='log':
38 lo,hi = log10(lo),log10(hi)
39 if pt is not None: pt = log10(pt)
40
41
42 if step > 0:
43 delta = float(hi-lo)*step/100
44 else:
45 delta = float(hi-lo)*step/(100-step)
46
47
48
49 if bal is None:
50 bal = float(pt-lo)/(hi-lo)
51 lo = lo - bal*delta
52 hi = hi + (1-bal)*delta
53
54
55 if scale=='log':
56 lo,hi = pow(10.,lo),pow(10.,hi)
57
58 return (lo,hi)
59
61 """
62 Signal an error condition -- in a GUI, popup a error dialog
63 """
64
65
66 dialog =wx.MessageDialog(parent = parent,
67 message = msg,
68 caption = 'Polplot error',
69 style=wx.OK | wx.CENTRE)
70 dialog.ShowModal()
71 dialog.Destroy()
72 return None
73
74
76 """
77 Given a canvas and a a base filename, construct a file save
78 dialog to save the canvas.
79 """
80
81
82
83 filetypes, exts, filter_index = canvas._get_imagesave_wildcards()
84 default_file = filebase + "." + canvas.get_default_filetype()
85 dlg = wx.FileDialog(canvas, "Save to file", "", default_file, filetypes,
86 wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
87 dlg.SetFilterIndex(filter_index)
88 if dlg.ShowModal() == wx.ID_OK:
89 dirname = dlg.GetDirectory()
90 filename = dlg.GetFilename()
91
92 format = exts[dlg.GetFilterIndex()]
93
94
95 try:
96 canvas.print_figure(os.path.join(dirname, filename), format=format)
97 except Exception, e:
98 error_msg(str(e))
99 return
100
102 """
103 Return a Bbox that contains all of the given bboxes.
104 """
105 from matplotlib.transforms import Bbox
106 if len(bboxes) == 1:
107 return bboxes[0]
108
109 x0 = numpy.inf
110 y0 = numpy.inf
111 x1 = -numpy.inf
112 y1 = -numpy.inf
113
114 for bbox in bboxes:
115 xs = bbox.intervalx
116 ys = bbox.intervaly
117 x0 = min(x0, numpy.min(xs))
118 y0 = min(y0, numpy.min(ys))
119 x1 = max(x1, numpy.max(xs))
120 y1 = max(y1, numpy.max(ys))
121
122 return Bbox([[x0, y0], [x1, y1]])
123
124
126 - def __init__(self, parent, id = -1, dpi = None, **kwargs):
127 wx.Panel.__init__(self, parent, id=id, **kwargs)
128 self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
129 self.canvas = Canvas(self, -1, self.figure)
130 sizer = wx.BoxSizer(wx.VERTICAL)
131 sizer.Add(self.canvas,1,wx.EXPAND)
132 self.SetSizer(sizer)
133
135 """
136 The PlotPanel has a Figure and a Canvas. OnSize events simply set a
137 flag, and the actually redrawing of the
138 figure is triggered by an Idle event.
139 """
140 - def __init__(self, parent, id = -1, dpi = None, color=None, **kwargs):
141
142
143
144
145 wx.Panel.__init__(self, parent, id = id, **kwargs)
146 self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
147
148
149 self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
150
151 self.canvas = Canvas(self, -1, self.figure)
152 self.canvas.mpl_connect('key_press_event',self.onKeyPress)
153 self.canvas.mpl_connect('button_press_event',self.onButtonPress)
154 self.canvas.mpl_connect('scroll_event',self.onWheel)
155
156 sizer = wx.BoxSizer(wx.VERTICAL)
157 sizer.Add(self.canvas,1,wx.EXPAND)
158 self.SetSizer(sizer)
159
160
161
162
163
164
165
166
167 self.pp = self.figure.add_subplot(221)
168 self.mm = self.figure.add_subplot(222,sharex=self.pp,sharey=self.pp)
169 self.pm = self.figure.add_subplot(223,sharex=self.pp,sharey=self.pp)
170 self.mp = self.figure.add_subplot(224,sharex=self.pp,sharey=self.pp)
171 self.figure.subplots_adjust(left=.1, bottom=.1, top=.9, right=0.85,
172 wspace=0.0, hspace=0.0)
173
174 self.axes = [self.pp, self.mm, self.pm, self.mp]
175 self.grid = True
176
177
178
179 self.coloraxes = self.figure.add_axes([0.88, 0.2, 0.04, 0.6])
180 self.colormapper = mpl.image.FigureImage(self.figure)
181 self.colormapper.set_array(numpy.ones(1))
182 self.colorbar = self.figure.colorbar(self.colormapper,self.coloraxes)
183
184
185 self.tbox = self.figure.text(0.5, 0.95, '',
186 horizontalalignment='center',
187 fontproperties=FontProperties(size=16))
188 self.xbox = self.figure.text(0.5,0.01,'',
189 verticalalignment='bottom',
190 horizontalalignment='center',
191 rotation='horizontal')
192 self.ybox = self.figure.text(0.01,0.5,'',
193 horizontalalignment='left',
194 verticalalignment='center',
195 rotation='vertical')
196 self.zbox = self.figure.text(0.99,0.5,'',
197 horizontalalignment='right',
198 verticalalignment='center',
199 rotation='vertical')
200
201 self.xscale = 'linear'
202 self.yscale = 'linear'
203 self.zscale = 'linear'
204 self.set_vscale('log')
205
206
207 self.clear()
208
209 self._sets = {}
210
212 return
213 bbox = bbox_union([ax.dataLim for ax in self.axes])
214 xlims = bbox.intervalx
215 ylims = bbox.intervaly
216 self.pp.axis(list(xlims)+list(ylims))
217
219
220
221 if event.key == 'a':
222 setattr(event,'step',5.)
223 self.onWheel(event)
224 elif event.key == 'z':
225 setattr(event,'step',-5.)
226 self.onWheel(event)
227
237
239 """
240 Process mouse wheel as zoom events
241 """
242 ax = event.inaxes
243 step = event.step
244
245
246 if ax == self.coloraxes:
247
248
249 lo,hi = self.colormapper.get_clim()
250 lo,hi = _rescale(lo,hi,step,bal=event.ydata,scale=self.vscale)
251 self.colormapper.set_clim(lo,hi)
252 elif ax != None:
253
254 lo,hi = ax.get_xlim()
255 lo,hi = _rescale(lo,hi,step,pt=event.xdata)
256 ax.set_xlim((lo,hi))
257
258 lo,hi = ax.get_ylim()
259 lo,hi = _rescale(lo,hi,step,pt=event.ydata)
260 ax.set_ylim((lo,hi))
261 else:
262
263 xdata,ydata = None,None
264 x,y = event.x,event.y
265 for ax in self.axes:
266 insidex,_ = ax.xaxis.contains(event)
267 if insidex:
268 xdata,_ = ax.transAxes.inverse_xy_tup((x,y))
269
270 insidey,_ = ax.yaxis.contains(event)
271 if insidey:
272 _,ydata = ax.transAxes.inverse_xy_tup((x,y))
273
274 if xdata is not None:
275 lo,hi = ax.get_xlim()
276 lo,hi = _rescale(lo,hi,step,bal=xdata)
277 ax.set_xlim((lo,hi))
278 if ydata is not None:
279 lo,hi = ax.get_ylim()
280 lo,hi = _rescale(lo,hi,step,bal=ydata)
281 ax.set_ylim((lo,hi))
282
283 self.canvas.draw_idle()
284
285
286
287
288
292
295
299
302
304 """Alternate between log and linear colormap"""
305 if scale == 'linear':
306 vmapper = Normalize(*self.colormapper.get_clim())
307 vformat = mpl.ticker.ScalarFormatter()
308 vlocate = mpl.ticker.AutoLocator()
309 else:
310 vmapper = LogNorm(*self.colormapper.get_clim())
311 vformat = mpl.ticker.LogFormatterMathtext(base=10,labelOnlyBase=False)
312 vlocate = mpl.ticker.LogLocator(base=10)
313 self.colormapper.set_norm(vmapper)
314 self.colorbar.formatter = vformat
315 self.colorbar.locator = vlocate
316
317 self.vscale = scale
318
321
322
328
329
331 self.grid = not self.grid
332 for ax in self.axes: ax.grid(alpha=0.4,visible=self.grid)
333 self.draw()
334
336 """
337 Default context menu for a plot panel
338 """
339
340
341
342 x,y = event.GetPosition()
343 self.menuevent = LocationEvent("context",self.canvas,
344 x,y,guiEvent=event)
345
346 popup = wx.Menu()
347 item = popup.Append(wx.ID_ANY,'&Save image', 'Save image as PNG')
348 wx.EVT_MENU(self, item.GetId(), self.onSaveImage)
349 item = popup.Append(wx.ID_ANY,'&Grid on/off', 'Toggle grid lines')
350 wx.EVT_MENU(self, item.GetId(), self.onGridToggle)
351 cmaps = CMapMenu(self, mapper=self.colormapper, canvas=self.canvas)
352 item = popup.AppendMenu(wx.ID_ANY, "Colourmaps", cmaps)
353
354
355
356 pos = event.GetPosition()
357 pos = self.ScreenToClient(pos)
358 self.PopupMenu(popup, pos)
359
360
361
363 """Set some properties of the graph.
364
365 The set of properties is not yet determined.
366 """
367
368
369
370
371
372
373
374
375 self.xbox.set_text(r"$%s$" % prop["xlabel"])
376 self.ybox.set_text(r"$%s$" % prop["ylabel"])
377 self.vbox.set_text(r"$%s$" % prop["vlabel"])
378 self.tbox.set_text(r"$%s$" % prop["title"])
379
380
381
382
384 """Reset the plot"""
385
386
387
388
389 for ax in self.axes:
390 ax.clear()
391 ax.hold(True)
392
393
394 props = dict(va='center',ha='left',
395 bbox=dict(facecolor='yellow',alpha=0.67))
396 self.pp.text( 0.05, 0.9, '$++$', props, transform=self.pp.transAxes)
397 self.pm.text( 0.05, 0.9, '$+-$', props, transform=self.pm.transAxes)
398 self.mp.text( 0.05, 0.9, '$-+$', props, transform=self.mp.transAxes)
399 self.mm.text( 0.05, 0.9, '$--$', props, transform=self.mm.transAxes)
400
401
402
403 self.vmin, self.vmax = numpy.inf,-numpy.inf
404 self.colormapper.set_clim(vmin=1,vmax=10)
405
406
407 for l in self.pp.get_xticklabels(): l.set_visible(False)
408 for l in self.mm.get_xticklabels(): l.set_visible(False)
409 for l in self.mm.get_yticklabels(): l.set_visible(False)
410 for l in self.mp.get_yticklabels(): l.set_visible(False)
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431 for ax in self.axes: ax.grid(alpha=0.4,visible=self.grid)
432 pass
433
434 - def xaxis(self,label,units):
435 """xaxis label and units.
436
437 Axis labels know about units.
438
439 We need to do this so that we can detect when axes are not
440 commesurate. Currently this is ignored other than for formatting
441 purposes.
442 """
443 if units != "": label = label + " (" + units + ")"
444 self.xbox.set_text(r"$%s$" % (label))
445 pass
446
447 - def yaxis(self,label,units):
448 """yaxis label and units."""
449 if units != "": label = label + " (" + units + ")"
450 self.ybox.set_text(r"$%s$" % (label))
451 pass
452
453 - def vaxis(self,label,units):
454 """vaxis label and units."""
455 if units != "": label = label + " (" + units + ")"
456 self.vbox.set_text(r"$%s$" % (label))
457 pass
458
460 for slice,data in [('++',poldata.pp),('--',poldata.mm),
461 ('+-',poldata.pm),('-+',poldata.mp)]:
462 self.surface(slice,data)
463 pass
464
466 if slice == '++': ax = self.pp
467 elif slice == '+-': ax = self.pm
468 elif slice == '-+': ax = self.mp
469 elif slice == '--': ax = self.mm
470 else: raise ValueError, "expected polarization crosssection"
471
472
473
474
475
476
477 x,y,v = data.xedges,data.yedges,data.v+1
478 try:
479 im = ax.pcolorfast(x,y,v)
480 except AttributeError:
481 im = ax.pcolormesh(x,y,v,shading='flat')
482
483 def on_changed(m):
484 print "changed",m
485 self.colorbar.set_cmap(m.get_cmap())
486 self.colorbar.set_clim(m.get_clim())
487 self.colorbar.update_bruteforce(m)
488 im.set_cmap(m.get_cmap())
489 im.set_clim(m.get_clim())
490 self.canvas.draw_idle()
491 self.colormapper.callbacksSM.connect('changed',on_changed)
492
493 self.vmin = min(self.vmin, numpy.min(v))
494 self.vmax = max(self.vmax, numpy.max(v))
495 self.colormapper.set_clim(vmin=self.vmin,vmax=self.vmax)
496 self.autoaxes()
497 self.canvas.draw_idle()
498
499 return im
500
501
503 import data
504
505 d = data.peakspol(n=355)
506
507
508
509 app = wx.PySimpleApp()
510 frame = wx.Frame(None,-1,'Plottables')
511 plotter = Plotter4(frame)
512 frame.Show()
513
514
515 plotter.clear()
516 plotter.set_vscale('linear')
517 plotter.surfacepol(d)
518
519 app.MainLoop()
520 pass
521
522 if __name__ == "__main__": demo()
523