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

1#!/usr/bin/env python 

2''' 

3 Plotting functions for Larch, wrapping the mplot plotting 

4 widgets which use matplotlib 

5 

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 

20 

21import larch 

22from ..utils import mkdir 

23from ..xrf import isLarchMCAGroup 

24from ..larchlib import ensuremod 

25from ..site_config import user_larchdir 

26 

27from .xrfdisplay import XRFDisplayFrame 

28 

29mplconfdir = os.path.join(user_larchdir, 'matplotlib') 

30mkdir(mplconfdir) 

31os.environ['MPLCONFIGDIR'] = mplconfdir 

32 

33from matplotlib.axes import Axes 

34HIST_DOC = Axes.hist.__doc__ 

35 

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} 

47 

48_larch_name = '_plotter' 

49 

50__DOC__ = ''' 

51General Plotting and Image Display Functions 

52 

53The functions here include (but are not limited to): 

54 

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 

61 

62imshow image display (false-color intensity image) 

63 

64xrf_plot browsable display for XRF spectra 

65''' 

66 

67MAX_WINDOWS = 25 

68MAX_CURSHIST = 100 

69 

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) 

84 

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 

89 

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) 

99 

100 self.Destroy() 

101 

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) 

108 

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) 

114 

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') 

126 

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) 

131 

132 if window not in PLOT_DISPLAYS: 

133 PLOT_DISPLAYS[window] = self 

134 

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) 

144 

145 self.Destroy() 

146 

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) 

158 

159 

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) 

164 

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') 

177 

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) 

182 

183 if window not in FITPLOT_DISPLAYS: 

184 FITPLOT_DISPLAYS[window] = self 

185 

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) 

195 

196 self.Destroy() 

197 

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) 

209 

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 

227 

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) 

232 

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() 

244 

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) 

255 

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 

267 

268 global PLOTOPTS 

269 try: 

270 PLOTOPTS = deepcopy(_larch.symtable._sys.wx.plotopts) 

271 except: 

272 pass 

273 

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] 

283 

284 

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) 

305 

306 if wintitle is not None: 

307 title = wintitle 

308 

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 

320 

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 

328 

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() 

340 

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))) 

348 

349 ddict[win] = display 

350 return display, new_display 

351 

352 

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) 

378 

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 

387 

388 

389_getDisplay = get_display # back compatibility 

390 

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]) 

395 

396 Show XRF trace of energy, data 

397 

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 

404 

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 

412 

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 

422 

423 if isLarchMCAGroup(x): 

424 mca = x 

425 y = x.counts 

426 x = x.energy 

427 

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) 

444 

445 

446def _xrf_oplot(x=None, y=None, mca=None, win=1, _larch=None, **kws): 

447 """xrf_oplot(energy, data[, win=1], options]) 

448 

449 Overplot a second XRF trace of energy, data 

450 

451 Parameters: 

452 -------------- 

453 energy : array of energies 

454 counts : array of counts 

455 mca: Group counting MCA data (rois, etc) 

456 

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') 

460 

461 See Also: xrf_plot 

462 """ 

463 _xrf_plot(x=x, y=y, mca=mca, win=win, _larch=_larch, new=False, **kws) 

464 

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]) 

468 

469 Plot 2-D trace of x, y arrays in a Plot Frame, clearing any plot currently in the Plot Frame. 

470 

471 Parameters: 

472 -------------- 

473 x : array of ordinate values 

474 y : array of abscissa values (x and y must be same size!) 

475 

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) 

488 

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 

494 

495 drawstyle: style for joining line segments 

496 

497 dy: array for error bars in y (must be same size as y!) 

498 yaxis='left'?? 

499 use_dates 

500 

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) 

515 

516def _redraw_plot(win=1, xrf=False, stacked=False, size=None, wintitle=None, 

517 _larch=None, wxparent=None): 

518 """redraw_plot(win=1) 

519 

520 redraw a plot window, especially convenient to force setting limits after 

521 multiple plot()s with delay_draw=True 

522 """ 

523 

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() 

528 

529 

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 

538 

539 plotter.panel.update_line(trace, x, y, draw=True, side=side) 

540 wx_update(_larch=_larch) 

541 

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 

549 

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)) 

557 

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]]) 

561 

562 Plot 2-D trace of x, y arrays in a Plot Frame, over-plotting any 

563 plot currently in the Plot Frame. 

564 

565 This is equivalent to 

566 plot(x, y[, win=1[, new=False[, options]]]) 

567 

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) 

573 

574def _newplot(x, y, win=1, _larch=None, wxparent=None, size=None, wintitle=None, 

575 **kws): 

576 """newplot(x, y[, win=1[, options]]) 

577 

578 Plot 2-D trace of x, y arrays in a Plot Frame, clearing any 

579 plot currently in the Plot Frame. 

580 

581 This is equivalent to 

582 plot(x, y[, win=1[, new=True[, options]]]) 

583 

584 See Also: plot, oplot 

585 """ 

586 _plot(x, y, win=win, size=size, new=True, _larch=_larch, 

587 wxparent=wxparent, wintitle=wintitle, **kws) 

588 

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) 

593 

594 add text at x, y coordinates of a plot 

595 

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') 

606 

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() 

614 

615 plotter.add_text(text, x, y, side=side, 

616 rotation=rotation, ha=ha, va=va, **kws) 

617 

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): 

623 

624 """plot_arrow(x1, y1, x2, y2, win=1, **kws) 

625 

626 draw arrow from x1, y1 to x2, y2. 

627 

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 

642 

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) 

653 

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): 

656 

657 """plot_marker(x, y, marker='o', size=4, color='black') 

658 

659 draw a marker at x, y 

660 

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'] 

668 

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) 

678 

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) 

682 

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() 

701 

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) 

705 

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() 

724 

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) 

728 

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) 

732 

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. 

736 

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) 

747 

748 xval = symtable.get_symbol(xsym, create=True) 

749 yval = symtable.get_symbol(ysym, create=True) 

750 symtable.set_symbol(xsym, None) 

751 

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 

758 

759 # restore value on timeout 

760 if symtable.get_symbol(xsym, create=False) is None: 

761 symtable.set_symbol(xsym, xval) 

762 

763 return (symtable.get_symbol(xsym), symtable.get_symbol(ysym)) 

764 

765def last_cursor_pos(win=None, _larch=None): 

766 """return most recent cursor position -- 'last click on plot' 

767 

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 

770 

771 Arguments 

772 --------- 

773 win (int or None) index of window to get cursor position [None, all windows] 

774 

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) 

787 

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 

800 

801 

802def _scatterplot(x,y, win=1, _larch=None, wxparent=None, size=None, 

803 force_draw=True, **kws): 

804 """scatterplot(x, y[, win=1], options]) 

805 

806 Plot x, y values as a scatterplot. Parameters are very similar to 

807 those of plot() 

808 

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) 

818 

819 

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) 

823 

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. 

826 

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. 

829 

830 Parameters are the same as for plot() and oplot() 

831 

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() 

850 

851 

852def _hist(x, bins=10, win=1, new=False, 

853 _larch=None, wxparent=None, size=None, force_draw=True, *args, **kws): 

854 

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() 

861 

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 

867 

868 

869_hist.__doc__ = """ 

870 hist(x, bins, win=1, options) 

871 

872 %s 

873""" % (HIST_DOC) 

874 

875 

876def _imshow(map, x=None, y=None, colormap=None, win=1, _larch=None, 

877 wxparent=None, size=None, **kws): 

878 """imshow(map[, options]) 

879 

880 Display an 2-D array of intensities as a false-color map 

881 

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) 

887 

888def _contour(map, x=None, y=None, _larch=None, **kws): 

889 """contour(map[, options]) 

890 

891 Display an 2-D array of intensities as a contour plot 

892 

893 map: 2-dimensional array for map 

894 """ 

895 kws.update(dict(style='contour')) 

896 _imshow(map, x=x, y=y, _larch=_larch, **kws) 

897 

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) 

925 

926def _saveimg(fname, _larch=None, **kws): 

927 """save image from image display""" 

928 kws.update({'image':True}) 

929 _saveplot(fname, _larch=_larch, **kws) 

930 

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 

939 

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 

957 

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 

979 

980def fileplot(filename, col1=1, col2=2, **kws): 

981 """gnuplot-like plot of columns from a plain text column data file, 

982 

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] 

988 

989 

990 Examples 

991 -------- 

992 > fileplot('xmu.dat', 1, 4, new=True) 

993 

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] 

1013 

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') 

1021 

1022 _plot(fdat.data[ix,:], fdat.data[iy,:], xlabel=xlabel, ylabel=ylabel, 

1023 title=title, **kws)