Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bb50393
code to create groups; remove CIF Author
briantoby Sep 6, 2025
96bc5e5
Merge remote-tracking branch 'origin/main' into TOFgroup
briantoby Sep 8, 2025
bf68f82
Merge remote-tracking branch 'origin/main' into TOFgroup
briantoby Sep 8, 2025
6d5470b
Add section for plotting grouped histograms. Still need to figure out…
briantoby Sep 10, 2025
0c00d51
Label plots using common & unique part of hist name; use reflection t…
briantoby Sep 10, 2025
3985cbe
make sharing X-axes an option for grouped plots
briantoby Sep 18, 2025
e99db49
WIP: adding group tree items & plot groups
briantoby Oct 22, 2025
bdb60ed
Merge remote-tracking branch 'origin/main' into TOFgroup to bring up …
briantoby Oct 22, 2025
751d211
WIP: get plotting working, fix display of groups. Next: table for edi…
briantoby Oct 23, 2025
68b0c08
WIP: shows table of HAP values, but can we make row labels not scroll?
briantoby Oct 27, 2025
789a02e
working implementation with unscrolled labels not 100% ideal and not …
briantoby Oct 29, 2025
88d1f19
clean implementation of group HAP display
briantoby Oct 30, 2025
df236d1
WIP: now sample param works
briantoby Nov 1, 2025
1f3458e
WIP: Edit parametric name; move refine after value
briantoby Nov 1, 2025
2019dbb
Full working version to display HAP, Sample & Inst vals
briantoby Nov 4, 2025
f9e6ed3
bring TOFgroup up to date with main
briantoby Nov 4, 2025
d2db3b9
Merge remote-tracking branch 'origin/main' into TOFgroup
briantoby Nov 7, 2025
98f2a43
Fix tickmarks in plot
briantoby Nov 8, 2025
31712f9
get latest changes in main
briantoby Nov 9, 2025
262e52b
update to latest main version
briantoby Nov 9, 2025
99cdfa5
fix bindings in plotting so that plotType etc is current
briantoby Nov 10, 2025
47b4858
WIP: implement Limits page; start on copy & ref/all
briantoby Nov 10, 2025
df1849d
finish copy/ref button implementation
briantoby Nov 10, 2025
d177f5c
Merge remote-tracking branch 'origin/main' into TOFgroup
briantoby Nov 19, 2025
d6263a5
bring in latest changes in main
briantoby Nov 22, 2025
6c44c16
allow limits to be set in Q & d; reset drags when mouse is out of plo…
briantoby Nov 29, 2025
b9d1c8d
update to match latest main branch & minor picker fixes
briantoby Nov 30, 2025
26fd234
move copy button to between 1st & 2nd column, but remove from limits …
briantoby Dec 1, 2025
0a25d8c
update from main
briantoby Dec 2, 2025
1a2742a
Add background terms
briantoby Dec 13, 2025
6e491f1
bring in fix for pdabc
briantoby Dec 13, 2025
c0ae310
implement limits on grouped plots; rework arrows & publish plot arrow…
briantoby Dec 14, 2025
8c32385
wrap group plot if too few entries
briantoby Dec 14, 2025
6a9fb4c
add menu bar for groups; fix bug for 2+ digit bank number
briantoby Dec 17, 2025
14d7ca2
pull in fix to bkg peaks
briantoby Dec 20, 2025
784e3be
refactor value reference in prep for copy by group
briantoby Dec 23, 2025
633f1bd
Add group copy/copy selected; load histogram & phase once; make prmAr…
briantoby Dec 27, 2025
820fe17
Add code based on PR #285 that preserves plot x-range & y-scaling acr…
briantoby Dec 28, 2025
3dd616b
closer to PR #285: preserves plot x-range in sharedX mode, but not y-…
briantoby Dec 29, 2025
6c64f45
Add routines to save/restore limits on all plot axes; restore limits …
briantoby Jan 2, 2026
20f40b7
fix problem where use of thin-long tickmarks rescaled x-axis
briantoby Jan 2, 2026
c8e308b
update pixi to match main
briantoby Jan 3, 2026
681763b
more work on preventing limit resets; this should replace everything …
briantoby Jan 3, 2026
fd5b416
update to latest version in main
briantoby Jan 6, 2026
93839da
contour plotting updates that have also been applied to main
briantoby Jan 8, 2026
750ccb7
update to latest main version
briantoby Jan 8, 2026
f369751
Add cross-hair cursor & change S/C buttons to ballot-box, as perhttps…
briantoby Jan 9, 2026
945ef81
Suppress liveplot except when PWDR selected; add Replot for Group aft…
briantoby Jan 10, 2026
b114fae
Address "Switch between single bank & grouped plot mode" request in 1…
briantoby Jan 11, 2026
85b53a0
Allow # of background terms to change from Group page as on 1/7 list …
briantoby Jan 16, 2026
249020c
Add a reset button for instrument parameters on delta-inst page
briantoby Jan 17, 2026
00ebc2c
Merge branch 'TOFgroup' of github.com:AdvancedPhotonSource/GSAS-II in…
briantoby Jan 17, 2026
225a8e4
For groups: color widgets by shift; set bkg color for ValidatedTextCt…
briantoby Jan 18, 2026
b1d26e8
rework group display considerably; address bugs seen on Mac
briantoby Jan 20, 2026
57d77c3
bring in latest changes from main
briantoby Jan 20, 2026
e158411
bring in latest changes from main
briantoby Jan 20, 2026
fa1cddd
show message when delta-inst table would be empty
briantoby Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 189 additions & 59 deletions GSASII/GSASIIdataGUI.py

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion GSASII/GSASIIfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,11 @@ def FormatValue(val,maxdigits=None):
digits.append('f')
if not val:
digits[2] = 'f'
fmt="{:"+str(digits[0])+"."+str(digits[1])+digits[2]+"}"
if digits[2] == 'g':
fmt="{:#"+str(digits[0])+"."+str(digits[1])+digits[2]+"}"
# the # above forces inclusion of a decimal place: 10.000 rather than 10 for 9.999999999
else:
fmt="{:"+str(digits[0])+"."+str(digits[1])+digits[2]+"}"
string = fmt.format(float(val)).strip() # will standard .f formatting work?
if len(string) <= digits[0]:
if ':' in string: # deal with weird bug where a colon pops up in a number when formatting (EPD 7.3.2!)
Expand Down
1,281 changes: 1,281 additions & 0 deletions GSASII/GSASIIgroupGUI.py

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions GSASII/GSASIImiscGUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

