Coverage for larch/wxlib/plotter.py: 16%
573 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 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 pathlib import Path
18from copy import deepcopy
19from wxmplot import PlotFrame, ImageFrame, StackedPlotFrame
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 = Path(user_larchdir, 'matplotlib').as_posix()
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, position=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]
284 win = max(1, min(MAX_WINDOWS, int(abs(win))))
285 title = 'Plot Window %i' % win
286 symname = '%s.plot%i' % (_larch_name, win)
287 creator = PlotDisplay
288 display_dict = PLOT_DISPLAYS
289 if image:
290 creator = ImageDisplay
291 display_dict = IMG_DISPLAYS
292 title = 'Image Window %i' % win
293 symname = '%s.img%i' % (_larch_name, win)
294 elif xrf:
295 creator = XRFDisplay
296 display_dict = XRF_DISPLAYS
297 title = 'XRF Display Window %i' % win
298 symname = '%s.xrf%i' % (_larch_name, win)
299 elif stacked:
300 creator = StackedPlotDisplay
301 display_dict = FITPLOT_DISPLAYS
302 title = 'Fit Plot Window %i' % win
303 symname = '%s.fitplot%i' % (_larch_name, win)
305 if wintitle is not None:
306 title = wintitle
308 def _get_disp(symname, creator, win, ddict, wxparent,
309 size, position, height, width, _larch):
310 wxapp = wx.GetApp()
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 = wxapp.GetTopWindow()
340 if position is not None:
341 display.SetPosition(position)
342 elif parent is not None:
343 xpos, ypos = parent.GetPosition()
344 xsiz, ysiz = parent.GetSize()
345 x = xpos + xsiz*0.75
346 y = ypos + ysiz*0.75
347 if len(PLOT_DISPLAYS) > 0:
348 try:
349 xpos, ypos = PLOT_DISPLAYS[1].GetPosition()
350 xsiz, ysiz = PLOT_DISPLAYS[1].GetSize()
351 except:
352 pass
353 off = 0.20*(win-1)
354 x = max(25, xpos + xsiz*off)
355 y = max(25, ypos + ysiz*off)
356 global DISPLAY_LIMITS
357 dlims = DISPLAY_LIMITS
358 if dlims is None:
359 dlims = [0, 5000, 0, 5000]
360 if y+0.75*ysiz > dlims[3]:
361 y = 40+max(40, 40+ysiz*(off-0.5))
362 if x+0.75*xsiz > dlims[1]:
363 x = 20+max(10, 10+xpos+xsiz*(off-0.5))
364 display.SetPosition((int(x), int(y)))
365 ddict[win] = display
366 return display, new_display
369 display, isnew = _get_disp(symname, creator, win, display_dict, wxparent,
370 size, position, height, width, _larch)
371 if isnew and creator in (PlotDisplay, StackedPlotDisplay):
372 if theme is not None:
373 PLOTOPTS['theme'] = theme
374 if show_grid is not None:
375 PLOTOPTS['show_grid'] = show_grid
376 if show_fullbox is not None:
377 PLOTOPTS['show_fullbox'] = show_fullbox
378 if linewidth is not None:
379 PLOTOPTS['linewidth'] = linewidth
380 if markersize is not None:
381 PLOTOPTS['markersize'] = markersize
382 panels = [display.panel]
383 if creator == StackedPlotDisplay:
384 panels.append(display.panel_bot)
385 for panel in panels:
386 conf = panel.conf
387 conf.set_theme(theme=PLOTOPTS['theme'])
388 conf.enable_grid(PLOTOPTS['show_grid'])
389 conf.axes_style = 'box' if PLOTOPTS['show_fullbox'] else 'open'
390 for i in range(16):
391 conf.set_trace_linewidth(PLOTOPTS['linewidth'], trace=i)
392 try:
393 display.SetTitle(title)
395 except:
396 display_dict.pop(win)
397 display, isnew = _get_disp(symname, creator, win, display_dict, wxparent,
398 size, position, _larch)
399 display.SetTitle(title)
400 if hasattr(_larch, 'symtable'):
401 _larch.symtable.set_symbol(symname, display)
402 return display
405_getDisplay = get_display # back compatibility
407def _xrf_plot(x=None, y=None, mca=None, win=1, new=True, as_mca2=False, _larch=None,
408 wxparent=None, size=None, side='left', force_draw=True, wintitle=None,
409 **kws):
410 """xrf_plot(energy, data[, win=1], options])
412 Show XRF trace of energy, data
414 Parameters:
415 --------------
416 energy : array of energies
417 counts : array of counts
418 mca: Group counting MCA data (rois, etc)
419 as_mca2: use mca as background MCA
421 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
422 new: flag (True/False, default False) for whether to start a new plot.
423 color: color for trace (name such as 'red', or '#RRGGBB' hex string)
424 style: trace linestyle (one of 'solid', 'dashed', 'dotted', 'dot-dash')
425 linewidth: integer width of line
426 marker: symbol to draw at each point ('+', 'o', 'x', 'square', etc)
427 markersize: integer size of marker
429 See Also: xrf_oplot, plot
430 """
431 plotter = get_display(wxparent=wxparent, win=win, size=size,
432 _larch=_larch, wintitle=wintitle, xrf=True)
433 if plotter is None:
434 return
435 plotter.Raise()
436 if x is None:
437 return
439 if isLarchMCAGroup(x):
440 mca = x
441 y = x.counts
442 x = x.energy
444 if as_mca2:
445 if isLarchMCAGroup(mca):
446 plotter.add_mca(mca, as_mca2=True, plot=False)
447 plotter.plotmca(mca, as_mca2=True, **kws)
448 elif y is not None:
449 plotter.oplot(x, y, mca=mca, as_mca2=True, **kws)
450 elif new:
451 if isLarchMCAGroup(mca):
452 plotter.add_mca(mca, plot=False)
453 plotter.plotmca(mca, **kws)
454 elif y is not None:
455 plotter.plot(x, y, mca=mca, **kws)
456 elif y is not None:
457 if isLarchMCAGroup(mca):
458 plotter.add_mca(mca, plot=False)
459 plotter.oplot(x, y, mca=mca, **kws)
462def _xrf_oplot(x=None, y=None, mca=None, win=1, _larch=None, **kws):
463 """xrf_oplot(energy, data[, win=1], options])
465 Overplot a second XRF trace of energy, data
467 Parameters:
468 --------------
469 energy : array of energies
470 counts : array of counts
471 mca: Group counting MCA data (rois, etc)
473 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
474 color: color for trace (name such as 'red', or '#RRGGBB' hex string)
475 style: trace linestyle (one of 'solid', 'dashed', 'dotted', 'dot-dash')
477 See Also: xrf_plot
478 """
479 _xrf_plot(x=x, y=y, mca=mca, win=win, _larch=_larch, new=False, **kws)
481def _plot(x,y, win=1, new=False, _larch=None, wxparent=None, size=None,
482 xrf=False, stacked=False, force_draw=True, side='left',
483 wintitle=None, **kws):
484 """plot(x, y[, win=1], options])
486 Plot 2-D trace of x, y arrays in a Plot Frame, clearing any plot currently in the Plot Frame.
488 Parameters:
489 --------------
490 x : array of ordinate values
491 y : array of abscissa values (x and y must be same size!)
493 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
494 new: flag (True/False, default False) for whether to start a new plot.
495 force_draw: flag (True/False, default Tree) for whether force a draw.
496 This will take a little extra time, and is not needed when
497 typing at the command-line, but is needed for plots to update
498 from inside scripts.
499 label: label for trace
500 title: title for Plot
501 xlabel: x-axis label
502 ylabel: y-axis label
503 ylog_scale: whether to show y-axis as log-scale (True or False)
504 grid: whether to draw background grid (True or False)
506 color: color for trace (name such as 'red', or '#RRGGBB' hex string)
507 style: trace linestyle (one of 'solid', 'dashed', 'dotted', 'dot-dash')
508 linewidth: integer width of line
509 marker: symbol to draw at each point ('+', 'o', 'x', 'square', etc)
510 markersize: integer size of marker
512 drawstyle: style for joining line segments
514 dy: array for error bars in y (must be same size as y!)
515 yaxis='left'??
516 use_dates
518 See Also: oplot, newplot
519 """
520 plotter = get_display(wxparent=wxparent, win=win, size=size,
521 xrf=xrf, stacked=stacked,
522 wintitle=wintitle, _larch=_larch)
523 if plotter is None:
524 return
525 plotter.Raise()
526 if new:
527 plotter.plot(x, y, side=side, **kws)
528 else:
529 plotter.oplot(x, y, side=side, **kws)
530 if force_draw:
531 wx_update(_larch=_larch)
533def _redraw_plot(win=1, xrf=False, stacked=False, size=None, wintitle=None,
534 _larch=None, wxparent=None):
535 """redraw_plot(win=1)
537 redraw a plot window, especially convenient to force setting limits after
538 multiple plot()s with delay_draw=True
539 """
541 plotter = get_display(wxparent=wxparent, win=win, size=size,
542 xrf=xrf, stacked=stacked,
543 wintitle=wintitle, _larch=_larch)
544 plotter.panel.unzoom_all()
547def _update_trace(x, y, trace=1, win=1, _larch=None, wxparent=None,
548 side='left', redraw=False, **kws):
549 """update a plot trace with new data, avoiding complete redraw"""
550 plotter = get_display(wxparent=wxparent, win=win, _larch=_larch)
551 if plotter is None:
552 return
553 plotter.Raise()
554 trace -= 1 # wxmplot counts traces from 0
556 plotter.panel.update_line(trace, x, y, draw=True, side=side)
557 wx_update(_larch=_larch)
559def wx_update(_larch=None, **kws):
560 try:
561 _larch.symtable.get_symbol('_sys.wx.ping')(timeout=0.002)
562 except:
563 pass
565def _plot_setlimits(xmin=None, xmax=None, ymin=None, ymax=None, win=1, wxparent=None,
566 _larch=None):
567 """set plot view limits for plot in window `win`"""
568 plotter = get_display(wxparent=wxparent, win=win, _larch=_larch)
569 if plotter is None:
570 return
571 plotter.panel.set_xylims((xmin, xmax, ymin, ymax))
573def _oplot(x, y, win=1, _larch=None, wxparent=None, xrf=False, stacked=False,
574 size=None, **kws):
575 """oplot(x, y[, win=1[, options]])
577 Plot 2-D trace of x, y arrays in a Plot Frame, over-plotting any
578 plot currently in the Plot Frame.
580 This is equivalent to
581 plot(x, y[, win=1[, new=False[, options]]])
583 See Also: plot, newplot
584 """
585 kws['new'] = False
586 _plot(x, y, win=win, size=size, xrf=xrf, stacked=stacked,
587 wxparent=wxparent, _larch=_larch, **kws)
589def _newplot(x, y, win=1, _larch=None, wxparent=None, size=None, wintitle=None,
590 **kws):
591 """newplot(x, y[, win=1[, options]])
593 Plot 2-D trace of x, y arrays in a Plot Frame, clearing any
594 plot currently in the Plot Frame.
596 This is equivalent to
597 plot(x, y[, win=1[, new=True[, options]]])
599 See Also: plot, oplot
600 """
601 _plot(x, y, win=win, size=size, new=True, _larch=_larch,
602 wxparent=wxparent, wintitle=wintitle, **kws)
604def _plot_text(text, x, y, win=1, side='left', size=None,
605 stacked=False, xrf=False, rotation=None, ha='left', va='center',
606 _larch=None, wxparent=None, **kws):
607 """plot_text(text, x, y, win=1, options)
609 add text at x, y coordinates of a plot
611 Parameters:
612 --------------
613 text: text to draw
614 x: x position of text
615 y: y position of text
616 win: index of Plot Frame (0, 1, etc). May create a new Plot Frame.
617 side: which axis to use ('left' or 'right') for coordinates.
618 rotation: text rotation. angle in degrees or 'vertical' or 'horizontal'
619 ha: horizontal alignment ('left', 'center', 'right')
620 va: vertical alignment ('top', 'center', 'bottom', 'baseline')
622 See Also: plot, oplot, plot_arrow
623 """
624 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
625 stacked=stacked, _larch=_larch)
626 if plotter is None:
627 return
628 plotter.Raise()
630 plotter.add_text(text, x, y, side=side,
631 rotation=rotation, ha=ha, va=va, **kws)
633def _plot_arrow(x1, y1, x2, y2, win=1, side='left',
634 shape='full', color='black',
635 width=0.00, head_width=0.05, head_length=0.25,
636 _larch=None, wxparent=None, stacked=False, xrf=False,
637 size=None, **kws):
639 """plot_arrow(x1, y1, x2, y2, win=1, **kws)
641 draw arrow from x1, y1 to x2, y2.
643 Parameters:
644 --------------
645 x1: starting x coordinate
646 y1: starting y coordinate
647 x2: ending x coordinate
648 y2: ending y coordinate
649 side: which axis to use ('left' or 'right') for coordinates.
650 shape: arrow head shape ('full', 'left', 'right')
651 color: arrow color ('black')
652 width: width of arrow line (in points. default=0.0)
653 head_width: width of arrow head (in points. default=0.05)
654 head_length: length of arrow head (in points. default=0.25)
655 overhang: amount the arrow is swept back (in points. default=0)
656 win: window to draw too
658 See Also: plot, oplot, plot_text
659 """
660 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
661 stacked=stacked, _larch=_larch)
662 if plotter is None:
663 return
664 plotter.Raise()
665 plotter.add_arrow(x1, y1, x2, y2, side=side, shape=shape,
666 color=color, width=width, head_length=head_length,
667 head_width=head_width, **kws)
669def _plot_marker(x, y, marker='o', size=4, color='black', label='_nolegend_',
670 _larch=None, wxparent=None, win=1, xrf=False, stacked=False, **kws):
672 """plot_marker(x, y, marker='o', size=4, color='black')
674 draw a marker at x, y
676 Parameters:
677 -----------
678 x: x coordinate
679 y: y coordinate
680 marker: symbol to draw at each point ('+', 'o', 'x', 'square', etc) ['o']
681 size: symbol size [4]
682 color: color ['black']
684 See Also: plot, oplot, plot_text
685 """
686 plotter = get_display(wxparent=wxparent, win=win, size=None, xrf=xrf,
687 stacked=stacked, _larch=_larch)
688 if plotter is None:
689 return
690 plotter.Raise()
691 plotter.oplot([x], [y], marker=marker, markersize=size, label=label,
692 color=color, _larch=_larch, wxparent=wxparent, **kws)
694def _plot_axhline(y, xmin=0, xmax=1, win=1, wxparent=None, xrf=False,
695 stacked=False, size=None, delay_draw=False, _larch=None, **kws):
696 """plot_axhline(y, xmin=None, ymin=None, **kws)
698 plot a horizontal line spanning the plot axes
699 Parameters:
700 --------------
701 y: y position of line
702 xmin: starting x fraction (window units -- not user units!)
703 xmax: ending x fraction (window units -- not user units!)
704 See Also: plot, oplot, plot_arrow
705 """
706 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
707 stacked=stacked, _larch=_larch)
708 if plotter is None:
709 return
710 plotter.Raise()
711 if 'label' not in kws:
712 kws['label'] = '_nolegend_'
713 plotter.panel.axes.axhline(y, xmin=xmin, xmax=xmax, **kws)
714 if delay_draw:
715 plotter.panel.canvas.draw()
717def _plot_axvline(x, ymin=0, ymax=1, win=1, wxparent=None, xrf=False,
718 stacked=False, size=None, delay_draw=False, _larch=None, **kws):
719 """plot_axvline(y, xmin=None, ymin=None, **kws)
721 plot a vertical line spanning the plot axes
722 Parameters:
723 --------------
724 x: x position of line
725 ymin: starting y fraction (window units -- not user units!)
726 ymax: ending y fraction (window units -- not user units!)
727 See Also: plot, oplot, plot_arrow
728 """
729 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
730 stacked=stacked, _larch=_larch)
731 if plotter is None:
732 return
733 plotter.Raise()
734 if 'label' not in kws:
735 kws['label'] = '_nolegend_'
736 plotter.panel.axes.axvline(x, ymin=ymin, ymax=ymax, **kws)
737 if not delay_draw:
738 plotter.panel.canvas.draw()
740def _getcursor(win=1, timeout=15, _larch=None, wxparent=None, size=None,
741 xrf=False, stacked=False, **kws):
742 """get_cursor(win=1, timeout=30)
744 waits (up to timeout) for cursor click in selected plot window, and
745 returns x, y position of cursor. On timeout, returns the last known
746 cursor position, or (None, None)
748 Note that _plotter.plotWIN_x and _plotter.plotWIN_y will be updated,
749 with each cursor click, and so can be used to read the last cursor
750 position without blocking.
752 For a more consistent programmatic approach, this routine can be called
753 with timeout <= 0 to read the most recently clicked cursor position.
754 """
755 plotter = get_display(wxparent=wxparent, win=win, size=size, xrf=xrf,
756 stacked=stacked, _larch=_larch)
757 if plotter is None:
758 return
759 symtable = ensuremod(_larch, _larch_name)
760 xsym = '%s.plot%i_x' % (_larch_name, win)
761 ysym = '%s.plot%i_y' % (_larch_name, win)
763 xval = symtable.get_symbol(xsym, create=True)
764 yval = symtable.get_symbol(ysym, create=True)
765 symtable.set_symbol(xsym, None)
767 t0 = time.time()
768 while time.time() - t0 < timeout:
769 wx_update(_larch=_larch)
770 time.sleep(0.05)
771 if symtable.get_symbol(xsym) is not None:
772 break
774 # restore value on timeout
775 if symtable.get_symbol(xsym, create=False) is None:
776 symtable.set_symbol(xsym, xval)
778 return (symtable.get_symbol(xsym), symtable.get_symbol(ysym))
780def last_cursor_pos(win=None, _larch=None):
781 """return most recent cursor position -- 'last click on plot'
783 By default, this returns the last postion for all plot windows.
784 If win is not `None`, the last position for that window will be returned
786 Arguments
787 ---------
788 win (int or None) index of window to get cursor position [None, all windows]
790 Returns
791 -------
792 x, y coordinates of most recent cursor click, in user units
793 """
794 if hasattr(_larch, 'symtable'):
795 plotter = _larch.symtable._plotter
796 else:
797 return None, None
798 histories = []
799 for attr in dir(plotter):
800 if attr.endswith('_cursor_hist'):
801 histories.append(attr)
803 if win is not None:
804 tmp = []
805 for attr in histories:
806 if attr.startswith('plot%d_' % win):
807 tmp.append(attr)
808 histories = tmp
809 _x, _y, _t = None, None, 0
810 for hist in histories:
811 for px, py, pt in getattr(plotter, hist, [None, None, -1]):
812 if pt > _t and px is not None:
813 _x, _y, _t = px, py, pt
814 return _x, _y
817def _scatterplot(x,y, win=1, _larch=None, wxparent=None, size=None,
818 force_draw=True, **kws):
819 """scatterplot(x, y[, win=1], options])
821 Plot x, y values as a scatterplot. Parameters are very similar to
822 those of plot()
824 See Also: plot, newplot
825 """
826 plotter = get_display(wxparent=wxparent, win=win, size=size, _larch=_larch)
827 if plotter is None:
828 return
829 plotter.Raise()
830 plotter.scatterplot(x, y, **kws)
831 if force_draw:
832 wx_update(_larch=_larch)
835def _fitplot(x, y, y2=None, panel='top', label=None, label2=None, win=1,
836 _larch=None, wxparent=None, size=None, **kws):
837 """fit_plot(x, y, y2=None, win=1, options)
839 Plot x, y values in the top of a StackedPlot. If y2 is not None, then x, y2 values
840 will also be plotted in the top frame, and the residual (y-y2) in the bottom panel.
842 By default, arrays will be plotted in the top panel, and you must
843 specify `panel='bot'` to plot an array in the bottom panel.
845 Parameters are the same as for plot() and oplot()
847 See Also: plot, newplot
848 """
849 plotter = get_display(wxparent=wxparent, win=win, size=size,
850 stacked=True, _larch=_larch)
851 if plotter is None:
852 return
853 plotter.Raise()
854 plotter.plot(x, y, panel='top', label=label, **kws)
855 if y2 is not None:
856 kws.update({'label': label2})
857 plotter.oplot(x, y2, panel='top', **kws)
858 plotter.plot(x, y2-y, panel='bot')
859 plotter.panel.conf.set_margins(top=0.15, bottom=0.01,
860 left=0.15, right=0.05)
861 plotter.panel.unzoom_all()
862 plotter.panel_bot.conf.set_margins(top=0.01, bottom=0.35,
863 left=0.15, right=0.05)
864 plotter.panel_bot.unzoom_all()
867def _hist(x, bins=10, win=1, new=False,
868 _larch=None, wxparent=None, size=None, force_draw=True, *args, **kws):
870 plotter = get_display(wxparent=wxparent, win=win, size=size, _larch=_larch)
871 if plotter is None:
872 return
873 plotter.Raise()
874 if new:
875 plotter.panel.axes.clear()
877 out = plotter.panel.axes.hist(x, bins=bins, **kws)
878 plotter.panel.canvas.draw()
879 if force_draw:
880 wx_update(_larch=_larch)
881 return out
884_hist.__doc__ = """
885 hist(x, bins, win=1, options)
887 %s
888""" % (HIST_DOC)
891def _imshow(map, x=None, y=None, colormap=None, win=1, _larch=None,
892 wxparent=None, size=None, **kws):
893 """imshow(map[, options])
895 Display an 2-D array of intensities as a false-color map
897 map: 2-dimensional array for map
898 """
899 img = get_display(wxparent=wxparent, win=win, size=size, _larch=_larch, image=True)
900 if img is not None:
901 img.display(map, x=x, y=y, colormap=colormap, **kws)
903def _contour(map, x=None, y=None, _larch=None, **kws):
904 """contour(map[, options])
906 Display an 2-D array of intensities as a contour plot
908 map: 2-dimensional array for map
909 """
910 kws.update(dict(style='contour'))
911 _imshow(map, x=x, y=y, _larch=_larch, **kws)
913def _saveplot(fname, dpi=300, format=None, win=1, _larch=None, wxparent=None,
914 size=None, facecolor='w', edgecolor='w', quality=90,
915 image=False, **kws):
916 """formats: png (default), svg, pdf, jpeg, tiff"""
917 thisdir = Path.cwd().as_posix()
918 if format is None:
919 suffix = Path(fname).name
920 if suffix is not None:
921 if suffix.startswith('.'):
922 suffix = suffix[1:]
923 format = suffix
924 if format is None: format = 'png'
925 format = format.lower()
926 canvas = get_display(wxparent=wxparent, win=win, size=size,
927 _larch=_larch, image=image).panel.canvas
928 if canvas is None:
929 return
930 if format in ('jpeg', 'jpg'):
931 canvas.print_jpeg(fname, quality=quality, **kws)
932 elif format in ('tiff', 'tif'):
933 canvas.print_tiff(fname, **kws)
934 elif format in ('png', 'svg', 'pdf', 'emf', 'eps'):
935 canvas.print_figure(fname, dpi=dpi, format=format,
936 facecolor=facecolor, edgecolor=edgecolor, **kws)
937 else:
938 print('unsupported image format: ', format)
939 os.chdir(thisdir)
941def _saveimg(fname, _larch=None, **kws):
942 """save image from image display"""
943 kws.update({'image':True})
944 _saveplot(fname, _larch=_larch, **kws)
946def _closeDisplays(_larch=None, **kws):
947 for display in (PLOT_DISPLAYS, IMG_DISPLAYS,
948 FITPLOT_DISPLAYS, XRF_DISPLAYS):
949 for win in display.values():
950 try:
951 win.Destroy()
952 except:
953 pass
955def get_zoomlimits(plotpanel, dgroup):
956 """save current zoom limits, to be reapplied with set_zoomlimits()"""
957 view_lims = plotpanel.get_viewlimits()
958 zoom_lims = plotpanel.conf.zoom_lims
959 out = None
960 inrange = 3
961 if len(zoom_lims) > 0:
962 if zoom_lims[-1] is not None:
963 _ax = list(zoom_lims[0].keys())[-1]
964 if all([_ax.get_xlabel() == dgroup.plot_xlabel,
965 _ax.get_ylabel() == dgroup.plot_ylabel,
966 min(dgroup.xplot) <= view_lims[1],
967 max(dgroup.xplot) >= view_lims[0],
968 min(dgroup.yplot) <= view_lims[3],
969 max(dgroup.yplot) >= view_lims[2]]):
970 out = (_ax, view_lims, zoom_lims)
971 return out
973def set_zoomlimits(plotpanel, limits, verbose=False):
974 """set zoom limits returned from get_zoomlimits()"""
975 if limits is None:
976 if verbose:
977 print("set zoom, no limits")
978 return False
979 ax, vlims, zoom_lims = limits
980 plotpanel.reset_formats()
981 if ax == plotpanel.axes:
982 try:
983 ax.set_xlim((vlims[0], vlims[1]), emit=True)
984 ax.set_ylim((vlims[2], vlims[3]), emit=True)
985 if len(plotpanel.conf.zoom_lims) == 0 and len(zoom_lims) > 0:
986 plotpanel.conf.zoom_lims = zoom_lims
987 if verbose:
988 print("set zoom, ", zoom_lims)
989 except:
990 if verbose:
991 print("set zoom, exception")
992 return False
993 return True
995def fileplot(filename, col1=1, col2=2, **kws):
996 """gnuplot-like plot of columns from a plain text column data file,
998 Arguments
999 ---------
1000 filename, str: name of file to be read with `read_ascii()`
1001 col1, int: index of column (starting at 1) for x-axis [1]
1002 col2, int: index of column (starting at 1) for y-axis [2]
1005 Examples
1006 --------
1007 > fileplot('xmu.dat', 1, 4, new=True)
1009 Notes
1010 -----
1011 1. Additional keywords arguments will be forwarded to `plot()`, including
1012 new = True/False
1013 title, xlabel, ylabel,
1014 linewidth, marker, color
1015 2. If discoverable, column labels will be used to label axes
1016 """
1017 from larch.io import read_ascii
1018 fdat = read_ascii(filename)
1019 ncols, npts = fdat.data.shape
1020 ix = max(0, col1-1)
1021 iy = max(0, col2-1)
1022 xlabel = f"col {col1}"
1023 flabel = f"col {col2}"
1024 if ix < len(fdat.array_labels):
1025 xlabel = fdat.array_labels[ix]
1026 if iy < len(fdat.array_labels):
1027 ylabel = fdat.array_labels[iy]
1029 title = f"{filename:s} {col1:d}:{col2:d}"
1030 if 'xlabel' in kws:
1031 xlabel = kws.pop('xlabel')
1032 if 'ylabel' in kws:
1033 ylabel = kws.pop('ylabel')
1034 if 'title' in kws:
1035 title = kws.pop('title')
1037 _plot(fdat.data[ix,:], fdat.data[iy,:], xlabel=xlabel, ylabel=ylabel,
1038 title=title, **kws)