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

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 pathlib import Path 

18from copy import deepcopy 

19from wxmplot import PlotFrame, ImageFrame, StackedPlotFrame 

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 = Path(user_larchdir, 'matplotlib').as_posix() 

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

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

304 

305 if wintitle is not None: 

306 title = wintitle 

307 

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 

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

367 

368 

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) 

394 

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 

403 

404 

405_getDisplay = get_display # back compatibility 

406 

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

411 

412 Show XRF trace of energy, data 

413 

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 

420 

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 

428 

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 

438 

439 if isLarchMCAGroup(x): 

440 mca = x 

441 y = x.counts 

442 x = x.energy 

443 

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) 

460 

461 

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

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

464 

465 Overplot a second XRF trace of energy, data 

466 

467 Parameters: 

468 -------------- 

469 energy : array of energies 

470 counts : array of counts 

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

472 

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

476 

477 See Also: xrf_plot 

478 """ 

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

480 

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

485 

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

487 

488 Parameters: 

489 -------------- 

490 x : array of ordinate values 

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

492 

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) 

505 

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 

511 

512 drawstyle: style for joining line segments 

513 

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

515 yaxis='left'?? 

516 use_dates 

517 

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) 

532 

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

534 _larch=None, wxparent=None): 

535 """redraw_plot(win=1) 

536 

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

538 multiple plot()s with delay_draw=True 

539 """ 

540 

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

545 

546 

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 

555 

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

557 wx_update(_larch=_larch) 

558 

559def wx_update(_larch=None, **kws): 

560 try: 

561 _larch.symtable.get_symbol('_sys.wx.ping')(timeout=0.002) 

562 except: 

563 pass 

564 

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

572 

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

576 

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

578 plot currently in the Plot Frame. 

579 

580 This is equivalent to 

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

582 

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) 

588 

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

590 **kws): 

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

592 

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

594 plot currently in the Plot Frame. 

595 

596 This is equivalent to 

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

598 

599 See Also: plot, oplot 

600 """ 

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

602 wxparent=wxparent, wintitle=wintitle, **kws) 

603 

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) 

608 

609 add text at x, y coordinates of a plot 

610 

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

621 

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

629 

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

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

632 

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

638 

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

640 

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

642 

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 

657 

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) 

668 

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

671 

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

673 

674 draw a marker at x, y 

675 

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

683 

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) 

693 

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) 

697 

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

716 

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) 

720 

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

739 

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) 

743 

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) 

747 

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. 

751 

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) 

762 

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

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

765 symtable.set_symbol(xsym, None) 

766 

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 

773 

774 # restore value on timeout 

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

776 symtable.set_symbol(xsym, xval) 

777 

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

779 

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

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

782 

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 

785 

786 Arguments 

787 --------- 

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

789 

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) 

802 

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 

815 

816 

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

818 force_draw=True, **kws): 

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

820 

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

822 those of plot() 

823 

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) 

833 

834 

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) 

838 

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. 

841 

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. 

844 

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

846 

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

865 

866 

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

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

869 

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

876 

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 

882 

883 

884_hist.__doc__ = """ 

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

886 

887 %s 

888""" % (HIST_DOC) 

889 

890 

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

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

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

894 

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

896 

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) 

902 

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

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

905 

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

907 

908 map: 2-dimensional array for map 

909 """ 

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

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

912 

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) 

940 

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

942 """save image from image display""" 

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

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

945 

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 

954 

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 

972 

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 

994 

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

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

997 

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] 

1003 

1004 

1005 Examples 

1006 -------- 

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

1008 

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] 

1028 

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

1036 

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

1038 title=title, **kws)