'''

from __future__ import division, print_function

# # Allow this to be imported without wx present.
# try:
# import wx
Expand Down Expand Up @@ -554,6 +552,8 @@ def ProjFileOpen(G2frame,showProvenance=True):
finally:
dlg.Destroy()
wx.BeginBusyCursor()
groupDict = {}
groupInserted = False # only need to do this once
try:
if GSASIIpath.GetConfigValue('show_gpxSize'):
posPrev = 0
Expand All @@ -575,13 +575,23 @@ def ProjFileOpen(G2frame,showProvenance=True):
#if unexpectedObject:
# print(datum[0])
# GSASIIpath.IPyBreak()
# insert groups before any individual PDWR items
if datum[0].startswith('PWDR') and groupDict and not groupInserted:
Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Groups/Powder')
G2frame.GPXtree.SetItemPyData(Id,{})
for nam in groupDict:
sub = G2frame.GPXtree.AppendItem(parent=Id,text=nam)
G2frame.GPXtree.SetItemPyData(sub,{})
groupInserted = True
Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
if datum[0] == 'Phases' and GSASIIpath.GetConfigValue('SeparateHistPhaseTreeItem',False):
G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Hist/Phase')
if updateFromSeq and datum[0] == 'Phases':
for pdata in data[1:]:
if pdata[0] in Phases:
pdata[1].update(Phases[pdata[0]])
elif datum[0] == 'Controls':
groupDict = datum[1].get('Groups',{}).get('groupDict',{})
elif updateFromSeq and datum[0] == 'Covariance':
data[0][1] = CovData
elif updateFromSeq and datum[0] == 'Rigid bodies':
Expand Down Expand Up @@ -720,7 +730,7 @@ def ProjFileSave(G2frame):
while item:
data = []
name = G2frame.GPXtree.GetItemText(item)
if name.startswith('Hist/Phase'): # skip over this
if name.startswith('Hist/Phase') or name.startswith('Groups'): # skip over this
item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
continue
data.append([name,G2frame.GPXtree.GetItemPyData(item)])
Expand Down
24 changes: 12 additions & 12 deletions GSASII/GSASIIobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,9 +611,9 @@ def CompileVarDesc():
for key,value in {
# derived or other sequential vars
'([abc])$' : 'Lattice parameter, \\1, from Ai and Djk', # N.B. '$' prevents match if any characters follow
u'\u03B1' : u'Lattice parameter, \u03B1, computed from both Ai and Djk',
u'\u03B2' : u'Lattice parameter, \u03B2, computed from both Ai and Djk',
u'\u03B3' : u'Lattice parameter, \u03B3, computed from both Ai and Djk',
'\u03B1' : 'Lattice parameter, \u03B1, computed from both Ai and Djk',
'\u03B2' : 'Lattice parameter, \u03B2, computed from both Ai and Djk',
'\u03B3' : 'Lattice parameter, \u03B3, computed from both Ai and Djk',
# ambiguous, alas:
'Scale' : 'Phase fraction (as p:h:Scale) or Histogram scale factor (as :h:Scale)',
# Phase vars (p::<var>)
Expand Down Expand Up @@ -647,8 +647,8 @@ def CompileVarDesc():
'D([123][123])' : 'Anisotropic strain coef. \\1',
'Extinction' : 'Extinction coef.',
'MD' : 'March-Dollase coef.',
'Mustrain;.*' : 'Microstrain coefficient (delta Q/Q x 10**6)',
'Size;.*' : 'Crystallite size value (in microns)',
'Mustrain;(.*)' : 'Microstrain coefficient (delta Q/Q x 10**6, \\1: i=iso/eq, a=axial, general:0-5, mx=LGmix)',
'Size;(.*)' : 'Crystallite size value (in microns, \\1: i=iso/eq, a=axial, general:0-5, mx=LGmix)',
'eA$' : 'Cubic mustrain value',
'Ep$' : 'Primary extinction',
'Es$' : 'Secondary type II extinction',
Expand All @@ -669,14 +669,14 @@ def CompileVarDesc():
'SH/L' : ('FCJ peak asymmetry correction',1e-4),
'([UVW])$' : ('Gaussian instrument broadening \\1',1e-5),
'([XYZ])$' : ('Cauchy instrument broadening \\1',1e-5),
'Zero' : 'Debye-Scherrer zero correction',
'Zero' : 'Debye-Scherrer/TOF zero correction',
'Shift' : 'Bragg-Brentano sample displ.',
'SurfRoughA' : 'Bragg-Brentano surface roughness A',
'SurfRoughB' : 'Bragg-Brentano surface roughness B',
'Transparency' : 'Bragg-Brentano sample tranparency',
'DebyeA' : 'Debye model amplitude',
'DebyeR' : 'Debye model radius',
'DebyeU' : 'Debye model Uiso',
'Transparency' : 'Bragg-Brentano sample transparency',
'DebyeA;(.*)' : 'Debye model peak #\\1 amplitude',
'DebyeR;(.*)' : 'Debye model peak #\\1 radius',
'DebyeU;(.*)' : 'Debye model peak #\\1 Uiso',
'RBV.*' : 'Vector rigid body parameter',
'RBVO([aijk])' : 'Vector rigid body orientation parameter \\1',
'RBVP([xyz])' : 'Vector rigid body \\1 position parameter',
Expand Down Expand Up @@ -2271,14 +2271,14 @@ def showEQ(calcobj):

obj.expression = "A*np.exp(B)"
obj.assgnVars = {'B': '0::Afrac:1'}
obj.freeVars = {'A': [u'A', 0.5, True]}
obj.freeVars = {'A': ['A', 0.5, True]}
#obj.CheckVars()
calcobj = ExpressionCalcObj(obj)

obj1 = ExpressionObj()
obj1.expression = "A*np.exp(B)"
obj1.assgnVars = {'B': '0::Afrac:*'}
obj1.freeVars = {'A': [u'Free Prm A', 0.5, True]}
obj1.freeVars = {'A': ['Free Prm A', 0.5, True]}
#obj.CheckVars()
calcobj1 = ExpressionCalcObj(obj1)

Expand Down
148 changes: 133 additions & 15 deletions GSASII/GSASIIplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,14 @@ def __init__(self,parent,id=-1,dpi=None,**kwargs):

class G2PlotMpl(_tabPlotWin):
'Creates a Matplotlib 2-D plot in the GSAS-II graphics window'
def __init__(self,parent,id=-1,dpi=None,publish=None,**kwargs):
def __init__(self,parent,id=-1,dpi=None,**kwargs):
_tabPlotWin.__init__(self,parent,id=id,**kwargs)
mpl.rcParams['legend.fontsize'] = 10
mpl.rcParams['axes.grid'] = False
#TODO: set dpi here via config var: this changes the size of the labeling font 72-100 is normal
self.figure = mplfig.Figure(dpi=dpi,figsize=(5,6))
self.canvas = Canvas(self,-1,self.figure)
self.toolbar = GSASIItoolbar(self.canvas,publish=publish)
self.toolbar = GSASIItoolbar(self.canvas)
self.toolbar.Realize()
self.plotStyle = {'qPlot':False,'dPlot':False,'sqrtPlot':False,'sqPlot':False,
'logPlot':False,'exclude':False,'partials':True,'chanPlot':False}
Expand Down Expand Up @@ -324,6 +324,7 @@ def __init__(self,parent,id=-1,G2frame=None):
self.allowZoomReset = True # this indicates plot should be updated not initialized
# (BHT: should this be in tabbed panel rather than here?)
self.lastRaisedPlotTab = None
self.savedPlotLims = None

def OnNotebookKey(self,event):
'''Called when a keystroke event gets picked up by the notebook window
Expand Down Expand Up @@ -394,7 +395,7 @@ def GetTabIndex(self,label):
# if plotNum is not None:
# wx.CallAfter(self.SetSelectionNoRefresh,plotNum)

