Skip to content

Commit 820fe17

Browse files
committed
Add code based on PR #285 that preserves plot x-range & y-scaling across refinements & repurposes Home button
1 parent 633f1bd commit 820fe17

2 files changed

Lines changed: 219 additions & 20 deletions

File tree

GSASII/GSASIIplot.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,27 @@ def _update_view(self):
671671
wx.CallAfter(*self.updateActions)
672672
Toolbar._update_view(self)
673673

674+
def home(self, *args):
675+
'''Override home button to clear saved GROUP plot limits and trigger replot.
676+
This ensures that pressing home resets to full data range while retaining x-units.
677+
For GROUP plots, we need to replot rather than use matplotlib's home because
678+
matplotlib's home would restore the original shared limits, not per-histogram limits.
679+
(based on MG/Cl Sonnet code)
680+
'''
681+
G2frame = wx.GetApp().GetMainTopWindow()
682+
# Check if we're in GROUP plot mode - if so, clear saved GROUP
683+
# plot x-limits and trigger a replot
684+
if self.arrows.get('_groupMode'):
685+
# PlotPatterns will use full data range
686+
if hasattr(G2frame, 'groupXlim'):
687+
del G2frame.groupXlim
688+
# Trigger a full replot for GROUP plots
689+
if self.updateActions:
690+
wx.CallAfter(*self.updateActions)
691+
return
692+
# For non-GROUP plots, call the parent's home method
693+
Toolbar.home(self, *args)
694+
674695
def AnyActive(self):
675696
for Itool in range(self.GetToolsCount()):
676697
if self.GetToolState(self.GetToolByPos(Itool).GetId()):

GSASII/GSASIIpwdplot.py

