Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/plotter.py: 16%
559 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2'''
3 Plotting functions for Larch, wrapping the mplot plotting
4 widgets which use matplotlib
6Exposed functions here are
7 plot: display 2D line plot to an enhanced,
8 configurable Plot Frame
9 oplot: overplot a 2D line plot on an existing Plot Frame
10 imshow: display a false-color map from array data on
11 a configurable Image Display Frame.
12'''
13import time
14import os
15import sys
16import wx
17from copy import deepcopy
18from wxmplot import PlotFrame, ImageFrame, StackedPlotFrame
19from wxmplot.interactive import get_wxapp
21import larch
22from ..utils import mkdir
23from ..xrf import isLarchMCAGroup
24from ..larchlib import ensuremod
25from ..site_config import user_larchdir
27from .xrfdisplay import XRFDisplayFrame
29mplconfdir = os.path.join(user_larchdir, 'matplotlib')
30mkdir(mplconfdir)
31os.environ['MPLCONFIGDIR'] = mplconfdir
33from matplotlib.axes import Axes
34HIST_DOC = Axes.hist.__doc__
36IMG_DISPLAYS = {}
37PLOT_DISPLAYS = {}
38FITPLOT_DISPLAYS = {}
39XRF_DISPLAYS = {}
40DISPLAY_LIMITS = None
41PLOTOPTS = {'theme': 'light',
42 'height': 550,
43 'width': 600,
44 'linewidth': 2.5,
45 'show_grid': True,
46 'show_fullbox': True}
48_larch_name = '_plotter'
50__DOC__ = '''
51General Plotting and Image Display Functions
53The functions here include (but are not limited to):
55function description
56------------ ------------------------------
57plot 2D (x, y) plotting, with many, many options
58plot_text add text to a 2D plot
59plot_marker add a marker to a 2D plot
60plot_arrow add an arrow to a 2D plot
62imshow image display (false-color intensity image)
64xrf_plot browsable display for XRF spectra
65'''
67MAX_WINDOWS = 25
68MAX_CURSHIST = 100
70class XRFDisplay(XRFDisplayFrame):
71 def __init__(self, wxparent=None, window=1, _larch=None,
72 size=(725, 425), **kws):
73 XRFDisplayFrame.__init__(self, parent=wxparent, size=size,
74 _larch=_larch,
75 exit_callback=self.onExit, **kws)
76 self.Show()
77 self.Raise()
78 self.panel.cursor_callback = self.onCursor
79 self.window = int(window)
80 self._larch = _larch
81 self._xylims = {}
82 self.symname = '%s.xrf%i' % (_larch_name, self.window)
83 symtable = ensuremod(self._larch, _larch_name)
85 if symtable is not None:
86 symtable.set_symbol(self.symname, self)
87 if window not in XRF_DISPLAYS:
88 XRF_DISPLAYS[window] = self
90 def onExit(self, o, **kw):
91 try:
92 symtable = self._larch.symtable
93 if symtable.has_group(_larch_name):
94 symtable.del_symbol(self.symname)
95 except:
96 pass
97 if self.window in XRF_DISPLAYS:
98 XRF_DISPLAYS.pop(self.window)
100 self.Destroy()
102 def onCursor(self, x=None, y=None, **kw):
103 symtable = ensuremod(self._larch, _larch_name)
104 if symtable is None:
105 return
106 symtable.set_symbol('%s_xrf_x' % self.symname, x)
107 symtable.set_symbol('%s_xrf_y' % self.symname, y)
109class PlotDisplay(PlotFrame):
110 def __init__(self, wxparent=None, window=1, _larch=None, size=None, **kws):
111 PlotFrame.__init__(self, parent=None, size=size,
112 output_title='larchplot',
113 exit_callback=self.onExit, **kws)
115 self.Show()
116 self.Raise()
117 self.panel.cursor_callback = self.onCursor
118 self.panel.cursor_mode = 'zoom'
119 self.window = int(window)
120 self._larch = _larch
121 self._xylims = {}
122 self.cursor_hist = []
123 self.symname = '%s.plot%i' % (_larch_name, self.window)
124 symtable = ensuremod(self._larch, _larch_name)
125 self.panel.canvas.figure.set_facecolor('#FDFDFB')
127 if symtable is not None:
128 symtable.set_symbol(self.symname, self)
129 if not hasattr(symtable, '%s.cursor_maxhistory' % _larch_name):
130 symtable.set_symbol('%s.cursor_maxhistory' % _larch_name, MAX_CURSHIST)
132 if window not in PLOT_DISPLAYS:
133 PLOT_DISPLAYS[window] = self
135 def onExit(self, o, **kw):
136 try:
137 symtable = self._larch.symtable
138 if symtable.has_group(_larch_name):
139 symtable.del_symbol(self.symname)
140 except:
141 pass
142 if self.window in PLOT_DISPLAYS:
143 PLOT_DISPLAYS.pop(self.window)
145 self.Destroy()
147 def onCursor(self, x=None, y=None, **kw):
148 symtable = ensuremod(self._larch, _larch_name)
149 if symtable is None:
150 return
151 hmax = getattr(symtable, '%s.cursor_maxhistory' % _larch_name, MAX_CURSHIST)
152 symtable.set_symbol('%s_x' % self.symname, x)
153 symtable.set_symbol('%s_y' % self.symname, y)
154 self.cursor_hist.insert(0, (x, y, time.time()))
155 if len(self.cursor_hist) > hmax:
156 self.cursor_hist = self.cursor_hist[:hmax]
157 symtable.set_symbol('%s_cursor_hist' % self.symname, self.cursor_hist)
160class StackedPlotDisplay(StackedPlotFrame):
161 def __init__(self, wxparent=None, window=1, _larch=None, size=None, **kws):
162 StackedPlotFrame.__init__(self, parent=None,
163 exit_callback=self.onExit, **kws)
165 self.Show()
166 self.Raise()
167 self.panel.cursor_callback = self.onCursor
168 self.panel.cursor_mode = 'zoom'
169 self.window = int(window)
170 self._larch = _larch
171 self._xylims = {}
172 self.cursor_hist = []
173 self.symname = '%s.fitplot%i' % (_larch_name, self.window)
174 symtable = ensuremod(self._larch, _larch_name)
175 self.panel.canvas.figure.set_facecolor('#FDFDFB')
176 self.panel_bot.canvas.figure.set_facecolor('#FDFDFB')
178 if symtable is not None:
179 symtable.set_symbol(self.symname, self)
180 if not hasattr(symtable, '%s.cursor_maxhistory' % _larch_name):
181 symtable.set_symbol('%s.cursor_maxhistory' % _larch_name, MAX_CURSHIST)
183 if window not in FITPLOT_DISPLAYS:
184 FITPLOT_DISPLAYS[window] = self
186 def onExit(self, o, **kw):
187 try:
188 symtable = self._larch.symtable
189 if symtable.has_group(_larch_name):
190 symtable.del_symbol(self.symname)
191 except:
192 pass
193 if self.window in FITPLOT_DISPLAYS:
194 FITPLOT_DISPLAYS.pop(self.window)
196 self.Destroy()
198 def onCursor(self, x=None, y=None, **kw):
199 symtable = ensuremod(self._larch, _larch_name)
200 if symtable is None:
201 return
202 hmax = getattr(symtable, '%s.cursor_maxhistory' % _larch_name, MAX_CURSHIST)
203 symtable.set_symbol('%s_x' % self.symname, x)
204 symtable.set_symbol('%s_y' % self.symname, y)
205 self.cursor_hist.insert(0, (x, y, time.time()))
206 if len(self.cursor_hist) > hmax:
207 self.cursor_hist = self.cursor_hist[:hmax]
208 symtable.set_symbol('%s_cursor_hist' % self.symname, self.cursor_hist)
210class ImageDisplay(ImageFrame):
211 def __init__(self, wxparent=None, window=1, _larch=None, size=None, **kws):
212 ImageFrame.__init__(self, parent=None, size=size,
213 exit_callback=self.onExit, **kws)
214 self.Show()
215 self.Raise()
216 self.cursor_pos = []
217 self.panel.cursor_callback = self.onCursor
218 self.panel.contour_callback = self.onContour
219 self.window = int(window)
220 self.symname = '%s.img%i' % (_larch_name, self.window)
221 self._larch = _larch
222 symtable = ensuremod(self._larch, _larch_name)
223 if symtable is not None:
224 symtable.set_symbol(self.symname, self)
225 if self.window not in IMG_DISPLAYS:
226 IMG_DISPLAYS[self.window] = self
228 def onContour(self, levels=None, **kws):
229 symtable = ensuremod(self._larch, _larch_name)
230 if symtable is not None and levels is not None:
231 symtable.set_symbol('%s_contour_levels' % self.symname, levels)
233 def onExit(self, o, **kw):
234 try:
235 symtable = self._larch.symtable
236 symtable.has_group(_larch_name), self.symname
237 if symtable.has_group(_larch_name):
238 symtable.del_symbol(self.symname)
239 except:
240 pass
241 if self.window in IMG_DISPLAYS:
242 IMG_DISPLAYS.pop(self.window)
243 self.Destroy()
245 def onCursor(self,x=None, y=None, ix=None, iy=None, val=None, **kw):
246 symtable = ensuremod(self._larch, _larch_name)
247 if symtable is None:
248 return
249 set = symtable.set_symbol
250 if x is not None: set('%s_x' % self.symname, x)
251 if y is not None: set('%s_y' % self.symname, y)
252 if ix is not None: set('%s_ix' % self.symname, ix)
253 if iy is not None: set('%s_iy' % self.symname, iy)
254 if val is not None: set('%s_val' % self.symname, val)
256def get_display(win=1, _larch=None, wxparent=None, size=None,
257 wintitle=None, xrf=False, image=False, stacked=False,
258 theme=None, linewidth=None, markersize=None,
259 show_grid=None, show_fullbox=None, height=None,
260 width=None):
261 """make a plotter"""
262 # global PLOT_DISPLAYS, IMG_DISPlAYS
263 if hasattr(_larch, 'symtable'):
264 if (getattr(_larch.symtable._sys.wx, 'wxapp', None) is None or
265 getattr(_larch.symtable._plotter, 'no_plotting', False)):
266 return None
268 global PLOTOPTS
269 try:
270 PLOTOPTS = deepcopy(_larch.symtable._sys.wx.plotopts)
271 except:
272 pass
274 global DISPLAY_LIMITS
275 if DISPLAY_LIMITS is None:
276 displays = [wx.Display(i) for i in range(wx.Display.GetCount())]
277 geoms = [d.GetGeometry() for d in displays]
278 _left = min([g.Left for g in geoms])
279 _right = max([g.Right for g in geoms])
280 _top = min([g.Top for g in geoms])
281 _bot = max([g.Bottom for g in geoms])
282 DISPLAY_LIMITS = [_left, _right, _top, _bot]
285 win = max(1, min(MAX_WINDOWS, int(abs(win))))
286 title = 'Plot Window %i' % win
287 symname = '%s.plot%i' % (_larch_name, win)
288 creator = PlotDisplay
289 display_dict = PLOT_DISPLAYS
290 if image:
291 creator = ImageDisplay
292 display_dict = IMG_DISPLAYS
293 title = 'Image Window %i' % win
294 symname = '%s.img%i' % (_larch_name, win)
295 elif xrf:
296 creator = XRFDisplay
297 display_dict = XRF_DISPLAYS
298 title = 'XRF Display Window %i' % win
299 symname = '%s.xrf%i' % (_larch_name, win)
300 elif stacked:
301 creator = StackedPlotDisplay
302 display_dict = FITPLOT_DISPLAYS
303 title = 'Fit Plot Window %i' % win
304 symname = '%s.fitplot%i' % (_larch_name, win)
306 if wintitle is not None:
307 title = wintitle
309 def _get_disp(symname, creator, win, ddict, wxparent, size, height, width, _larch):
310 wxapp = get_wxapp()
311 display = None
312 new_display = False
313 if win in ddict:
314 display = ddict[win]
315 try:
316 s = display.GetSize()
317 except RuntimeError: # window has been deleted
318 ddict.pop(win)
319 display = None
321 if display is None and hasattr(_larch, 'symtable'):
322 display = _larch.symtable.get_symbol(symname, create=True)
323 if display is not None:
324 try:
325 s = display.GetSize()
326 except RuntimeError: # window has been deleted
327 display = None
329 if display is None:
330 if size is None:
331 if height is None:
332 height = PLOTOPTS['height']
333 if width is None:
334 width = PLOTOPTS['width']
335 size = (int(width), int(height))
336 display = creator(window=win, wxparent=wxparent,
337 size=size, _larch=_larch)
338 new_display = True
339 parent = wx.GetApp().GetTopWindow()
341 if parent is not None:
342 xpos, ypos = parent.GetPosition()
343 xsiz, ysiz = parent.GetSize()
344 dlims = DISPLAY_LIMITS
345 x = min(dlims[1]-width*0.7, max(dlims[0]+5, xpos+xsiz*(0.65+0.35*win)))
346 y = min(dlims[3]-height*0.7, max(dlims[2]+5, ypos-ysiz*0.05*(win-1)))
347 display.SetPosition((int(x), int(y)))
349 ddict[win] = display
350 return display, new_display
353 display, isnew = _get_disp(symname, creator, win, display_dict, wxparent,
354 size, height, width, _larch)
355 if isnew and creator in (PlotDisplay, StackedPlotDisplay):
356 if theme is not None:
357 PLOTOPTS['theme'] = theme
358 if show_grid is not None:
359 PLOTOPTS['show_grid'] = show_grid
360 if show_fullbox is not None:
361 PLOTOPTS['show_fullbox'] = show_fullbox
362 if linewidth is not None:
363 PLOTOPTS['linewidth'] = linewidth
364 if markersize is not None:
365 PLOTOPTS['markersize'] = markersize
366 panels = [display.panel]
367 if creator == StackedPlotDisplay:
368 panels.append(display.panel_bot)
369 for panel in panels:
370 conf = panel.conf
371 conf.set_theme(theme=PLOTOPTS['theme'])
372 conf.enable_grid(PLOTOPTS['show_grid'])
373 conf.axes_style = 'box' if PLOTOPTS['show_fullbox'] else 'open'
374 for i in range(16):
375 conf.set_trace_linewidth(PLOTOPTS['linewidth'], trace=i)
376 try:
377 display.SetTitle(title)
379 except:
380 display_dict.pop(win)
381 display, isnew = _get_disp(symname, creator, win, display_dict, wxparent,
382 size, _larch)
383 display.SetTitle(title)
384 if hasattr(_larch, 'symtable'):
385 _larch.symtable.set_symbol(symname, display)
386 return display
389_getDisplay = get_display # back compatibility
391def _xrf_plot(x=None, y=None, mca=None, win=1, new=True, as_mca2=False, _larch=None,
392 wxparent=None, size=None, side='left', force_draw=True, wintitle=None,
393 **kws):
394 """xrf_plot(energy, data[, win=1], options])
396 Show XRF trace of energy, data
398 Parameters:
399 --------------
400 energy : array of energies
401 counts : array of counts
402 mca: Group counting MCA data (rois, etc)
403 as_mca2: use mca as background MCA
405 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
406 new: flag (True/False, default False) for whether to start a new plot.
407 color: color for trace (name such as 'red', or '#RRGGBB' hex string)
408 style: trace linestyle (one of 'solid', 'dashed', 'dotted', 'dot-dash')
409 linewidth: integer width of line
410 marker: symbol to draw at each point ('+', 'o', 'x', 'square', etc)
411 markersize: integer size of marker
413 See Also: xrf_oplot, plot
414 """
415 plotter = get_display(wxparent=wxparent, win=win, size=size,
416 _larch=_larch, wintitle=wintitle, xrf=True)
417 if plotter is None:
418 return
419 plotter.Raise()
420 if x is None:
421 return
423 if isLarchMCAGroup(x):
424 mca = x
425 y = x.counts
426 x = x.energy
428 if as_mca2:
429 if isLarchMCAGroup(mca):
430 plotter.add_mca(mca, as_mca2=True, plot=False)
431 plotter.plotmca(mca, as_mca2=True, **kws)
432 elif y is not None:
433 plotter.oplot(x, y, mca=mca, as_mca2=True, **kws)
434 elif new:
435 if isLarchMCAGroup(mca):
436 plotter.add_mca(mca, plot=False)
437 plotter.plotmca(mca, **kws)
438 elif y is not None:
439 plotter.plot(x, y, mca=mca, **kws)
440 elif y is not None:
441 if isLarchMCAGroup(mca):
442 plotter.add_mca(mca, plot=False)
443 plotter.oplot(x, y, mca=mca, **kws)
446def _xrf_oplot(x=None, y=None, mca=None, win=1, _larch=None, **kws):
447 """xrf_oplot(energy, data[, win=1], options])
449 Overplot a second XRF trace of energy, data
451 Parameters:
452 --------------
453 energy : array of energies
454 counts : array of counts
455 mca: Group counting MCA data (rois, etc)
457 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
458 color: color for trace (name such as 'red', or '#RRGGBB' hex string)
459 style: trace linestyle (one of 'solid', 'dashed', 'dotted', 'dot-dash')
461 See Also: xrf_plot
462 """
463 _xrf_plot(x=x, y=y, mca=mca, win=win, _larch=_larch, new=False, **kws)
465def _plot(x,y, win=1, new=False, _larch=None, wxparent=None, size=None,
466 xrf=False, stacked=False, force_draw=True, side='left', wintitle=None, **kws):
467 """plot(x, y[, win=1], options])
469 Plot 2-D trace of x, y arrays in a Plot Frame, clearing any plot currently in the Plot Frame.
471 Parameters:
472 --------------
473 x : array of ordinate values
474 y : array of abscissa values (x and y must be same size!)
476 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
477 new: flag (True/False, default False) for whether to start a new plot.
478 force_draw: flag (True/False, default Tree) for whether force a draw.
479 This will take a little extra time, and is not needed when
480 typing at the command-line, but is needed for plots to update
481 from inside scripts.
482 label: label for trace
483 title: title for Plot
484 xlabel: x-axis label
485 ylabel: y-axis label
486 ylog_scale: whether to show y-axis as log-scale (True or False)
487 grid: whether to draw background grid (True or False)
489 color: color for trace (name such as 'red', or '#RRGGBB' hex string)
490 style: trace linestyle (one of 'solid', 'dashed', 'dotted', 'dot-dash')
491 linewidth: integer width of line
492 marker: symbol to draw at each point ('+', 'o', 'x', 'square', etc)
493 markersize: integer size of marker
495 drawstyle: style for joining line segments
497 dy: array for error bars in y (must be same size as y!)
498 yaxis='left'??
499 use_dates
501 See Also: oplot, newplot
502 """
503 plotter = get_display(wxparent=wxparent, win=win, size=size,
504 xrf=xrf, stacked=stacked,
505 wintitle=wintitle, _larch=_larch)
506 if plotter is None:
507 return
508 plotter.Raise()
509 if new:
510 plotter.plot(x, y, side=side, **kws)
511 else:
512 plotter.oplot(x, y, side=side, **kws)
513 if force_draw:
514 wx_update(_larch=_larch)
516def _redraw_plot(win=1, xrf=False, stacked=False, size=None, wintitle=None,
517 _larch=None, wxparent=None):
518 """redraw_plot(win=1)
520 redraw a plot window, especially convenient to force setting limits after
521 multiple plot()s with delay_draw=True
522 """
524 plotter = get_display(wxparent=wxparent, win=win, size=size,
525 xrf=xrf, stacked=stacked,
526 wintitle=wintitle, _larch=_larch)
527 plotter.panel.unzoom_all()
530def _update_trace(x, y, trace=1, win=1, _larch=None, wxparent=None,
531 side='left', redraw=False, **kws):
532 """update a plot trace with new data, avoiding complete redraw"""
533 plotter = get_display(wxparent=wxparent, win=win, _larch=_larch)
534 if plotter is None:
535 return
536 plotter.Raise()
537 trace -= 1 # wxmplot counts traces from 0
539 plotter.panel.update_line(trace, x, y, draw=True, side=side)
540 wx_update(_larch=_larch)
542def wx_update(_larch=None, **kws):
543 if hasattr(_larch, 'symtable'):
544 _larch.symtable.set_symbol('_sys.wx.force_wxupdate', True)
545 try:
546 _larch.symtable.get_symbol('_sys.wx.ping')(timeout=0.002)
547 except:
548 pass
550def _plot_setlimits(xmin=None, xmax=None, ymin=None, ymax=None, win=1, wxparent=None,
551 _larch=None):
552 """set plot view limits for plot in window `win`"""
553 plotter = get_display(wxparent=wxparent, win=win, _larch=_larch)
554 if plotter is None:
555 return
556 plotter.panel.set_xylims((xmin, xmax, ymin, ymax))
558def _oplot(x, y, win=1, _larch=None, wxparent=None, xrf=False, stacked=False,
559 size=None, **kws):
560 """oplot(x, y[, win=1[, options]])
562 Plot 2-D trace of x, y arrays in a Plot Frame, over-plotting any
563 plot currently in the Plot Frame.
565 This is equivalent to
566 plot(x, y[, win=1[, new=False[, options]]])
568 See Also: plot, newplot
569 """
570 kws['new'] = False
571 _plot(x, y, win=win, size=size, xrf=xrf, stacked=stacked,
572 wxparent=wxparent, _larch=_larch, **kws)
574def _newplot(x, y, win=1, _larch=None, wxparent=None, size=None, wintitle=None,
575 **kws):
576 """newplot(x, y[, win=1[, options]])
578 Plot 2-D trace of x, y arrays in a Plot Frame, clearing any
579 plot currently in the Plot Frame.
581 This is equivalent to
582 plot(x, y[, win=1[, new=True[, options]]])
584 See Also: plot, oplot
585 """
586 _plot(x, y, win=win, size=size, new=True, _larch=_larch,
587 wxparent=wxparent, wintitle=wintitle, **kws)
589def _plot_text(text, x, y, win=1, side='left', size=None,
590 stacked=False, xrf=False, rotation=None, ha='left', va='center',
591 _larch=None, wxparent=None, **kws):
592 """plot_text(text, x, y, win=1, options)
594 add text at x, y coordinates of a plot
596 Parameters:
597 --------------
598 text: text to draw
599 x: x position of text
600 y: y position of text
601 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
602 side: which axis to use ('left' or 'right') for coordinates.
603 rotation: text rotation. angle in degrees or 'vertical' or 'horizontal'
604 ha: horizontal alignment ('left', 'center', 'right')
605 va: vertical alignment ('top', 'center', 'bottom', 'baseline')
607 See Also: plot, oplot, plot_arrow
608 """
609 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
610 stacked=stacked, _larch=_larch)
611 if plotter is None:
612 return
613 plotter.Raise()
615 plotter.add_text(text, x, y, side=side,
616 rotation=rotation, ha=ha, va=va, **kws)
618def _plot_arrow(x1, y1, x2, y2, win=1, side='left',
619 shape='full', color='black',
620 width=0.00, head_width=0.05, head_length=0.25,
621 _larch=None, wxparent=None, stacked=False, xrf=False,
622 size=None, **kws):
624 """plot_arrow(x1, y1, x2, y2, win=1, **kws)
626 draw arrow from x1, y1 to x2, y2.
628 Parameters:
629 --------------
630 x1: starting x coordinate
631 y1: starting y coordinate
632 x2: ending x coordinate
633 y2: ending y coordinate
634 side: which axis to use ('left' or 'right') for coordinates.
635 shape: arrow head shape ('full', 'left', 'right')
636 color: arrow color ('black')
637 width: width of arrow line (in points. default=0.0)
638 head_width: width of arrow head (in points. default=0.05)
639 head_length: length of arrow head (in points. default=0.25)
640 overhang: amount the arrow is swept back (in points. default=0)
641 win: window to draw too
643 See Also: plot, oplot, plot_text
644 """
645 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
646 stacked=stacked, _larch=_larch)
647 if plotter is None:
648 return
649 plotter.Raise()
650 plotter.add_arrow(x1, y1, x2, y2, side=side, shape=shape,
651 color=color, width=width, head_length=head_length,
652 head_width=head_width, **kws)
654def _plot_marker(x, y, marker='o', size=4, color='black', label='_nolegend_',
655 _larch=None, wxparent=None, win=1, xrf=False, stacked=False, **kws):
657 """plot_marker(x, y, marker='o', size=4, color='black')
659 draw a marker at x, y
661 Parameters:
662 -----------
663 x: x coordinate
664 y: y coordinate
665 marker: symbol to draw at each point ('+', 'o', 'x', 'square', etc) ['o']
666 size: symbol size [4]
667 color: color ['black']
669 See Also: plot, oplot, plot_text
670 """
671 plotter = get_display(wxparent=wxparent, win=win, size=None, xrf=xrf,
672 stacked=stacked, _larch=_larch)
673 if plotter is None:
674 return
675 plotter.Raise()
676 plotter.oplot([x], [y], marker=marker, markersize=size, label=label,
677 color=color, _larch=_larch, wxparent=wxparent, **kws)
679def _plot_axhline(y, xmin=0, xmax=1, win=1, wxparent=None, xrf=False,
680 stacked=False, size=None, delay_draw=False, _larch=None, **kws):
681 """plot_axhline(y, xmin=None, ymin=None, **kws)
683 plot a horizontal line spanning the plot axes
684 Parameters:
685 --------------
686 y: y position of line
687 xmin: starting x fraction (window units -- not user units!)
688 xmax: ending x fraction (window units -- not user units!)
689 See Also: plot, oplot, plot_arrow
690 """
691 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
692 stacked=stacked, _larch=_larch)
693 if plotter is None:
694 return
695 plotter.Raise()
696 if 'label' not in kws:
697 kws['label'] = '_nolegend_'
698 plotter.panel.axes.axhline(y, xmin=xmin, xmax=xmax, **kws)
699 if delay_draw:
700 plotter.panel.canvas.draw()
702def _plot_axvline(x, ymin=0, ymax=1, win=1, wxparent=None, xrf=False,
703 stacked=False, size=None, delay_draw=False, _larch=None, **kws):
704 """plot_axvline(y, xmin=None, ymin=None, **kws)
706 plot a vertical line spanning the plot axes
707 Parameters:
708 --------------
709 x: x position of line
710 ymin: starting y fraction (window units -- not user units!)
711 ymax: ending y fraction (window units -- not user units!)
712 See Also: plot, oplot, plot_arrow
713 """
714 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
715 stacked=stacked, _larch=_larch)
716 if plotter is None:
717 return
718 plotter.Raise()
719 if 'label' not in kws:
720 kws['label'] = '_nolegend_'
721 plotter.panel.axes.axvline(x, ymin=ymin, ymax=ymax, **kws)
722 if not delay_draw:
723 plotter.panel.canvas.draw()
725def _getcursor(win=1, timeout=15, _larch=None, wxparent=None, size=None,
726 xrf=False, stacked=False, **kws):
727 """get_cursor(win=1, timeout=30)
729 waits (up to timeout) for cursor click in selected plot window, and
730 returns x, y position of cursor. On timeout, returns the last known
731 cursor position, or (None, None)
733 Note that _plotter.plotWIN_x and _plotter.plotWIN_y will be updated,
734 with each cursor click, and so can be used to read the last cursor
735 position without blocking.
737 For a more consistent programmatic approach, this routine can be called
738 with timeout <= 0 to read the most recently clicked cursor position.
739 """
740 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
741 stacked=stacked, _larch=_larch)
742 if plotter is None:
743 return
744 symtable = ensuremod(_larch, _larch_name)
745 xsym = '%s.plot%i_x' % (_larch_name, win)
746 ysym = '%s.plot%i_y' % (_larch_name, win)
748 xval = symtable.get_symbol(xsym, create=True)
749 yval = symtable.get_symbol(ysym, create=True)
750 symtable.set_symbol(xsym, None)
752 t0 = time.time()
753 while time.time() - t0 < timeout:
754 wx_update(_larch=_larch)
755 time.sleep(0.05)
756 if symtable.get_symbol(xsym) is not None:
757 break
759 # restore value on timeout
760 if symtable.get_symbol(xsym, create=False) is None:
761 symtable.set_symbol(xsym, xval)
763 return (symtable.get_symbol(xsym), symtable.get_symbol(ysym))
765def last_cursor_pos(win=None, _larch=None):
766 """return most recent cursor position -- 'last click on plot'
768 By default, this returns the last postion for all plot windows.
769 If win is not `None`, the last position for that window will be returned
771 Arguments
772 ---------
773 win (int or None) index of window to get cursor position [None, all windows]
775 Returns
776 -------
777 x, y coordinates of most recent cursor click, in user units
778 """
779 if hasattr(_larch, 'symtable'):
780 plotter = _larch.symtable._plotter
781 else:
782 return None, None
783 histories = []
784 for attr in dir(plotter):
785 if attr.endswith('_cursor_hist'):
786 histories.append(attr)
788 if win is not None:
789 tmp = []
790 for attr in histories:
791 if attr.startswith('plot%d_' % win):
792 tmp.append(attr)
793 histories = tmp
794 _x, _y, _t = None, None, 0
795 for hist in histories:
796 for px, py, pt in getattr(plotter, hist, [None, None, -1]):
797 if pt > _t and px is not None:
798 _x, _y, _t = px, py, pt
799 return _x, _y
802def _scatterplot(x,y, win=1, _larch=None, wxparent=None, size=None,
803 force_draw=True, **kws):
804 """scatterplot(x, y[, win=1], options])
806 Plot x, y values as a scatterplot. Parameters are very similar to
807 those of plot()
809 See Also: plot, newplot
810 """
811 plotter = get_display(wxparent=wxparent, win=win, size=size, _larch=_larch)
812 if plotter is None:
813 return
814 plotter.Raise()
815 plotter.scatterplot(x, y, **kws)
816 if force_draw:
817 wx_update(_larch=_larch)
820def _fitplot(x, y, y2=None, panel='top', label=None, label2=None, win=1,
821 _larch=None, wxparent=None, size=None, **kws):
822 """fit_plot(x, y, y2=None, win=1, options)
824 Plot x, y values in the top of a StackedPlot. If y2 is not None, then x, y2 values
825 will also be plotted in the top frame, and the residual (y-y2) in the bottom panel.
827 By default, arrays will be plotted in the top panel, and you must
828 specify `panel='bot'` to plot an array in the bottom panel.
830 Parameters are the same as for plot() and oplot()
832 See Also: plot, newplot
833 """
834 plotter = get_display(wxparent=wxparent, win=win, size=size,
835 stacked=True, _larch=_larch)
836 if plotter is None:
837 return
838 plotter.Raise()
839 plotter.plot(x, y, panel='top', label=label, **kws)
840 if y2 is not None:
841 kws.update({'label': label2})
842 plotter.oplot(x, y2, panel='top', **kws)
843 plotter.plot(x, y2-y, panel='bot')
844 plotter.panel.conf.set_margins(top=0.15, bottom=0.01,
845 left=0.15, right=0.05)
846 plotter.panel.unzoom_all()
847 plotter.panel_bot.conf.set_margins(top=0.01, bottom=0.35,
848 left=0.15, right=0.05)
849 plotter.panel_bot.unzoom_all()
852def _hist(x, bins=10, win=1, new=False,
853 _larch=None, wxparent=None, size=None, force_draw=True, *args, **kws):
855 plotter = get_display(wxparent=wxparent, win=win, size=size, _larch=_larch)
856 if plotter is None:
857 return
858 plotter.Raise()
859 if new:
860 plotter.panel.axes.clear()
862 out = plotter.panel.axes.hist(x, bins=bins, **kws)
863 plotter.panel.canvas.draw()
864 if force_draw:
865 wx_update(_larch=_larch)
866 return out
869_hist.__doc__ = """
870 hist(x, bins, win=1, options)
872 %s
873""" % (HIST_DOC)
876def _imshow(map, x=None, y=None, colormap=None, win=1, _larch=None,
877 wxparent=None, size=None, **kws):
878 """imshow(map[, options])
880 Display an 2-D array of intensities as a false-color map
882 map: 2-dimensional array for map
883 """
884 img = get_display(wxparent=wxparent, win=win, size=size, _larch=_larch, image=True)
885 if img is not None:
886 img.display(map, x=x, y=y, colormap=colormap, **kws)
888def _contour(map, x=None, y=None, _larch=None, **kws):
889 """contour(map[, options])
891 Display an 2-D array of intensities as a contour plot
893 map: 2-dimensional array for map
894 """
895 kws.update(dict(style='contour'))
896 _imshow(map, x=x, y=y, _larch=_larch, **kws)
898def _saveplot(fname, dpi=300, format=None, win=1, _larch=None, wxparent=None,
899 size=None, facecolor='w', edgecolor='w', quality=90,
900 image=False, **kws):
901 """formats: png (default), svg, pdf, jpeg, tiff"""
902 thisdir = os.path.abspath(os.curdir)
903 if format is None:
904 pref, suffix = os.path.splitext(fname)
905 if suffix is not None:
906 if suffix.startswith('.'):
907 suffix = suffix[1:]
908 format = suffix
909 if format is None: format = 'png'
910 format = format.lower()
911 canvas = get_display(wxparent=wxparent, win=win, size=size,
912 _larch=_larch, image=image).panel.canvas
913 if canvas is None:
914 return
915 if format in ('jpeg', 'jpg'):
916 canvas.print_jpeg(fname, quality=quality, **kws)
917 elif format in ('tiff', 'tif'):
918 canvas.print_tiff(fname, **kws)
919 elif format in ('png', 'svg', 'pdf', 'emf', 'eps'):
920 canvas.print_figure(fname, dpi=dpi, format=format,
921 facecolor=facecolor, edgecolor=edgecolor, **kws)
922 else:
923 print('unsupported image format: ', format)
924 os.chdir(thisdir)
926def _saveimg(fname, _larch=None, **kws):
927 """save image from image display"""
928 kws.update({'image':True})
929 _saveplot(fname, _larch=_larch, **kws)
931def _closeDisplays(_larch=None, **kws):
932 for display in (PLOT_DISPLAYS, IMG_DISPLAYS,
933 FITPLOT_DISPLAYS, XRF_DISPLAYS):
934 for win in display.values():
935 try:
936 win.Destroy()
937 except:
938 pass
940def get_zoomlimits(plotpanel, dgroup):
941 """save current zoom limits, to be reapplied with set_zoomlimits()"""
942 view_lims = plotpanel.get_viewlimits()
943 zoom_lims = plotpanel.conf.zoom_lims
944 out = None
945 inrange = 3
946 if len(zoom_lims) > 0:
947 if zoom_lims[-1] is not None:
948 _ax = list(zoom_lims[0].keys())[-1]
949 if all([_ax.get_xlabel() == dgroup.plot_xlabel,
950 _ax.get_ylabel() == dgroup.plot_ylabel,
951 min(dgroup.xdat) <= view_lims[1],
952 max(dgroup.xdat) >= view_lims[0],
953 min(dgroup.ydat) <= view_lims[3],
954 max(dgroup.ydat) >= view_lims[2]]):
955 out = (_ax, view_lims, zoom_lims)
956 return out
958def set_zoomlimits(plotpanel, limits, verbose=False):
959 """set zoom limits returned from get_zoomlimits()"""
960 if limits is None:
961 if verbose:
962 print("set zoom, no limits")
963 return False
964 ax, vlims, zoom_lims = limits
965 plotpanel.reset_formats()
966 if ax == plotpanel.axes:
967 try:
968 ax.set_xlim((vlims[0], vlims[1]), emit=True)
969 ax.set_ylim((vlims[2], vlims[3]), emit=True)
970 if len(plotpanel.conf.zoom_lims) == 0 and len(zoom_lims) > 0:
971 plotpanel.conf.zoom_lims = zoom_lims
972 if verbose:
973 print("set zoom, ", zoom_lims)
974 except:
975 if verbose:
976 print("set zoom, exception")
977 return False
978 return True
980def fileplot(filename, col1=1, col2=2, **kws):
981 """gnuplot-like plot of columns from a plain text column data file,
983 Arguments
984 ---------
985 filename, str: name of file to be read with `read_ascii()`
986 col1, int: index of column (starting at 1) for x-axis [1]
987 col2, int: index of column (starting at 1) for y-axis [2]
990 Examples
991 --------
992 > fileplot('xmu.dat', 1, 4, new=True)
994 Notes
995 -----
996 1. Additional keywords arguments will be forwarded to `plot()`, including
997 new = True/False
998 title, xlabel, ylabel,
999 linewidth, marker, color
1000 2. If discoverable, column labels will be used to label axes
1001 """
1002 from larch.io import read_ascii
1003 fdat = read_ascii(filename)
1004 ncols, npts = fdat.data.shape
1005 ix = max(0, col1-1)
1006 iy = max(0, col2-1)
1007 xlabel = f"col {col1}"
1008 flabel = f"col {col2}"
1009 if ix < len(fdat.array_labels):
1010 xlabel = fdat.array_labels[ix]
1011 if iy < len(fdat.array_labels):
1012 ylabel = fdat.array_labels[iy]
1014 title = f"{filename:s} {col1:d}:{col2:d}"
1015 if 'xlabel' in kws:
1016 xlabel = kws.pop('xlabel')
1017 if 'ylabel' in kws:
1018 ylabel = kws.pop('ylabel')
1019 if 'title' in kws:
1020 title = kws.pop('title')
1022 _plot(fdat.data[ix,:], fdat.data[iy,:], xlabel=xlabel, ylabel=ylabel,
1023 title=title, **kws)