def FindPlotTab(self,label,Type,newImage=True,publish=None):
def FindPlotTab(self,label,Type,newImage=True,saveLimits=False):
'''Open a plot tab for initial plotting, or raise the tab if it already exists
Set a flag (Page.plotInvalid) that it has been redrawn
Record the name of the this plot in self.lastRaisedPlotTab
Expand All @@ -408,9 +409,8 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None):

:param bool newImage: forces creation of a new graph for matplotlib
plots only (defaults as True)
:param function publish: reference to routine used to create a
publication version of the current mpl plot (default is None,
which prevents use of this).
:param bool saveLimits: When True, limits for all MPL axes (plots)
are saved in self.savedPlotLims.
:returns: new,plotNum,Page,Plot,limits where

* new: will be True if the tab was just created
Expand All @@ -419,15 +419,17 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None):
the plot appears
* Plot: the mpl.Axes object for the graphic (mpl) or the figure for
openGL.
* limits: for mpl plots, when a plot already exists, this will be a tuple
with plot scaling. None otherwise.
* limits: for mpl plots, when a plot already exists, this
will be a tuple with plot scaling. None otherwise. Only appropriate
for plots with one set of axes.
'''
limits = None
Plot = None
try:
new = False
plotNum,Page = self.GetTabIndex(label)
if Type == 'mpl' or Type == '3d':
if saveLimits: self.savePlotLims(Page)
Axes = Page.figure.get_axes()
Plot = Page.figure.gca() #get previous plot
limits = [Plot.get_xlim(),Plot.get_ylim()] # save previous limits
Expand All @@ -443,7 +445,7 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None):
except (ValueError,AttributeError):
new = True
if Type == 'mpl':
Plot = self.addMpl(label,publish=publish).gca()
Plot = self.addMpl(label).gca()
elif Type == 'ogl':
Plot = self.addOgl(label)
elif Type == '3d':
Expand All @@ -468,8 +470,63 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None):
Page.helpKey = self.G2frame.dataWindow.helpKey
except AttributeError:
Page.helpKey = 'HelpIntro'
try:
Page.toolbar.enableArrows() # Disable Arrow keys if present
except AttributeError:
pass
return new,plotNum,Page,Plot,limits

