Coverage for larch/wxxas/xydata_panel.py: 0%
390 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1#!/usr/bin/env python
2"""
3 uncategorized XY data Panel
4"""
5import time
6import wx
7import numpy as np
9from functools import partial
10from xraydb import guess_edge, atomic_number
12from larch.utils import gformat, path_split
13from larch.math import index_of
14from larch.xafs.xafsutils import guess_energy_units
16from larch.wxlib import (BitmapButton, FloatCtrl, FloatSpin, get_icon,
17 SimpleText, pack, Button, HLine, Choice, Check,
18 GridPanel, CEN, RIGHT, LEFT, plotlabels,
19 get_zoomlimits, set_zoomlimits)
21from larch.utils.strutils import fix_varname, fix_filename, file2groupname
23from larch.utils.physical_constants import ATOM_NAMES
24from larch.wxlib.plotter import last_cursor_pos
25from .taskpanel import TaskPanel, autoset_fs_increment, update_confval
26from .config import (make_array_choice, EDGES, ATSYMS,
27 NNORM_CHOICES, NNORM_STRINGS, NORM_METHODS)
29np.seterr(all='ignore')
31PLOTOPTS_1 = dict(style='solid', marker='None')
32PLOTOPTS_2 = dict(style='short dashed', zorder=3, marker='None')
33PLOTOPTS_D = dict(style='solid', zorder=2, side='right', marker='None')
35# Plot_EnergyRanges = {'full X range': None }
37PlotOne_Choices = {'XY Data': 'y',
38 'Scaled Data': 'ynorm',
39 'Derivative ': 'dydx',
40 'XY Data + Derivative': 'y+dydx',
41 'Scaled Data + Derivative': 'ynorm+dydx',
42 }
44PlotSel_Choices = {'XY Data': 'y',
45 'Scaled Data': 'ynorm',
46 'Derivative': 'dydx'}
48FSIZE = 120
49FSIZEBIG = 175
52class XYDataPanel(TaskPanel):
53 """XY Data Panel"""
54 def __init__(self, parent, controller=None, **kws):
55 TaskPanel.__init__(self, parent, controller, panel='xydata', **kws)
57 def build_display(self):
58 panel = self.panel
59 self.wids = {}
60 self.last_plot_type = 'one'
62 trow = wx.Panel(panel)
63 plot_sel = Button(trow, 'Plot Selected Groups', size=(175, -1),
64 action=self.onPlotSel)
65 plot_one = Button(trow, 'Plot Current Group', size=(175, -1),
66 action=self.onPlotOne)
68 self.plotsel_op = Choice(trow, choices=list(PlotSel_Choices.keys()),
69 action=self.onPlotSel, size=(300, -1))
70 self.plotone_op = Choice(trow, choices=list(PlotOne_Choices.keys()),
71 action=self.onPlotOne, size=(300, -1))
73 opts = {'digits': 2, 'increment': 0.05, 'value': 0, 'size': (FSIZE, -1)}
74 plot_voff = self.add_floatspin('plot_voff', with_pin=False,
75 parent=trow,
76 action=self.onVoffset,
77 max_val=10000, min_val=-10000,
78 **opts)
80 vysize, vxsize = plot_sel.GetBestSize()
81 voff_lab = wx.StaticText(parent=trow, label=' Y Offset:', size=(80, vxsize),
82 style=wx.RIGHT|wx.ALIGN_CENTRE_HORIZONTAL|wx.ST_NO_AUTORESIZE)
84 self.plotone_op.SetSelection(0)
85 self.plotsel_op.SetSelection(1)
87 tsizer = wx.GridBagSizer(3, 3)
88 tsizer.Add(plot_sel, (0, 0), (1, 1), LEFT, 2)
89 tsizer.Add(self.plotsel_op, (0, 1), (1, 1), LEFT, 2)
90 tsizer.Add(voff_lab, (0, 2), (1, 1), RIGHT, 2)
91 tsizer.Add(plot_voff, (0, 3), (1, 1), RIGHT, 2)
92 tsizer.Add(plot_one, (1, 0), (1, 1), LEFT, 2)
93 tsizer.Add(self.plotone_op, (1, 1), (1, 1), LEFT, 2)
95 pack(trow, tsizer)
97 scale = self.add_floatspin('scale', action=self.onSet_Scale,
98 digits=6, increment=0.05, value=1.0,
99 size=(FSIZEBIG, -1))
101 xshift = self.add_floatspin('xshift', action=self.onSet_XShift,
102 digits=6, increment=0.05, value=0.0,
103 size=(FSIZEBIG, -1))
105 self.wids['is_frozen'] = Check(panel, default=False, label='Freeze Group',
106 action=self.onFreezeGroup)
108 use_auto = Button(panel, 'Use Default Settings', size=(200, -1),
109 action=self.onUseDefaults)
111 def CopyBtn(name):
112 return Button(panel, 'Copy', size=(60, -1),
113 action=partial(self.onCopyParam, name))
115 copy_all = Button(panel, 'Copy All Parameters', size=(200, -1),
116 action=partial(self.onCopyParam, 'all'))
118 add_text = self.add_text
119 HLINEWID = 700
121 panel.Add(SimpleText(panel, 'XY Data, General',
122 size=(650, -1), **self.titleopts), style=LEFT, dcol=4)
123 panel.Add(trow, dcol=4, newrow=True)
125 panel.Add(HLine(panel, size=(HLINEWID, 3)), dcol=4, newrow=True)
127 add_text('XY Data:')
128 panel.Add(use_auto, dcol=1)
129 panel.Add(SimpleText(panel, 'Copy to Selected Groups:'), style=RIGHT, dcol=2)
131 add_text('Scale Factor:')
132 panel.Add(scale)
133 panel.Add(SimpleText(panel, 'Scaled Data = Y /(Scale Factor)'))
134 panel.Add(CopyBtn('scale'), dcol=1, style=RIGHT)
136 add_text('Shift X scale:' )
137 panel.Add(xshift)
138 panel.Add(CopyBtn('xshift'), dcol=2, style=RIGHT)
140 panel.Add(HLine(panel, size=(HLINEWID, 3)), dcol=4, newrow=True)
141 panel.Add(self.wids['is_frozen'], newrow=True)
142 panel.Add(copy_all, dcol=3, style=RIGHT)
144 panel.pack()
146 sizer = wx.BoxSizer(wx.VERTICAL)
147 sizer.Add((5, 5), 0, LEFT, 3)
148 sizer.Add(panel, 0, LEFT, 3)
149 sizer.Add((5, 5), 0, LEFT, 3)
150 pack(self, sizer)
152 def get_config(self, dgroup=None):
153 """custom get_config"""
154 if dgroup is None:
155 dgroup = self.controller.get_group()
156 if dgroup is None:
157 return self.get_defaultconfig()
158 self.read_form()
160 defconf = self.get_defaultconfig()
161 conf = getattr(dgroup.config, self.configname, defconf)
163 for k, v in defconf.items():
164 if k not in conf:
165 conf[k] = v
167 fname = getattr(dgroup, 'filename', None)
168 if fname is None:
169 fname = getattr(dgroup, 'groupname', None)
170 if fname is None:
171 fname =file2groupname('unknown_group',
172 symtable=self._larch.symtable)
174 for attr in ('scale', 'xshift'):
175 conf[attr] = getattr(dgroup, attr, conf[attr])
177 setattr(dgroup.config, self.configname, conf)
178 return conf
180 def fill_form(self, dgroup):
181 """fill in form from a data group"""
182 opts = self.get_config(dgroup)
183 self.skip_process = True
185 self.plotone_op.SetChoices(list(PlotOne_Choices.keys()))
186 self.plotsel_op.SetChoices(list(PlotSel_Choices.keys()))
188 self.wids['scale'].SetValue(opts['scale'])
189 self.wids['xshift'].SetValue(opts['xshift'])
191 frozen = opts.get('is_frozen', False)
192 frozen = getattr(dgroup, 'is_frozen', frozen)
194 self.wids['is_frozen'].SetValue(frozen)
195 self._set_frozen(frozen)
196 wx.CallAfter(self.unset_skip_process)
198 def unset_skip_process(self):
199 self.skip_process = False
201 def read_form(self):
202 "read form, return dict of values"
203 form_opts = {}
204 form_opts['scale'] = self.wids['scale'].GetValue()
205 form_opts['xshift'] = self.wids['xshift'].GetValue()
206 return form_opts
209 def _set_frozen(self, frozen):
210 try:
211 dgroup = self.controller.get_group()
212 dgroup.is_frozen = frozen
213 except:
214 pass
216 for wattr in ('scale',):
217 self.wids[wattr].Enable(not frozen)
219 def onFreezeGroup(self, evt=None):
220 self._set_frozen(evt.IsChecked())
222 def onPlotEither(self, evt=None):
223 if self.last_plot_type == 'multi':
224 self.onPlotSel(evt=evt)
225 else:
226 self.onPlotOne(evt=evt)
228 def onPlotOne(self, evt=None):
229 self.last_plot_type = 'one'
230 self.plot(self.controller.get_group())
231 wx.CallAfter(self.controller.set_focus)
233 def onVoffset(self, evt=None):
234 time.sleep(0.002)
235 wx.CallAfter(self.onPlotSel)
237 def onPlotSel(self, evt=None):
238 newplot = True
239 self.last_plot_type = 'multi'
240 group_ids = self.controller.filelist.GetCheckedStrings()
241 if len(group_ids) < 1:
242 return
243 last_id = group_ids[-1]
245 groupname = self.controller.file_groups[str(last_id)]
246 dgroup = self.controller.get_group(groupname)
248 plot_choices = PlotSel_Choices
250 ytitle = self.plotsel_op.GetStringSelection()
251 yarray_name = plot_choices.get(ytitle, 'ynorm')
252 ylabel = getattr(plotlabels, yarray_name, ytitle)
253 xlabel = getattr(dgroup, 'plot_xlabel', getattr(plotlabels, 'xplot'))
255 voff = self.wids['plot_voff'].GetValue()
256 plot_traces = []
257 newplot = True
258 plotopts = self.controller.get_plot_conf()
259 popts = {'style': 'solid', 'marker': None}
260 popts['linewidth'] = plotopts.pop('linewidth')
261 popts['marksize'] = plotopts.pop('markersize')
262 popts['grid'] = plotopts.pop('show_grid')
263 popts['fullbox'] = plotopts.pop('show_fullbox')
265 for ix, checked in enumerate(group_ids):
266 groupname = self.controller.file_groups[str(checked)]
267 dgroup = self.controller.get_group(groupname)
268 if dgroup is None:
269 continue
270 trace = {'xdata': dgroup.xplot,
271 'ydata': getattr(dgroup, yarray_name) + ix*voff,
272 'label': dgroup.filename, 'new': newplot}
273 trace.update(popts)
274 plot_traces.append(trace)
275 newplot = False
277 ppanel = self.controller.get_display(stacked=False).panel
278 zoom_limits = get_zoomlimits(ppanel, dgroup)
280 nplot_traces = len(ppanel.conf.traces)
281 nplot_request = len(plot_traces)
282 if nplot_request > nplot_traces:
283 linecolors = ppanel.conf.linecolors
284 ncols = len(linecolors)
285 for i in range(nplot_traces, nplot_request+5):
286 ppanel.conf.init_trace(i, linecolors[i%ncols], 'dashed')
289 ppanel.plot_many(plot_traces, xlabel=plotlabels.xplot, ylabel=ylabel,
290 zoom_limits=zoom_limits, show_legend=True)
291 set_zoomlimits(ppanel, zoom_limits) or ppanel.unzoom_all()
292 ppanel.canvas.draw()
293 wx.CallAfter(self.controller.set_focus)
295 def onUseDefaults(self, evt=None):
296 self.wids['scale'].SetValue(1.0)
297 self.wids['xshift'].SetValue(0.0)
300 def onCopyAuto(self, evt=None):
301 opts = dict(scale=1)
302 for checked in self.controller.filelist.GetCheckedStrings():
303 groupname = self.controller.file_groups[str(checked)]
304 grp = self.controller.get_group(groupname)
305 if grp != self.controller.group and not getattr(grp, 'is_frozen', False):
306 self.update_config(opts, dgroup=grp)
307 self.fill_form(grp)
308 self.process(grp, force=True)
311 def onSaveConfigBtn(self, evt=None):
312 conf = self.get_config()
313 conf.update(self.read_form())
316 def onCopyParam(self, name=None, evt=None):
317 conf = self.get_config()
318 form = self.read_form()
319 conf.update(form)
320 dgroup = self.controller.get_group()
321 self.update_config(conf)
322 self.fill_form(dgroup)
323 opts = {}
324 name = str(name)
325 def copy_attrs(*args):
326 for a in args:
327 opts[a] = conf[a]
328 if name == 'all':
329 copy_attrs('scale')
330 elif name == 'scale':
331 copy_attrs('scale')
333 for checked in self.controller.filelist.GetCheckedStrings():
334 groupname = self.controller.file_groups[str(checked)]
335 grp = self.controller.get_group(groupname)
336 if grp != self.controller.group and not getattr(grp, 'is_frozen', False):
337 self.update_config(opts, dgroup=grp)
338 for key, val in opts.items():
339 if hasattr(grp, key):
340 setattr(grp, key, val)
341 self.fill_form(grp)
342 self.process(grp, force=True)
344 def onSet_Scale(self, evt=None, value=None):
345 "handle setting scale"
346 scale = self.wids['scale'].GetValue()
347 if scale < 0:
348 self.wids['scale'].SetValue(abs(scale))
349 self.update_config({'scale': self.wids['scale'].GetValue()})
350 autoset_fs_increment(self.wids['scale'], abs(scale))
351 time.sleep(0.01)
352 wx.CallAfter(self.onReprocess)
354 def onSet_XShift(self, evt=None, value=None):
355 "handle x shift"
356 xshift = self.wids['xshift'].GetValue()
357 self.update_config({'xshift': self.wids['xshift'].GetValue()})
358 autoset_fs_increment(self.wids['xshift'], abs(xshift))
359 time.sleep(0.01)
360 wx.CallAfter(self.onReprocess)
362 def pin_callback(self, opt='__', xsel=None, relative_e0=True, **kws):
363 """
364 get last selected point from a specified plot window
365 and fill in the value for the widget defined by `opt`.
367 by default it finds the latest cursor position from the
368 cursor history of the first 20 plot windows.
369 """
370 if xsel is None or opt not in self.wids:
371 return
372 if opt == 'scale':
373 self.wids['scale'].SetValue(kws['ysel'])
374 elif opt == 'xshift':
375 self.wids['xshift'].SetValue(kws['xsel'])
376 time.sleep(0.01)
377 wx.CallAfter(self.onReprocess)
379 def onReprocess(self, evt=None, value=None, **kws):
380 "handle request reprocess"
381 if self.skip_process:
382 return
383 try:
384 dgroup = self.controller.get_group()
385 except TypeError:
386 return
387 if not hasattr(dgroup.config, self.configname):
388 return
389 form = self.read_form()
390 self.process(dgroup=dgroup)
391 if self.stale_groups is not None:
392 for g in self.stale_groups:
393 self.process(dgroup=g, force=True)
394 self.stale_groups = None
395 self.onPlotEither()
398 def process(self, dgroup=None, force_mback=False, force=False, use_form=True, **kws):
399 """ handle process (pre-edge/normalize) of XAS data from XAS form
400 """
401 if self.skip_process and not force:
402 return
403 if dgroup is None:
404 dgroup = self.controller.get_group()
405 if dgroup is None:
406 return
408 self.skip_process = True
409 conf = self.get_config(dgroup)
410 form = self.read_form()
411 if not use_form:
412 form.update(self.get_defaultconfig())
414 form['group'] = dgroup.groupname
416 self.skip_process = False
417 scale = form.get('scale', conf.get('scale', 1.0))
418 xshift = form.get('xshift', conf.get('xshift', 0.0))
419 gname = dgroup.groupname
420 cmds = [f"{gname:s}.scale = {scale}",
421 f"{gname:s}.xshift = {xshift}",
422 f"{gname:s}.xplot = {gname:s}.x+{xshift}",
423 f"{gname:s}.ynorm = {gname:s}.y/{scale}",
424 f"{gname:s}.dydx = gradient({gname:s}.ynorm)/gradient({gname:s}.xplot)",
425 f"{gname:s}.d2ydx = gradient({gname:s}.dydx)/gradient({gname:s}.xplot)"]
427 self.larch_eval('\n'.join(cmds))
430 self.unset_skip_process()
431 return
434 def get_plot_arrays(self, dgroup):
435 lab = plotlabels.ynorm
436 if dgroup is None:
437 return
439 dgroup.plot_y2label = None
440 dgroup.plot_xlabel = plotlabels.xplot
441 dgroup.plot_yarrays = [('y', PLOTOPTS_1, lab)]
443 req_attrs = ['y', 'i0', 'ynorm', 'dydx', 'd2ydx']
444 pchoice = PlotOne_Choices.get(self.plotone_op.GetStringSelection(), 'ynorm')
446 if pchoice in ('y', 'i0', 'ynorm', 'dydx', 'd2ydx'):
447 lab = getattr(plotlabels, pchoice)
448 dgroup.plot_yarrays = [(pchoice, PLOTOPTS_1, lab)]
450 elif pchoice == 'y+dydx':
451 lab = plotlabels.y
452 dgroup.plot_y2label = lab2 = plotlabels.dydx
453 dgroup.plot_yarrays = [('y', PLOTOPTS_1, lab),
454 ('dydx', PLOTOPTS_D, lab2)]
455 elif pchoice == 'ynorm+dydx':
456 lab = plotlabels.ynorm
457 dgroup.plot_y2label = lab2 = plotlabels.dydx
458 dgroup.plot_yarrays = [('ynorm', PLOTOPTS_1, lab),
459 ('dydx', PLOTOPTS_D, lab2)]
461 elif pchoice == 'ynorm+i0':
462 lab = plotlabels.ynorm
463 dgroup.plot_y2label = lab2 = plotlabels.i0
464 dgroup.plot_yarrays = [('ynorm', PLOTOPTS_1, lab),
465 ('i0', PLOTOPTS_D, lab2)]
467 dgroup.plot_ylabel = lab
468 needs_proc = False
469 for attr in req_attrs:
470 needs_proc = needs_proc or (not hasattr(dgroup, attr))
472 if needs_proc:
473 self.process(dgroup=dgroup, force=True)
475 y4e0 = dgroup.yplot = getattr(dgroup, dgroup.plot_yarrays[0][0], dgroup.y)
476 dgroup.plot_extras = []
478 popts = {'marker': 'o', 'markersize': 5,
479 'label': '_nolegend_',
480 'markerfacecolor': '#888',
481 'markeredgecolor': '#A00'}
484 def plot(self, dgroup, title=None, plot_yarrays=None, yoff=0,
485 delay_draw=True, multi=False, new=True, with_extras=True, **kws):
487 if self.skip_plotting:
488 return
489 ppanel = self.controller.get_display(stacked=False).panel
491 plotcmd = ppanel.oplot
492 if new:
493 plotcmd = ppanel.plot
495 groupname = getattr(dgroup, 'groupname', None)
496 if groupname is None:
497 return
499 if not hasattr(dgroup, 'xplot'):
500 print("Cannot plot group ", groupname)
502 if ((getattr(dgroup, 'plot_yarrays', None) is None or
503 getattr(dgroup, 'dydx', None) is None or
504 getattr(dgroup, 'd2ydx', None) is None or
505 getattr(dgroup, 'ynorm', None) is None)):
506 self.process(dgroup=dgroup)
507 self.get_plot_arrays(dgroup)
509 if plot_yarrays is None and hasattr(dgroup, 'plot_yarrays'):
510 plot_yarrays = dgroup.plot_yarrays
512 popts = self.controller.get_plot_conf()
513 popts.update(kws)
514 popts['grid'] = popts.pop('show_grid')
515 popts['fullbox'] = popts.pop('show_fullbox')
517 path, fname = path_split(dgroup.filename)
518 if 'label' not in popts:
519 popts['label'] = dgroup.plot_ylabel
521 zoom_limits = get_zoomlimits(ppanel, dgroup)
523 popts['xlabel'] = dgroup.plot_xlabel
524 popts['ylabel'] = dgroup.plot_ylabel
525 if getattr(dgroup, 'plot_y2label', None) is not None:
526 popts['y2label'] = dgroup.plot_y2label
528 plot_choices = PlotSel_Choices
530 if multi:
531 ylabel = self.plotsel_op.GetStringSelection()
532 yarray_name = plot_choices.get(ylabel, 'ynorm')
534 if self.is_xasgroup(dgroup):
535 ylabel = getattr(plotlabels, yarray_name, ylabel)
536 popts['ylabel'] = ylabel
538 plot_extras = None
539 if new:
540 if title is None:
541 title = fname
542 plot_extras = getattr(dgroup, 'plot_extras', None)
544 popts['title'] = title
545 popts['show_legend'] = len(plot_yarrays) > 1
546 narr = len(plot_yarrays) - 1
548 _linewidth = popts['linewidth']
549 for i, pydat in enumerate(plot_yarrays):
550 yaname, yopts, yalabel = pydat
551 popts.update(yopts)
552 if yalabel is not None:
553 popts['label'] = yalabel
554 linewidht = _linewidth
555 if 'linewidth' in popts:
556 linewidth = popts.pop('linewidth')
557 popts['delay_draw'] = delay_draw
559 if yaname == 'i0' and not hasattr(dgroup, yaname):
560 dgroup.i0 = np.ones(len(dgroup.xplot))
561 plotcmd(dgroup.xplot, getattr(dgroup, yaname)+yoff, linewidth=linewidth, **popts)
562 plotcmd = ppanel.oplot
564 if with_extras and plot_extras is not None:
565 axes = ppanel.axes
566 for etype, x, y, opts in plot_extras:
567 if etype == 'marker':
568 xpopts = {'marker': 'o', 'markersize': 5,
569 'label': '_nolegend_',
570 'markerfacecolor': 'red',
571 'markeredgecolor': '#884444'}
572 xpopts.update(opts)
573 axes.plot([x], [y], **xpopts)
574 elif etype == 'vline':
575 xpopts = {'ymin': 0, 'ymax': 1.0,
576 'label': '_nolegend_',
577 'color': '#888888'}
578 xpopts.update(opts)
579 axes.axvline(x, **xpopts)
581 # set_zoomlimits(ppanel, zoom_limits)
582 ppanel.reset_formats()
583 set_zoomlimits(ppanel, zoom_limits)
584 ppanel.conf.unzoom(full=True, delay_draw=False)