Lines changed: 198 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,10 @@ def OnPlotKeyPress(event):
229229
Page.plotStyle['flTicks'] = (Page.plotStyle.get('flTicks',0)+1)%3
230230
elif event.key == 'x' and groupName is not None: # share X axis scale for Pattern Groups
231231
plotOpt['sharedX'] = not plotOpt['sharedX']
232-
if not plotOpt['sharedX']: # reset scale
233-
newPlot = True
232+
# Clear saved x-limits when toggling sharedX mode (MG/Cl Sonnet)
233+
if hasattr(G2frame, 'groupXlim'):
234+
del G2frame.groupXlim
235+
newPlot = True
234236
elif event.key == 'x' and 'PWDR' in plottype:
235237
Page.plotStyle['exclude'] = not Page.plotStyle['exclude']
236238
elif event.key == '.':
@@ -421,6 +423,13 @@ def OnPlotKeyPress(event):
421423
newPlot = True
422424
if 'PWDR' in plottype or plottype.startswith('GROUP'):
423425
Page.plotStyle['qPlot'] = not Page.plotStyle['qPlot']
426+
# switching from d to Q
427+
if (Page.plotStyle['qPlot'] and
428+
Page.plotStyle['dPlot'] and
429+
getattr(G2frame, 'groupXlim', None) is not None):
430+
G2frame.groupXlim = (
431+
2.0 * np.pi / G2frame.groupXlim[1], # Q_max -> d_min
432+
2.0 * np.pi / G2frame.groupXlim[0]) # Q_min -> d_max
424433
Page.plotStyle['dPlot'] = False
425434
Page.plotStyle['chanPlot'] = False
426435
elif plottype in ['SASD','REFD']:
@@ -436,6 +445,13 @@ def OnPlotKeyPress(event):
436445
elif event.key == 't' and ('PWDR' in plottype or plottype.startswith('GROUP')):
437446
newPlot = True
438447
Page.plotStyle['dPlot'] = not Page.plotStyle['dPlot']
448+
# switching from Q to d
449+
if (Page.plotStyle['qPlot'] and
450+
Page.plotStyle['dPlot'] and
451+
getattr(G2frame, 'groupXlim', None) is not None):
452+
G2frame.groupXlim = (
453+
2.0 * np.pi / G2frame.groupXlim[1], # Q_min <- d_max
454+
2.0 * np.pi / G2frame.groupXlim[0]) # Q_max <- d_min
439455
Page.plotStyle['qPlot'] = False
440456
Page.plotStyle['chanPlot'] = False
441457
elif event.key == 'm':
@@ -1490,7 +1506,10 @@ def refPlotUpdate(Histograms,cycle=None,restore=False):
14901506
'''
14911507
if restore:
14921508
(G2frame.SinglePlot,G2frame.Contour,G2frame.Weight,
1493-
G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot']) = savedSettings
1509+
G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'],
1510+
Page.plotStyle['qPlot'],Page.plotStyle['dPlot']) = savedSettings
1511+
# Also save to G2frame so settings survive Page recreation during ResetPlots (MG/Cl Sonnet)
1512+
G2frame.savedPlotStyle = copy.copy(Page.plotStyle)
14941513
return
14951514

14961515
if plottingItem not in Histograms:
@@ -1704,7 +1723,70 @@ def drawTicks(Phases,phaseList,group=False):
17041723
Plot.axvline(xt,color=plcolor,
17051724
picker=3.,
17061725
label='_FLT_'+phase,lw=0.5)
1707-
1726+
1727+
# Callback used to update y-limits when user zooms interactively (MG/Cl Sonnet)
1728+
def onGroupXlimChanged(ax):
1729+
'''Callback to update y-limits for all panels when x-range changes.
1730+
We calculate the global y-range across all panels for the visible x-range,
1731+
then explicitly set y-limits on ALL panels.
1732+
'''
1733+
xlim = ax.get_xlim()
1734+
# Save x-limits for persistence across refinements
1735+
if (plotOpt['sharedX'] and
1736+
(Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])):
1737+
G2frame.groupXlim = xlim
1738+
1739+
# Calculate global y-range across ALL panels for visible x-range
1740+
global_ymin = float('inf')
1741+
global_ymax = float('-inf')
1742+
global_dzmin = float('inf')
1743+
global_dzmax = float('-inf')
1744+
max_tick_space = 0
1745+
1746+
for i in range(Page.groupN):
1747+
xarr = np.array(gX[i])
1748+
xye = gdat[i]
1749+
mask = (xarr >= xlim[0]) & (xarr <= xlim[1])
1750+
if np.any(mask):
1751+
# Calculate scaled y-values for visible data
1752+
scaleY = lambda Y, idx=i: (Y - gYmin[idx]) / (gYmax[idx] - gYmin[idx]) * 100
1753+
visible_obs = scaleY(xye[1][mask])
1754+
visible_calc = scaleY(xye[3][mask])
1755+
visible_bkg = scaleY(xye[4][mask])
1756+
ymin_visible = min(visible_obs.min(), visible_calc.min(), visible_bkg.min())
1757+
ymax_visible = max(visible_obs.max(), visible_calc.max(), visible_bkg.max())
1758+
global_ymin = min(global_ymin, ymin_visible)
1759+
global_ymax = max(global_ymax, ymax_visible)
1760+
# Track tick space needed
1761+
if not Page.plotStyle.get('flTicks', False):
1762+
max_tick_space = max(max_tick_space, len(RefTbl[i]) * 5)
1763+
else:
1764+
max_tick_space = max(max_tick_space, 1)
1765+
# Calculate diff y-limits
1766+
DZ_visible = (xye[1][mask] - xye[3][mask]) * np.sqrt(xye[2][mask])
1767+
global_dzmin = min(global_dzmin, DZ_visible.min())
1768+
global_dzmax = max(global_dzmax, DZ_visible.max())
1769+
1770+
# Apply global y-limits to ALL panels explicitly
1771+
if global_ymax > global_ymin:
1772+
yrange = global_ymax - global_ymin
1773+
ypad = max(yrange * 0.05, 1.0)
1774+
ylim_upper = (global_ymin - ypad - max_tick_space, global_ymax + ypad)
1775+
for i in range(Page.groupN):
1776+
up, down = adjustDim(i, Page.groupN)
1777+
Plots[up].set_ylim(ylim_upper)
1778+
Plots[up].autoscale(enable=False, axis='y')
1779+
if global_dzmax > global_dzmin:
1780+
dzrange = global_dzmax - global_dzmin
1781+
dzpad = max(dzrange * 0.05, 0.5)
1782+
ylim_lower = (global_dzmin - dzpad, global_dzmax + dzpad)
1783+
for i in range(Page.groupN):
1784+
up, down = adjustDim(i, Page.groupN)
1785+
Plots[down].set_ylim(ylim_lower)
1786+
Plots[down].autoscale(enable=False, axis='y')
1787+
# Force canvas redraw to apply new limits
1788+
Page.canvas.draw_idle()
1789+
17081790
#### beginning PlotPatterns execution #####################################
17091791
global exclLines,Page
17101792
global DifLine
@@ -1756,6 +1838,13 @@ def drawTicks(Phases,phaseList,group=False):
17561838
if not new and hasattr(Page,'prevPlotType'):
17571839
if Page.prevPlotType != plottype: new = True
17581840
Page.prevPlotType = plottype
1841+
1842+
# Restore saved plot style settings (qPlot, dPlot, logPlot) if they were preserved
1843+
# across a refinement cycle. These get saved in refPlotUpdate(restore=True) and
1844+
# need to be applied here because Page may have been recreated by ResetPlots. (based on MG/Cl Sonnet)
1845+
if hasattr(G2frame, 'savedPlotStyle'):
1846+
Page.plotStyle.update(G2frame.savedPlotStyle)
1847+
del G2frame.savedPlotStyle # Clear after applying
17591848

17601849
if G2frame.ifSetLimitsMode and G2frame.GPXtree.GetItemText(G2frame.GPXtree.GetSelection()) == 'Limits':
17611850
# note mode
@@ -1823,7 +1912,8 @@ def drawTicks(Phases,phaseList,group=False):
18231912
plottingItem = G2frame.GPXtree.GetItemText(G2frame.PatternId)
18241913
# save settings to be restored after refinement with repPlotUpdate({},restore=True)
18251914
savedSettings = (G2frame.SinglePlot,G2frame.Contour,G2frame.Weight,
1826-
G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'])
1915+
G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'],
1916+
Page.plotStyle['qPlot'],Page.plotStyle['dPlot'])
18271917
G2frame.SinglePlot = True
18281918
G2frame.Contour = False
18291919
G2frame.Weight = True
@@ -2143,7 +2233,7 @@ def drawTicks(Phases,phaseList,group=False):
21432233
Title += ' - background'
21442234
if Page.plotStyle['qPlot'] or plottype in ['SASD','REFD'] and not G2frame.Contour:
21452235
xLabel = r'$Q, \AA^{-1}$'
2146-
elif Page.plotStyle['dPlot'] and 'PWDR' in plottype:
2236+
elif Page.plotStyle['dPlot'] and ('PWDR' in plottype or plottype == 'GROUP'):
21472237
xLabel = r'$d, \AA$'
21482238
elif Page.plotStyle['chanPlot'] and G2frame.Contour:
21492239
xLabel = 'Channel no.'
@@ -2215,8 +2305,6 @@ def drawTicks(Phases,phaseList,group=False):
22152305
gdat[i][j] = np.where(y>=0.,np.sqrt(y),-np.sqrt(-y))
22162306
else:
22172307
gdat[i][j] = y
2218-
gYmax[i] = max(max(gdat[i][1]),max(gdat[i][3]))
2219-
gYmin[i] = min(min(gdat[i][1]),min(gdat[i][3]))
22202308
if Page.plotStyle['qPlot']:
22212309
gX[i] = 2.*np.pi/G2lat.Pos2dsp(gParms,gdat[i][0])
22222310
elif Page.plotStyle['dPlot']:
@@ -2225,6 +2313,9 @@ def drawTicks(Phases,phaseList,group=False):
22252313
gX[i] = gdat[i][0]
22262314
gXmin[i] = min(gX[i])
22272315
gXmax[i] = max(gX[i])
2316+
# Calculate Y range from full data initially (may be updated later for zoom)
2317+
gYmax[i] = max(max(gdat[i][1]),max(gdat[i][3]))
2318+
gYmin[i] = min(min(gdat[i][1]),min(gdat[i][3]))
22282319
# obs-calc/sigma
22292320
DZ = (gdat[i][1]-gdat[i][3])*np.sqrt(gdat[i][2])
22302321
DZmin = min(DZmin,DZ.min())
@@ -2237,22 +2328,23 @@ def drawTicks(Phases,phaseList,group=False):
22372328
Page.plotStyle['qPlot'] or Page.plotStyle['dPlot']):
22382329
Page.figure.text(0.001,0.94,'X shared',fontsize=11,
22392330
color='g')
2240-
Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True,
2331+
#Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True,
2332+
# gridspec_kw=GS_kw)
2333+
# Don't use sharey='row' when sharedX - we'll manage y-limits manually
2334+
# This avoids conflicts between sharey and our dynamic y-limit updates
2335+
Plots = Page.figure.subplots(2,Page.groupN,sharex=True,
22412336
gridspec_kw=GS_kw)
22422337
else:
22432338
Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex='col',
22442339
gridspec_kw=GS_kw)
22452340
Page.figure.subplots_adjust(left=5/100.,bottom=16/150.,
22462341
right=.99,top=1.-3/200.,hspace=0,wspace=0)
2247-
for i in range(Page.groupN):
2248-
up,down = adjustDim(i,Page.groupN)
2249-
Plots[up].set_xlim(gXmin[i],gXmax[i])
2250-
Plots[down].set_xlim(gXmin[i],gXmax[i])
2251-
Plots[down].set_ylim(DZmin,DZmax)
2252-
if not Page.plotStyle.get('flTicks',False):
2253-
Plots[up].set_ylim(-len(RefTbl[i])*5,102)
2254-
else:
2255-
Plots[up].set_ylim(-1,102)
2342+
2343+
# Connect callback for all panels when sharedX is enabled
2344+
if (plotOpt['sharedX'] and
2345+
(Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])):
2346+
up, down = adjustDim(0, Page.groupN)
2347+
Plots[up].callbacks.connect('xlim_changed', onGroupXlimChanged)
22562348

22572349
# pretty up the tick labels
22582350
up,down = adjustDim(0,Page.groupN)
@@ -2268,7 +2360,7 @@ def drawTicks(Phases,phaseList,group=False):
22682360
Plots[up].set_ylabel(r'$\rm\sqrt{Normalized\ intensity}$',fontsize=12)
22692361
else:
22702362
Plots[up].set_ylabel('Normalized Intensity',fontsize=12)
2271-
Page.figure.text(0.001,0.03,commonltrs,fontsize=13)
2363+
Page.figure.text(0.001,0.03,commonltrs,fontsize=13,color='g')
22722364
Page.figure.supxlabel(xLabel)
22732365
for i,h in enumerate(groupPlotList):
22742366
up,down = adjustDim(i,Page.groupN)
@@ -2284,7 +2376,7 @@ def drawTicks(Phases,phaseList,group=False):
22842376
transform=Plot.transAxes,
22852377
verticalalignment='top',
22862378
horizontalalignment=ha,
2287-
fontsize=14)
2379+
fontsize=14,color='g',fontweight='bold')
22882380
xye = gdat[i]
22892381
DZ = (xye[1]-xye[3])*np.sqrt(xye[2])
22902382
DifLine = Plot1.plot(gX[i],DZ,pwdrCol['Diff_color']) #,picker=1.,label=incCptn('diff')) #(Io-Ic)/sig(Io)
@@ -2296,13 +2388,99 @@ def drawTicks(Phases,phaseList,group=False):
22962388
Plot.plot(gX[i],scaleY(xye[3]),pwdrCol['Calc_color'],picker=0.,label=incCptn('calc'),linewidth=1.5)
22972389
Plot.plot(gX[i],scaleY(xye[4]),pwdrCol['Bkg_color'],picker=0.,label=incCptn('bkg'),linewidth=1.5) #background
22982390
drawTicks(RefTbl[i],list(RefTbl[i].keys()),True)
2391+
2392+
# Set axis limits AFTER plotting data to prevent autoscaling from overriding them (MG/Cl Sonnet)
2393+
# When sharedX is enabled, calculate common x-range encompassing all histograms
2394+
if (plotOpt['sharedX'] and
2395+
(Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])):
2396+
if getattr(G2frame, 'groupXlim', None) is not None:
2397+
commonXlim = getattr(G2frame, 'groupXlim', None)
2398+
else:
2399+
# Calculate union of all histogram x-ranges
2400+
commonXmin = min(gXmin.values())
2401+
commonXmax = max(gXmax.values())
2402+
commonXlim = (commonXmin, commonXmax)
2403+
2404+
# First pass: set x-limits and calculate global y-range for visible data
2405+
# Since sharey='row', all upper panels share y-limits, all lower panels share y-limits
2406+
global_ymin = float('inf')
2407+
global_ymax = float('-inf')
2408+
global_dzmin = float('inf')
2409+
global_dzmax = float('-inf')
2410+
max_tick_space = 0
2411+
2412+
for i in range(Page.groupN):
2413+
up, down = adjustDim(i, Page.groupN)
2414+
if (plotOpt['sharedX'] and
2415+
(Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])):
2416+
xlim = commonXlim
2417+
Plots[up].set_xlim(xlim)
2418+
Plots[down].set_xlim(xlim)
2419+
else:
2420+
xlim = (gXmin[i], gXmax[i])
2421+
Plots[up].set_xlim(xlim)
2422+
Plots[down].set_xlim(xlim)
2423+
2424+
# Calculate y-range for this panel's visible data
2425+
xarr = np.array(gX[i])
2426+
xye = gdat[i]
2427+
mask = (xarr >= xlim[0]) & (xarr <= xlim[1])
2428+
if np.any(mask):
2429+
scaleY = lambda Y, idx=i: (Y - gYmin[idx]) / (gYmax[idx] - gYmin[idx]) * 100
2430+
visible_obs = scaleY(xye[1][mask])
2431+
visible_calc = scaleY(xye[3][mask])
2432+
visible_bkg = scaleY(xye[4][mask])
2433+
ymin_visible = min(visible_obs.min(), visible_calc.min(), visible_bkg.min())
2434+
ymax_visible = max(visible_obs.max(), visible_calc.max(), visible_bkg.max())
2435+
global_ymin = min(global_ymin, ymin_visible)
2436+
global_ymax = max(global_ymax, ymax_visible)
2437+
# Track tick space needed
2438+
if not Page.plotStyle.get('flTicks', False):
2439+
max_tick_space = max(max_tick_space, len(RefTbl[i]) * 5)
2440+
else:
2441+
max_tick_space = max(max_tick_space, 1)
2442+
# Calculate diff y-limits
2443+
DZ_visible = (xye[1][mask] - xye[3][mask]) * np.sqrt(xye[2][mask])
2444+
global_dzmin = min(global_dzmin, DZ_visible.min())
2445+
global_dzmax = max(global_dzmax, DZ_visible.max())
2446+
2447+
# Apply global y-limits to ALL panels explicitly (sharey may not propagate properly)
2448+
if global_ymax > global_ymin:
2449+
yrange = global_ymax - global_ymin
2450+
ypad = max(yrange * 0.05, 1.0)
2451+
ylim_upper = (global_ymin - ypad - max_tick_space, global_ymax + ypad)
2452+
else:
2453+
# Fallback to full range
2454+
if not Page.plotStyle.get('flTicks', False):
2455+
ylim_upper = (-max_tick_space, 102)
2456+
else:
2457+
ylim_upper = (-1, 102)
2458+
if global_dzmax > global_dzmin:
2459+
dzrange = global_dzmax - global_dzmin
2460+
dzpad = max(dzrange * 0.05, 0.5)
2461+
ylim_lower = (global_dzmin - dzpad, global_dzmax + dzpad)
2462+
else:
2463+
ylim_lower = (DZmin, DZmax)
2464+
2465+
# Set y-limits on ALL panels
2466+
for i in range(Page.groupN):
2467+
up, down = adjustDim(i, Page.groupN)
2468+
Plots[up].set_ylim(ylim_upper)
2469+
Plots[down].set_ylim(ylim_lower)
2470+
22992471
try: # try used as in PWDR menu not Groups
23002472
# Not sure if this does anything
23012473
G2frame.dataWindow.moveTickLoc.Enable(False)
23022474
G2frame.dataWindow.moveTickSpc.Enable(False)
23032475
# G2frame.dataWindow.moveDiffCurve.Enable(True)
23042476
except:
23052477
pass
2478+
# Save the current x-limits for GROUP plots so they can be restored after refinement (MG/Cl Sonnet)
2479+
# When sharedX is enabled in Q/d-space, all panels share the same x-range
2480+
if (plotOpt['sharedX'] and
2481+
(Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])):
2482+
up,down = adjustDim(0,Page.groupN)
2483+
G2frame.groupXlim = Plots[up].get_xlim()
23062484
Page.canvas.draw()
23072485
return
23082486
elif G2frame.Weight and not G2frame.Contour:

0 commit comments

Comments
 (0)