def savePlotLims(self,Page=None,debug=False,label=None):
'''Make a copy of all the current axes in the notebook object
'''
if label and Page:
print('Warning: label and Page defined in savePlotLims')
elif label:
try:
plotNum,Page = self.GetTabIndex(label)
except ValueError:
print(f'Warning: plot {label} not found in savePlotLims')
return
elif not Page:
print('Error: neither label nor Page defined in savePlotLims')
return
self.savedPlotLims = [
[i.get_xlim() for i in Page.figure.get_axes()],
[i.get_ylim() for i in Page.figure.get_axes()]]
if debug:
print(f'saved {len(self.savedPlotLims[1])} axes limits')
#print( self.savedPlotLims)
def restoreSavedPlotLims(self,Page):
'''Restore the plot limits, when previously saved, and when
``G2frame.restorePlotLimits`` is set to True, which
is done when ``GSASIIpwdplot.refPlotUpdate`` is called with
``restore=True``, which indicates that "live plotting" is
finished. This is also set for certain plot key-press
combinations.
The restore operation can only be done once, as the limits
are deleted after use in this method.
'''
if self.savedPlotLims is None:
#print('---- nothing to restore')
return
if not getattr(self.G2frame,'restorePlotLimits',False):
#print('---- restorePlotLimits not set')
return
savedPlotLims = self.savedPlotLims
axesList = Page.figure.get_axes()
if len(axesList) != len(savedPlotLims[0]):
#print('saved lengths differ',len(axesList),len(savedPlotLims[0]))
return
for i,ax in enumerate(axesList):
ax.set_xlim(savedPlotLims[0][i])
ax.set_ylim(savedPlotLims[1][i])
#print(i,
# savedPlotLims[0][i][0],savedPlotLims[0][i][1],
# savedPlotLims[1][i][0],savedPlotLims[1][i][1])
self.savedPlotLims = None
self.G2frame.restorePlotLimits = False
Page.canvas.draw()

def _addPage(self,name,page):
'''Add the newly created page to the notebook and associated lists.

Expand All @@ -492,9 +549,9 @@ def _addPage(self,name,page):
#page.replotKWargs = {}
#self.skipPageChange = False

def addMpl(self,name="",publish=None):
def addMpl(self,name=""):
'Add a tabbed page with a matplotlib plot'
page = G2PlotMpl(self.nb,publish=publish)
page = G2PlotMpl(self.nb)
self._addPage(name,page)
return page.figure

Expand Down Expand Up @@ -605,7 +662,7 @@ def InvokeTreeItem(self,pid):

class GSASIItoolbar(Toolbar):
'Override the matplotlib toolbar so we can add more icons'
def __init__(self,plotCanvas,publish=None,Arrows=True):
def __init__(self,plotCanvas,Arrows=True):
'''Adds additional icons to toolbar'''
self.arrows = {}
# try to remove a button from the bar
Expand All @@ -630,16 +687,25 @@ def __init__(self,plotCanvas,publish=None,Arrows=True):
prfx = 'Shift plot '
fil = ''.join([i[0].lower() for i in direc.split()]+['arrow.ico'])
self.arrows[direc] = self.AddToolBarTool(sprfx+direc,prfx+direc,fil,self.OnArrow)
if publish:
self.AddToolBarTool('Publish plot','Create publishable version of plot','publish.ico',publish)
self.publishId = self.AddToolBarTool('Publish plot','Create publishable version of plot','publish.ico',self.Publish)
self.publishRoutine = None
self.EnableTool(self.publishId,False)
self.Realize()
def setPublish(self,publish=None):
'Set the routine to be used to publsh the plot'
self.publishRoutine = publish
self.EnableTool(self.publishId,bool(publish))
def Publish(self,*args,**kwargs):
'Called to publish the current plot'
if not self.publishRoutine: return
self.publishRoutine(*args,**kwargs)

def set_message(self,s):
''' this removes spurious text messages from the tool bar
'''
pass

# TODO: perhaps someday we could pull out the bitmaps and rescale there here
# TODO: perhaps someday we could pull out the bitmaps and rescale them here
# def AddTool(self,*args,**kwargs):
# print('AddTool',args,kwargs)
# return Toolbar.AddTool(self,*args,**kwargs)
Expand All @@ -663,6 +729,27 @@ def _update_view(self):
wx.CallAfter(*self.updateActions)
Toolbar._update_view(self)

def home(self, *args):
'''Override home button to clear saved GROUP plot limits and trigger replot.
This ensures that pressing home resets to full data range while retaining x-units.
For GROUP plots, we need to replot rather than use matplotlib's home because
matplotlib's home would restore the original shared limits, not per-histogram limits.
(based on MG/Cl Sonnet code)
'''
G2frame = wx.GetApp().GetMainTopWindow()
# Check if we're in GROUP plot mode - if so, clear saved GROUP
# plot x-limits and trigger a replot
if self.arrows.get('_groupMode'):
# PlotPatterns will use full data range
if hasattr(G2frame, 'groupXlim'):
del G2frame.groupXlim
# Trigger a full replot for GROUP plots
if self.updateActions:
wx.CallAfter(*self.updateActions)
return
# For non-GROUP plots, call the parent's home method
Toolbar.home(self, *args)

def AnyActive(self):
for Itool in range(self.GetToolsCount()):
if self.GetToolState(self.GetToolByPos(Itool).GetId()):
Expand All @@ -678,6 +765,22 @@ def GetActive(self):

def OnArrow(self,event):
'reposition limits to scan or zoom by button press'
if self.arrows.get('_groupMode'):
Page = self.arrows['_groupMode']
if event.Id == self.arrows['right']:
Page.groupOff += 1
elif event.Id == self.arrows['left']:
Page.groupOff -= 1
elif event.Id == self.arrows['Expand X']:
Page.groupMax += 1
elif event.Id == self.arrows['Shrink X']:
if Page.groupMax == 2: return
Page.groupMax -= 1
else:
return
if self.updateActions:
wx.CallLater(100,*self.updateActions)
return
axlist = self.plotCanvas.figure.get_axes()
if len(axlist) == 1:
ax = axlist[0]
Expand Down Expand Up @@ -735,6 +838,21 @@ def OnArrow(self,event):
# self.parent.toolbar.push_current()
if self.updateActions:
wx.CallAfter(*self.updateActions)
def enableArrows(self,mode='',updateActions=None):
'''Disable/Enable arrow keys.
Disables when updateActions is None.
mode='group' turns on 'x' buttons only
'''
if not self.arrows: return
self.updateActions = updateActions
if mode == 'group':
# assumes that all arrows previously disabled
for lbl in ('left', 'right', 'Expand X', 'Shrink X'):
self.EnableTool(self.arrows[lbl],True)
else:
for lbl in ('left','right','up','down', 'Expand X',
'Shrink X','Expand Y','Shrink Y'):
self.EnableTool(self.arrows[lbl],bool(updateActions))

def OnHelp(self,event):
'Respond to press of help button on plot toolbar'
Expand Down
Loading
Loading