diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index 9951f148..c6facee6 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -67,6 +67,7 @@ def new_util_find_library( name ): from . import GSASIIfpaGUI as G2fpa from . import GSASIIseqGUI as G2seq from . import GSASIIddataGUI as G2ddG +from . import GSASIIgroupGUI as G2gr try: wx.NewIdRef @@ -1404,6 +1405,7 @@ def OnImportSfact(self,event): header = 'Select phase(s) to add the new\nsingle crystal dataset(s) to:' for Name in newHistList: header += '\n '+str(Name) + if len(header) > 200: header = header[:200]+'...' result = G2G.ItemSelector(phaseNameList,self,header,header='Add to phase(s)',multiple=True) if not result: return # connect new phases to histograms @@ -1997,7 +1999,7 @@ def OnImportPowder(self,event): header = 'Select phase(s) to link\nto the newly-read data:' for Name in newHistList: header += '\n '+str(Name) - + if len(header) > 200: header = header[:200]+'...' result = G2G.ItemSelector(phaseNameList,self,header,header='Add to phase(s)',multiple=True) if not result: return # connect new phases to histograms @@ -4300,13 +4302,14 @@ def OnFileBrowse(self, event): print (traceback.format_exc()) - def StartProject(self): + def StartProject(self,selectItem=True): '''Opens a GSAS-II project file & selects the 1st available data set to display (PWDR, HKLF, REFD or SASD) ''' Id = 0 phaseId = None + GroupId = None seqId = None G2IO.ProjFileOpen(self) self.GPXtree.SetItemText(self.root,'Project: '+self.GSASprojectfile) @@ -4326,6 +4329,8 @@ def StartProject(self): seqId = item elif name == "Phases": phaseId = item + elif name.startswith("Groups"): + GroupId = item elif name == 'Controls': data = self.GPXtree.GetItemPyData(item) if data: @@ -4333,19 +4338,27 @@ def StartProject(self): item, cookie = self.GPXtree.GetNextChild(self.root, cookie) if phaseId: # show all phases self.GPXtree.Expand(phaseId) - if seqId: + if GroupId: + self.GPXtree.Expand(GroupId) + # select an item + if seqId and selectItem: self.EnablePlot = True SelectDataTreeItem(self,seqId) self.GPXtree.SelectItem(seqId) # needed on OSX or item is not selected in tree; perhaps not needed elsewhere - elif Id: + elif GroupId and selectItem: + self.EnablePlot = True + self.GPXtree.Expand(GroupId) + SelectDataTreeItem(self,GroupId) + self.GPXtree.SelectItem(GroupId) # needed on OSX or item is not selected in tree; perhaps not needed elsewhere + elif Id and selectItem: self.EnablePlot = True self.GPXtree.Expand(Id) SelectDataTreeItem(self,Id) self.GPXtree.SelectItem(Id) # needed on OSX or item is not selected in tree; perhaps not needed elsewhere - elif phaseId: + elif phaseId and selectItem: Id = phaseId # open 1st phase - Id, unused = self.GPXtree.GetFirstChild(phaseId) + Id,_ = self.GPXtree.GetFirstChild(phaseId) SelectDataTreeItem(self,Id) self.GPXtree.SelectItem(Id) # as before for OSX self.CheckNotebook() @@ -7495,6 +7508,20 @@ def _makemenu(): # routine to create menu when first used # don't know which menu was selected, but should be General on first phase use SetDataMenuBar(G2frame,self.DataGeneral) self.DataGeneral = _makemenu + + # Groups + G2G.Define_wxId('wxID_GRPALL','wxID_GRPSEL','wxID_HIDESAME') + def _makemenu(): # routine to create menu when first used + self.GroupMenu = wx.MenuBar() + self.PrefillDataMenu(self.GroupMenu) + self.GroupCmd = wx.Menu(title='') + self.GroupMenu.Append(menu=self.GroupCmd, title='Grp Cmds') + self.GroupCmd.Append(G2G.wxID_GRPALL,'Copy all','Copy all parameters by group') + self.GroupCmd.Append(G2G.wxID_GRPSEL,'Copy selected','Copy elected parameters by group') +# self.GroupCmd.Append(G2G.wxID_HIDESAME,'Hide identical rows','Omit rows that are the same and are not refinable from table') + self.PostfillDataMenu() + SetDataMenuBar(G2frame,self.GroupMenu) + self.GroupMenu = _makemenu # end of GSAS-II menu definitions def readFromFile(reader): @@ -7901,26 +7928,63 @@ def OnFsqRef(event): ShklSizer.Add(usrrej,0,WACV) return LSSizer,ShklSizer - def AuthSizer(): - def OnAuthor(event): - event.Skip() - data['Author'] = auth.GetValue() - - Author = data['Author'] - authSizer = wx.BoxSizer(wx.HORIZONTAL) - authSizer.Add(wx.StaticText(G2frame.dataWindow,label=' CIF Author (last, first):'),0,WACV) - auth = wx.TextCtrl(G2frame.dataWindow,-1,value=Author,style=wx.TE_PROCESS_ENTER) - auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor) - auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor) - authSizer.Add(auth,0,WACV) - return authSizer + # def AuthSizer(): + # def OnAuthor(event): + # event.Skip() + # data['Author'] = auth.GetValue() + # + # Author = data['Author'] + # authSizer = wx.BoxSizer(wx.HORIZONTAL) + # authSizer.Add(wx.StaticText(G2frame.dataWindow,label=' CIF Author (last, first):'),0,WACV) + # auth = wx.TextCtrl(G2frame.dataWindow,-1,value=Author,style=wx.TE_PROCESS_ENTER) + # auth.Bind(wx.EVT_TEXT_ENTER,OnAuthor) + # auth.Bind(wx.EVT_KILL_FOCUS,OnAuthor) + # authSizer.Add(auth,0,WACV) + # return authSizer def ClearFrozen(event): 'Removes all frozen parameters by clearing the entire dict' Controls['parmFrozen'] = {} wx.CallAfter(UpdateControls,G2frame,data) - # start of UpdateControls + def SearchGroups(event): + '''Create a dict to group similar histograms. Similarity + is judged by a common string that matches a template + supplied by the user + ''' + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + for hist in Histograms: + if hist.startswith('PWDR '): + break + else: + G2G.G2MessageBox(G2frame,'No used PWDR histograms found to group. Histograms must be assigned phase(s).', + 'Cannot group') + return + ans = G2frame.OnFileSave(None) + if not ans: return + data['Groups'] = G2gr.SearchGroups(G2frame,Histograms,hist) +# wx.CallAfter(UpdateControls,G2frame,data) + ans = G2frame.OnFileSave(None) + if not ans: return + G2frame.clearProject() # clear out data tree + G2frame.StartProject(False) + #self.EnablePlot = True + Id = GetGPXtreeItemId(G2frame,G2frame.root, 'Controls') + SelectDataTreeItem(G2frame,Id) + G2frame.GPXtree.SelectItem(Id) # needed on OSX or item is not selected in tree; perhaps not needed elsewhere + + def ClearGroups(event): + del data['Groups'] + ans = G2frame.OnFileSave(None) + if not ans: return + G2frame.clearProject() # clear out data tree + G2frame.StartProject(False) + #self.EnablePlot = True + Id = GetGPXtreeItemId(G2frame,G2frame.root, 'Controls') + SelectDataTreeItem(G2frame,Id) + G2frame.GPXtree.SelectItem(Id) # needed on OSX or item is not selected in tree; perhaps not needed elsewhere + + #======= start of UpdateControls =========================================== if 'SVD' in data['deriv type']: G2frame.GetStatusBar().SetStatusText('Hessian SVD not recommended for initial refinements; use analytic Hessian or Jacobian',1) else: @@ -7961,11 +8025,44 @@ def ClearFrozen(event): G2G.HorizontalLine(mainSizer,G2frame.dataWindow) subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add((-1,-1),1,wx.EXPAND) - subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Global Settings'),0,WACV) + subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Histogram Grouping'),0,WACV) + subSizer.Add((-1,-1),1,wx.EXPAND) + mainSizer.Add(subSizer,0,wx.EXPAND) + subSizer = wx.BoxSizer(wx.HORIZONTAL) + groupDict = data.get('Groups',{}).get('groupDict',{}) + subSizer.Add((-1,-1),1,wx.EXPAND) + if groupDict: + groupCount = [len(groupDict[k]) for k in groupDict] + if min(groupCount) == max(groupCount): + msg = f'Have {len(groupDict)} group(s) with {min(groupCount)} histograms in each' + else: + msg = (f'Have {len(groupDict)} group(s) with {min(groupCount)}' + f' to {min(groupCount)} histograms in each') + notGrouped = data.get('Groups',{}).get('notGrouped',0) + if notGrouped: + msg += f". {notGrouped} not in a group" + subSizer.Add(wx.StaticText(G2frame.dataWindow,label=msg),0,WACV) + subSizer.Add((5,-1)) + btn = wx.Button(G2frame.dataWindow, wx.ID_ANY,'Redefine groupings') + else: + btn = wx.Button(G2frame.dataWindow, wx.ID_ANY,'Define groupings') + btn.Bind(wx.EVT_BUTTON,SearchGroups) + subSizer.Add(btn) + if groupDict: + btn = wx.Button(G2frame.dataWindow, wx.ID_ANY,'Clear groupings') + subSizer.Add((5,-1)) + subSizer.Add(btn) + btn.Bind(wx.EVT_BUTTON,ClearGroups) subSizer.Add((-1,-1),1,wx.EXPAND) mainSizer.Add(subSizer,0,wx.EXPAND) - mainSizer.Add(AuthSizer()) - mainSizer.Add((5,5),0) + mainSizer.Add((-1,8)) + G2G.HorizontalLine(mainSizer,G2frame.dataWindow) + subSizer = wx.BoxSizer(wx.HORIZONTAL) + subSizer.Add((-1,-1),1,wx.EXPAND) + # subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Global Settings'),0,WACV) + # subSizer.Add((-1,-1),1,wx.EXPAND) + # mainSizer.Add(subSizer,0,wx.EXPAND) + # mainSizer.Add(AuthSizer()) Controls = data # count frozen variables (in appropriate place) for key in ('parmMinDict','parmMaxDict','parmFrozen'): @@ -8879,6 +8976,11 @@ def OnShowShift(event): #import imp #imp.reload(G2ddG) G2ddG.MakeHistPhaseWin(G2frame) + elif G2frame.GPXtree.GetItemText(item).startswith('Groups/'): + # groupDict is defined (or item would not be in tree). + # At least for now, this does nothing, so advance to first group entry + item, cookie = G2frame.GPXtree.GetFirstChild(item) + wx.CallAfter(G2frame.GPXtree.SelectItem,item) elif GSASIIpath.GetConfigValue('debug'): print('Unknown tree item',G2frame.GPXtree.GetItemText(item)) ############################################################################ @@ -9076,6 +9178,16 @@ def OnShowShift(event): data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) G2pdG.UpdateReflectionGrid(G2frame,data,HKLF=True,Name=name) G2frame.dataWindow.HideShow.Enable(True) + elif G2frame.GPXtree.GetItemText(parentID).startswith('Groups/'): + # if GSASIIpath.GetConfigValue('debug'): + # print('Debug: reloading',G2gr) + # from importlib import reload + # reload(G2G) + # reload(G2gr) + G2gr.UpdateGroup(G2frame,item) + elif GSASIIpath.GetConfigValue('debug'): + print(f'Unknown subtree item {G2frame.GPXtree.GetItemText(item)!r}', + f'\n\tparent: {G2frame.GPXtree.GetItemText(parentID)!r}') if G2frame.PickId: G2frame.PickIdText = G2frame.GetTreeItemsList(G2frame.PickId) diff --git a/GSASII/GSASIIfiles.py b/GSASII/GSASIIfiles.py index 231a34bb..e8e6f976 100644 --- a/GSASII/GSASIIfiles.py +++ b/GSASII/GSASIIfiles.py @@ -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!) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py new file mode 100644 index 00000000..f3522fa7 --- /dev/null +++ b/GSASII/GSASIIgroupGUI.py @@ -0,0 +1,1108 @@ +# -*- coding: utf-8 -*- +''' +Routines for working with groups of histograms. + +Groups are defined in Controls entry ['Groups'] which contains three entries: + +* Controls['Groups']['groupDict'] + a dict where each key is the name of the group and the value is a list of + histograms in the group +* Controls['Groups']['notGrouped'] + a count of the number of histograms that are not in any group +* Controls['Groups']['template'] + the string used to set the grouping + +** Parameter Data Table ** + +For use to create GUI tables and to copy values between histograms, parameters +are organized in a dict where each dict entry has contents of form: + + * dict['__dataSource'] : SourceArray + + * dict[histogram]['label'] : `innerdict` + +where `label` is the text shown on the row label and `innerdict` can contain +one or more of the following elements: + + * 'val' : (key1,key2,...) + * 'ref' : (key1, key2,...) + * 'range' : (float,float) + * 'str' : (key1,key2,...) + * 'fmt' : str + * 'txt' : str + * 'init' : float + * 'rowlbl' : (array, key) + +One of 'val', 'ref' or 'str' elements will be present. + + * The 'val' tuple provides a reference to the float value for the + defined quantity, where SourceArray[histogram][key1][key2][...] + provides r/w access to the parameter. + + * The 'ref' tuple provides a reference to the bool value, where + SourceArray[histogram][key1][key2][...] provides r/w access to the + refine flag for the labeled quantity + + Both 'ref' and 'val' are usually defined together, but either may + occur alone. These exceptions will be for parameters where a single + refine flag is used for a group of parameters or for non-refined + parameters. + + * The 'str' value is something that cannot be edited from the GUI; If 'str' is + present, the only other possible entries that can be present is either 'fmt' + or 'txt. + 'str' is used for a parameter value that is typically computed or must be + edited in the histogram section. + + * The 'fmt' value is a string used to format the 'str' value to + convert it to a string, if it is a float or int value. + + * The 'txt' value is a string that replaces the value in 'str'. + + * The 'init' value is also something that cannot be edited. + These 'init' values are used for Instrument Parameters + where there is both a current value for the parameter as + well as an initial value (usually read from the instrument + parameters file when the histogram is read. If 'init' is + present in `innerdict`, there will also be a 'val' entry + in `innerdict` and likely a 'ref' entry as well. + + * The 'rowlbl' value provides a reference to a str value that + will be an editable row label (FreePrmX sample parametric + values). + + * The 'range' list/tuple provides min and max float value for the + defined quantity to be defined. Use None for any value that + should not be enforced. The 'range' values will be used as limits + for the entry widget. + +''' + +# import math +# import os +import re +# import copy +# import platform +# import pickle +# import sys +# import random as ran + +#import numpy as np +# import numpy.ma as ma +import wx + +# from . import GSASIIpath +from . import GSASIIdataGUI as G2gd +# from . import GSASIIobj as G2obj +# from . import GSASIIpwdGUI as G2pdG +# from . import GSASIIimgGUI as G2imG +# from . import GSASIIElem as G2el +# from . import GSASIIfiles as G2fil +# from . import GSASIIctrlGUI as G2G +# from . import GSASIImath as G2mth +# from . import GSASIIElem as G2elem +from . import GSASIIspc as G2spc +from . import GSASIIlattice as G2lat +# from . import GSASIIpwd as G2pwd +from . import GSASIIctrlGUI as G2G +from . import GSASIIpwdplot as G2pwpl +WACV = wx.ALIGN_CENTER_VERTICAL + +def SearchGroups(G2frame,Histograms,hist): + '''Determine which histograms are in groups, called by SearchGroups in + :func:`GSASIIdataGUI.UpdateControls`. + ''' + repeat = True + srchStr = hist[5:] + msg = ('Edit the histogram name below placing a question mark (?) ' + 'at the location ' + 'of characters that change between groups of ' + 'histograms. Use backspace or delete to remove ' + 'characters that should be ignored as they will ' + 'vary within a histogram group (e.g. Bank 1). ' + 'Be sure to leave enough characters so the string ' + 'can be uniquely matched.') + while repeat: + srchStr = G2G.StringSearchTemplate(G2frame,'Set match template', + msg,srchStr) + if srchStr is None: return {} # cancel pressed + reSrch = re.compile(srchStr.replace('.',r'\.').replace('?','.')) + setDict = {} + keyList = [] + noMatchCount = 0 + for hist in Histograms: + if hist.startswith('PWDR '): + m = reSrch.search(hist) + if m: + key = hist[m.start():m.end()] + setDict[hist] = key + if key not in keyList: keyList.append(key) + else: + noMatchCount += 1 + groupDict = {} + groupCount = {} + for k in keyList: + groupDict[k] = [hist for hist,key in setDict.items() if k == key] + groupCount[k] = len(groupDict[k]) + + msg1 = f'With search template "{srchStr}", ' + + buttons = [] + OK = True + if len(groupCount) == 0: + msg1 += f'there are ho histograms in any groups.' + elif min(groupCount.values()) == max(groupCount.values()): + msg1 += f'there are {len(groupDict)} groups with {min(groupCount.values())} histograms each.' + else: + msg1 += (f'there are {len(groupDict)} groups with between {min(groupCount.values())}' + f' and {min(groupCount.values())} histograms in each.') + if noMatchCount: + msg1 += f"\n\nNote that {noMatchCount} PWDR histograms were not included in any groups." + # place a sanity check limit on the number of histograms in a group + if len(groupCount) == 0: + OK = False + elif max(groupCount.values()) >= 150: + OK = False + msg1 += '\n\nThis exceeds the maximum group length of 150 histograms' + elif min(groupCount.values()) == max(groupCount.values()) == 1: + OK = False + msg1 += '\n\nEach histogram is in a separate group. Grouping histograms only makes sense with multiple histograms in at least some groups.' + if OK: + buttons += [('OK', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK))] + buttons += [('try again', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_CANCEL))] + res = G2G.ShowScrolledInfo(G2frame,msg1,header='Grouping result', + buttonlist=buttons,height=150) + if res == wx.ID_OK: + repeat = False + + return {'groupDict':groupDict,'notGrouped':noMatchCount,'template':srchStr} + +def UpdateGroup(G2frame,item,plot=True): + def onDisplaySel(event): + G2frame.GroupInfo['displayMode'] = dsplType.GetValue() + wx.CallAfter(UpdateGroup,G2frame,item,False) + #UpdateGroup(G2frame,item) + def OnCopyAll(event): + G2G.G2MessageBox(G2frame, + 'Sorry, not fully implemented yet', + 'In progress') + return + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + groupName = G2frame.GroupInfo['groupName'] + # make a list of groups of the same length as the current + curLen = len(groupDict[groupName]) + matchGrps = [] + for g in groupDict: + if g == groupName: continue + if curLen != len(groupDict[g]): continue + matchGrps.append(g) + if len(matchGrps) == 0: + G2G.G2MessageBox(G2frame, + f'No groups found with {curLen} histograms', + 'No matching groups') + return + elif len(matchGrps) > 1: + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Copy to which groups?', 'Copy to?', matchGrps) + try: + if dlg.ShowModal() == wx.ID_OK: + selList = [matchGrps[i] for i in dlg.GetSelections()] + finally: + dlg.Destroy() + else: + selList = matchGrps + if len(selList) == 0: return + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + prmArray = None + if G2frame.GroupInfo['displayMode'].startswith('Sample'): + prmArray = getSampleVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): + prmArray = getInstVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Limits'): + prmArray = getLimitVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Background'): + prmArray = getBkgVals(G2frame,Histograms) + else: + print('Unexpected', G2frame.GroupInfo['displayMode']) + return + for h in selList: # group + for src,dst in zip(groupDict[groupName],groupDict[h]): + print('copy',src,'to',dst) + # for i in prmTable[src]: + # #if i not in prmTable[dst]: + # # print + # # continue + # if 'val' in prmTable[src][i]: + # breakpoint() + # so what do we copy? + #breakpoint() + print('OnCopyAll') + def OnCopySel(event): + print('OnCopySel') + G2G.G2MessageBox(G2frame, + f'Sorry, not fully implemented yet', + 'In progress') + return + + if not hasattr(G2frame,'GroupInfo'): + G2frame.GroupInfo = {} + G2frame.GroupInfo['displayMode'] = G2frame.GroupInfo.get('displayMode','Sample') + G2frame.GroupInfo['groupName'] = G2frame.GPXtree.GetItemText(item) + G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.GroupMenu) + G2frame.Bind(wx.EVT_MENU, OnCopyAll, id=G2G.wxID_GRPALL) + G2frame.Bind(wx.EVT_MENU, OnCopySel, id=G2G.wxID_GRPSEL) + + G2frame.dataWindow.ClearData() + G2frame.dataWindow.helpKey = "Groups/Powder" + topSizer = G2frame.dataWindow.topBox + topParent = G2frame.dataWindow.topPanel + dsplType = wx.ComboBox(topParent,wx.ID_ANY, + value=G2frame.GroupInfo['displayMode'], + choices=['Hist/Phase','Sample','Instrument', + 'Instrument-\u0394', + 'Limits','Background'], + style=wx.CB_READONLY|wx.CB_DROPDOWN) + dsplType.Bind(wx.EVT_COMBOBOX, onDisplaySel) + topSizer.Add(dsplType,0,WACV) + topSizer.Add(wx.StaticText(topParent, + label=f' parameters for group "{histLabels(G2frame)[0]}"'), + 0,WACV) + topSizer.Add((-1,-1),1,wx.EXPAND) + topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) + + if G2frame.GroupInfo['displayMode'].startswith('Hist'): + HAPframe(G2frame) + else: + HistFrame(G2frame) + if plot: G2pwpl.PlotPatterns(G2frame,plotType='GROUP') + G2frame.dataWindow.SetDataSize() + #wx.CallLater(100,G2frame.SendSizeEvent) + wx.CallAfter(G2frame.SendSizeEvent) + +def histLabels(G2frame): + '''Find portion of the set of hist names that are the same for all + histograms in the current group (determined by ``G2frame.GroupInfo['groupName']``) + and then for each histogram, the characters that are different. + + :Returns: commonltrs, histlbls where + + * commonltrs is a str containing the letters shared by all + histograms in the group and where differing letters are + replaced by a square box. + * histlbls is a list with an str for each histogram listing + the characters that differ in each histogram. + ''' + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( + G2frame,G2frame.root, 'Controls')) + groupName = G2frame.GroupInfo['groupName'] + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + l = max([len(i) for i in groupDict[groupName]]) + h0 = groupDict[groupName][0].ljust(l) + msk = [True] * l + for h in groupDict[groupName][1:]: + msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h.ljust(l),msk)] + # place rectangular box in the loc of non-common letter(s) + commonltrs = ''.join([h0i if m else '\u25A1' for (h0i,m) in zip(h0,msk)]) + # make list with histogram name unique letters + histlbls = [''.join([hi for (hi,m) in zip(h,msk) if not m]) + for h in groupDict[groupName]] + return commonltrs,histlbls + +def indexArrayRef(dataSource,hist,arrayIndices): + indx = arrayIndices[-1] + arr = dataSource[hist] + for i in arrayIndices[:-1]: + arr = arr[i] + return arr,indx + +def indexArrayVal(dataSource,hist,arrayIndices): + if arrayIndices is None: return None + arr = dataSource[hist] + for i in arrayIndices: + arr = arr[i] + return arr + +def onRefineAll(event): + '''Respond to the Refine All button. On the first press, all + refine check buttons are set as "on" and the button is relabeled + as 'C' (for clear). On the second press, all refine check + buttons are set as "off" and the button is relabeled as 'S' (for + set). + ''' + but = event.GetEventObject() + dataSource = but.refDict['dataSource'] + checkButList = but.checkButList + + if but.GetLabelText() == 'S': + setting = True + but.SetLabelText('C') + else: + setting = False + but.SetLabelText('S') + for c in checkButList: + c.SetValue(setting) + for item,hist in zip(but.refDict['arrays'],but.refDict['hists']): + arr,indx = indexArrayRef(dataSource,hist,item) + arr[indx] = setting + +def onSetAll(event): + '''Respond to the copy right button. Copies the first value to + all edit widgets + ''' + but = event.GetEventObject() + dataSource = but.valDict['dataSource'] + valList = but.valDict['arrays'] + histList = but.valDict['hists'] + valEditList = but.valDict['valEditList'] + firstVal = indexArrayVal(dataSource,histList[0],valList[0]) + for c in valEditList: + c.ChangeValue(firstVal) + +def displayDataArray(rowLabels,DataArray,Sizer,Panel,lblRow=False,deltaMode=False, + lblSizer=None,lblPanel=None,CopyCtrl=True): + '''Displays the data table in `DataArray` in Scrolledpanel `Panel` + with wx.FlexGridSizer `Sizer`. + ''' + firstentry = None + #lblRow = True + if lblSizer is None: lblSizer = Sizer + if lblPanel is None: lblPanel = Panel + checkButList = {} + valEditList = {} + lblDict = {} + dataSource = DataArray['_dataSource'] + for row in rowLabels: + checkButList[row] = [] + valEditList[row] = [] + # show the row labels, when not in a separate sizer + if lblRow: + # is a copy across and/or a refine all button needed? + refList = [] + valList = [] + for hist in DataArray: + if row not in DataArray[hist]: continue + if 'val' in DataArray[hist][row]: + valList.append(DataArray[hist][row]['val']) + if 'ref' in DataArray[hist][row]: + refList.append(DataArray[hist][row]['ref']) + + arr = None + histList = [] + for hist in DataArray: + if row not in DataArray[hist]: continue + histList.append(hist) + if 'rowlbl' in DataArray[hist][row]: + arr,key = DataArray[hist][row]['rowlbl'] + break + if arr is None: # text row labels + w = wx.StaticText(lblPanel,label=row) + else: # used for "renameable" sample vars (str) + w = G2G.ValidatedTxtCtrl(lblPanel,arr,key,size=(125,-1)) + lblSizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + lblDict[row] = w + + if len(refList) > 2: + lbl = 'S' + if all([indexArrayVal(dataSource,hist,i) for i in refList]): lbl = 'C' + refAll = wx.Button(lblPanel,label=lbl,style=wx.BU_EXACTFIT) + refAll.refDict = {'arrays': refList, 'hists': histList, + 'dataSource':dataSource} + refAll.checkButList = checkButList[row] + lblSizer.Add(refAll,0,wx.ALIGN_CENTER_VERTICAL) + refAll.Bind(wx.EVT_BUTTON,onRefineAll) + else: + lblSizer.Add((-1,-1)) + + i = -1 + for hist in DataArray: + if hist == '_dataSource': continue + i += 1 + if i == 1 and len(valList) > 2 and not deltaMode and CopyCtrl: + but = wx.Button(Panel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) + but.valDict = {'arrays': valList, 'hists': histList, + 'dataSource':dataSource, + 'valEditList' :valEditList[row]} + Sizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) + but.Bind(wx.EVT_BUTTON,onSetAll) + elif i == 1 and CopyCtrl: + Sizer.Add((-1,-1)) + minval = None + maxval = None + # format the entry depending on what is defined + if row not in DataArray[hist]: + Sizer.Add((-1,-1)) + continue + elif 'range' in DataArray[hist][row]: + minval, maxval = DataArray[hist][row]['range'] + if ('init' in DataArray[hist][row] and + deltaMode and 'ref' in DataArray[hist][row]): + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['val']) + delta = arr[indx] + arr,indx = DataArray[hist][row]['init'] + delta -= arr[indx] + if abs(delta) < 9e-6: delta = 0. + if delta == 0: + deltaS = "" + else: + deltaS = f"\u0394 {delta:.4g} " + valrefsiz = wx.BoxSizer(wx.HORIZONTAL) + valrefsiz.Add(wx.StaticText(Panel,label=deltaS),0) + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['ref']) + w = G2G.G2CheckBox(Panel,'',arr,indx) + valrefsiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL) + checkButList[row].append(w) + Sizer.Add(valrefsiz,0, + wx.EXPAND|wx.ALIGN_RIGHT) + elif 'init' in DataArray[hist][row] and deltaMode: + # does this ever happen? + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['val']) + delta = arr[indx] + arr,indx = DataArray[hist][row]['init'] + delta -= arr[indx] + if delta == 0: + deltaS = "" + else: + deltaS = f"\u0394 {delta:.4g} " + Sizer.Add(wx.StaticText(Panel,label=deltaS),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + elif 'val' in DataArray[hist][row] and 'ref' in DataArray[hist][row]: + valrefsiz = wx.BoxSizer(wx.HORIZONTAL) + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['val']) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1), + nDig=[9,7,'g'], + xmin=minval,xmax=maxval) + valEditList[row].append(w) + valrefsiz.Add(w,0,WACV) + if firstentry is None: firstentry = w + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['ref']) + w = G2G.G2CheckBox(Panel,'',arr,indx) + valrefsiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL) + checkButList[row].append(w) + Sizer.Add(valrefsiz,0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) + elif 'val' in DataArray[hist][row]: + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['val']) + nDig = [9,7,'g'] + if type(arr[indx]) is str: nDig = None + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1), + nDig=nDig, + xmin=minval,xmax=maxval,notBlank=False) + valEditList[row].append(w) + Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) + if firstentry is None: firstentry = w + + elif 'ref' in DataArray[hist][row]: + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['ref']) + w = G2G.G2CheckBox(Panel,'',arr,indx) + Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) + checkButList[row].append(w) + elif 'str' in DataArray[hist][row]: + val = indexArrayVal(dataSource,hist,DataArray[hist][row]['str']) + if 'txt' in DataArray[hist][row]: + val = DataArray[hist][row]['txt'] + elif 'fmt' in DataArray[hist][row]: + f = DataArray[hist][row]['fmt'] + val = f'{val:{f}}' + Sizer.Add(wx.StaticText(Panel,label=val),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) + else: + print('Should not happen',DataArray[hist][row],hist,row) + return firstentry,lblDict + +def HistFrame(G2frame): + '''Put everything in a single FlexGridSizer. + ''' + #--------------------------------------------------------------------- + # generate a dict with values for each histogram + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + CopyCtrl = True + if G2frame.GroupInfo['displayMode'].startswith('Sample'): + prmArray = getSampleVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): + prmArray = getInstVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Limits'): + CopyCtrl = False + prmArray = getLimitVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Background'): + prmArray = getBkgVals(G2frame,Histograms) + CopyCtrl = False + else: + print('Unexpected', G2frame.GroupInfo['displayMode']) + return + rowLabels = [] + lpos = 0 + nonZeroRows = [] + dataSource = prmArray['_dataSource'] + for hist in prmArray: + if hist == '_dataSource': continue + cols = len(prmArray) + prevkey = None + for key in prmArray[hist]: + # find delta-terms that are non-zero + if '\u0394' in G2frame.GroupInfo['displayMode']: + if 'val' in prmArray[hist][key] and 'init' in prmArray[hist][key]: + arr,indx = prmArray[hist][key]['init'] + val = indexArrayVal(dataSource,hist,prmArray[hist][key]['val']) + if abs(val-arr[indx]) > 1e-5: nonZeroRows.append(key) + if key not in rowLabels: + if prevkey is None: + rowLabels.insert(lpos,key) + lpos += 1 + else: + rowLabels.insert(rowLabels.index(prevkey)+1,key) + prevkey = key + # remove rows where delta-terms are all zeros + if '\u0394' in G2frame.GroupInfo['displayMode']: + rowLabels = [i for i in rowLabels if i in nonZeroRows] + #======= Generate GUI =============================================== + # layout the window + panel = midPanel = G2frame.dataWindow + mainSizer = wx.BoxSizer(wx.VERTICAL) + G2G.HorizontalLine(mainSizer,panel) + panel.SetSizer(mainSizer) + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + deltaMode = "\u0394" in G2frame.GroupInfo['displayMode'] + n = 2 + if CopyCtrl and len(prmArray) > 2: n += 1 # add column for copy (when more than one histogram) + valSizer = wx.FlexGridSizer(0,len(prmArray)+n-1,3,10) + mainSizer.Add(valSizer,1,wx.EXPAND) + valSizer.Add(wx.StaticText(midPanel,label=' ')) + valSizer.Add(wx.StaticText(midPanel,label=' Ref ')) + for i,hist in enumerate(histLabels(G2frame)[1]): + if i == 1 and CopyCtrl: + if deltaMode: + valSizer.Add((-1,-1)) + elif CopyCtrl: + valSizer.Add(wx.StaticText(midPanel,label=' Copy ')) + valSizer.Add(wx.StaticText(midPanel, + label=f"\u25A1 = {hist}"), + 0,wx.ALIGN_CENTER) + firstentry,lblDict = displayDataArray(rowLabels,prmArray,valSizer,midPanel, + lblRow=True, + deltaMode=deltaMode,CopyCtrl=CopyCtrl) + if firstentry is not None: # prevent scroll to show last entry + wx.Window.SetFocus(firstentry) + firstentry.SetInsertionPoint(0) # prevent selection of text in widget + +def getSampleVals(G2frame,Histograms): + '''Generate the Parameter Data Table (a dict of dicts) with + all Sample values for all histograms in the + selected histogram group (from G2frame.GroupInfo['groupName']). + This will be used to generate the contents of the GUI for Sample values. + ''' + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupName = G2frame.GroupInfo['groupName'] + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + # parameters to include in table + parms = [] + indexDict = {'_dataSource':Histograms} + def histderef(hist,l): + a = Histograms[hist] + for i in l: + a = a[i] + return a + # loop over histograms in group + for hist in groupDict[groupName]: + indexDict[hist] = {} + indexDict[hist]['Inst. name'] = { + 'val' : ('Sample Parameters','InstrName')} + indexDict[hist]['Diff type'] = { + 'str' : ('Sample Parameters','Type')} + indexDict[hist]['Scale factor'] = { + 'val' : ('Sample Parameters','Scale',0), + 'ref' : ('Sample Parameters','Scale',1),} + # make a list of parameters to show + histType = Histograms[hist]['Instrument Parameters'][0]['Type'][0] + dataType = Histograms[hist]['Sample Parameters']['Type'] + if histType[2] in ['A','B','C']: + parms.append(['Gonio. radius','Gonio radius','.3f']) + #if 'PWDR' in histName: + if dataType == 'Debye-Scherrer': + if 'T' in histType: + parms += [['Absorption','Sample abs, \xb5r/\u03bb',None,]] + else: + parms += [['DisplaceX',u'Sample X displ',None,], + ['DisplaceY','Sample Y displ',None,], + ['Absorption','Sample abs,\xb5\xb7r',None,]] + elif dataType == 'Bragg-Brentano': + parms += [['Shift','Sample displ',None,], + ['Transparency','Sample transp',None], + ['SurfRoughA','Surf rough A',None], + ['SurfRoughB','Surf rough B',None]] + #elif 'SASD' in histName: + # parms.append(['Thick','Sample thickness (mm)',[10,3]]) + # parms.append(['Trans','Transmission (meas)',[10,3]]) + # parms.append(['SlitLen',u'Slit length (Q,\xc5'+Pwrm1+')',[10,3]]) + parms.append(['Omega','Gonio omega',None]) + parms.append(['Chi','Gonio chi',None]) + parms.append(['Phi','Gonio phi',None]) + parms.append(['Azimuth','Detect azimuth',None]) + parms.append(['Time','time',None]) + parms.append(['Temperature','Sample T',None]) + parms.append(['Pressure','Sample P',None]) + + # and loop over them + for key,lbl,fmt in parms: + if fmt is None and type(Histograms[hist]['Sample Parameters'][key]) is list: + indexDict[hist][lbl] = { + 'val' : ('Sample Parameters',key,0), + 'ref' : ('Sample Parameters',key,1),} + + elif fmt is None: + indexDict[hist][lbl] = { + 'val' : ('Sample Parameters',key)} + elif type(fmt) is str: + indexDict[hist][lbl] = { + 'str' : ('Sample Parameters',key), + 'fmt' : fmt} + + for key in ('FreePrm1','FreePrm2','FreePrm3'): + lbl = Controls[key] + indexDict[hist][lbl] = { + 'val' : ('Sample Parameters',key), + 'rowlbl' : (Controls,key) + } + return indexDict + +def getInstVals(G2frame,Histograms): + '''Generate the Parameter Data Table (a dict of dicts) with + all Instrument Parameter values for all histograms in the + selected histogram group (from G2frame.GroupInfo['groupName']). + This will be used to generate the contents of the GUI values. + ''' + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupName = G2frame.GroupInfo['groupName'] + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + # parameters to include in table + indexDict = {'_dataSource':Histograms} + # loop over histograms in group + for hist in groupDict[groupName]: + indexDict[hist] = {} + insVal = Histograms[hist]['Instrument Parameters'][0] + insType = insVal['Type'][1] + if 'Bank' in Histograms[hist]['Instrument Parameters'][0]: + indexDict[hist]['Bank'] = { + 'str' : ('Instrument Parameters',0,'Bank',1), + 'fmt' : '.0f' + } + indexDict[hist]['Hist type'] = { + 'str' : ('Instrument Parameters',0,'Type',1), + } + if insType[2] in ['A','B','C']: #constant wavelength + keylist = [('Azimuth','Azimuth','.3f'),] + if 'Lam1' in insVal: + keylist += [('Lam1','Lambda 1','.6f'), + ('Lam2','Lambda 2','.6f'), + (['Source',1],'Source','s'), + ('I(L2)/I(L1)','I(L2)/I(L1)',None)] + else: + keylist += [('Lam','Lambda',None),] + itemList = ['Zero','Polariz.'] + if 'C' in insType: + itemList += ['U','V','W','X','Y','Z','SH/L'] + elif 'B' in insType: + itemList += ['U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1'] + else: #'A' + itemList += ['U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1','SH/L'] + for lbl in itemList: + keylist += [(lbl,lbl,None),] + elif 'E' in insType: + for lbl in ['XE','YE','ZE','WE']: + keylist += [(lbl,lbl,'.6f'),] + for lbl in ['A','B','C','X','Y','Z']: + keylist += [(lbl,lbl,None),] + elif 'T' in insType: + keylist = [('fltPath','Flight path','.3f'), + ('2-theta','2\u03B8','.2f'),] + for lbl in ['difC','difA','difB','Zero','alpha', + 'beta-0','beta-1','beta-q', + 'sig-0','sig-1','sig-2','sig-q','X','Y','Z']: + keylist += [(lbl,lbl,None),] + else: + return {} + for key,lbl,fmt in keylist: + if fmt is None: + indexDict[hist][lbl] = { + 'init' : (insVal[key],0), + 'val' : ('Instrument Parameters',0,key,1), + 'ref' : ('Instrument Parameters',0,key,2),} + else: + indexDict[hist][lbl] = { + 'str' : ('Instrument Parameters',0,key,1), + 'fmt' : fmt + } + return indexDict + +def getLimitVals(G2frame,Histograms): + '''Generate the Limits Data Table (a dict of dicts) with + all limits values for all histograms in the + selected histogram group (from G2frame.GroupInfo['groupName']). + This will be used to generate the contents of the GUI for limits values. + ''' + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupName = G2frame.GroupInfo['groupName'] + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + # parameters to include in table + indexDict = {'_dataSource':Histograms} + # loop over histograms in group + for hist in groupDict[groupName]: + indexDict[hist] = {} + for lbl,indx in [('Tmin',0),('Tmax',1)]: + indexDict[hist][lbl] = { + 'val' : ('Limits',1,indx), + 'range': [Histograms[hist]['Limits'][0][0], + Histograms[hist]['Limits'][0][1]] + } + for i,item in enumerate(Histograms[hist]['Limits'][2:]): + for l,indx in [('Low',0),('High',1)]: + lbl = f'excl {l} {i+1}' + indexDict[hist][lbl] = { + 'val' : ('Limits',2+i,indx), + 'range': [Histograms[hist]['Limits'][0][0], + Histograms[hist]['Limits'][0][1]]} + return indexDict + +def getBkgVals(G2frame,Histograms): + '''Generate the Background Data Table (a dict of dicts) with + all Background values for all histograms in the + selected histogram group (from G2frame.GroupInfo['groupName']). + This will be used to generate the contents of the GUI for + Background values. + ''' + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupName = G2frame.GroupInfo['groupName'] + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + # parameters to include in table + indexDict = {'_dataSource':Histograms} + # loop over histograms in group + for hist in groupDict[groupName]: + indexDict[hist] = {} + for lbl,indx,typ in [('Function',0,'str'), + ('ref flag',1,'ref'), + ('# Bkg terms',2,'str')]: + indexDict[hist][lbl] = { + typ : ('Background',0,indx) + } + if indx == 2: + indexDict[hist][lbl]['fmt'] = '.0f' + indexDict[hist]['# Debye terms'] = { + 'str' : ('Background',1,'nDebye'), + 'fmt' : '.0f'} + for i,term in enumerate(Histograms[hist]['Background'][1]['debyeTerms']): + for indx,l in enumerate(['A','R','U']): + lbl = f'{l} #{i+1}' + indexDict[hist][lbl] = { + 'val' : ('Background',1,'debyeTerms',i,2*indx), + 'ref' : ('Background',1,'debyeTerms',i,2*indx+1)} + indexDict[hist]['# Bkg Peaks'] = { + 'str' : ('Background',1,'nPeaks'), + 'fmt' : '.0f'} + for i,term in enumerate(Histograms[hist]['Background'][1]['peaksList']): + for indx,l in enumerate(['pos','int','sig','gam']): + lbl = f'{l} #{i+1}' + indexDict[hist][lbl] = { + 'val' : ('Background',1,'peaksList',i,2*indx), + 'ref' : ('Background',1,'peaksList',i,2*indx+1)} + if Histograms[hist]['Background'][1]['background PWDR'][0]: + val = 'yes' + else: + val = 'no' + indexDict[hist]['Fixed bkg file'] = { + 'str' : ('Background',1,'background PWDR',0), + 'txt' : val} + return indexDict + +def HAPframe(G2frame): + '''This creates two side-by-side scrolled panels, each containing + a FlexGridSizer. + The panel to the left contains the labels for the sizer to the right. + This way the labels are not scrolled horizontally and are always seen. + The two vertical scroll bars are linked together so that the labels + are synced to the table of values. + ''' + def selectPhase(event): + 'Display the selected phase' + def OnScroll(event): + 'Synchronize vertical scrolling between the two scrolled windows' + obj = event.GetEventObject() + pos = obj.GetViewStart()[1] + if obj == lblScroll: + HAPScroll.Scroll(-1, pos) + else: + lblScroll.Scroll(-1, pos) + event.Skip() + #--------------------------------------------------------------------- + # selectPhase starts here. Find which phase is selected. + if event: + page = event.GetSelection() + #print('page selected',page,phaseList[page]) + else: # initial call when window is created + page = 0 + #print('no page selected',phaseList[page]) + # generate a dict with HAP values for each phase (may not be the same) + HAParray = getHAPvals(G2frame,phaseList[page]) + # construct a list of row labels, attempting to keep the + # order they appear in the original array + rowLabels = [] + lpos = 0 + for hist in HAParray: + if hist == '_dataSource': continue + prevkey = None + for key in HAParray[hist]: + if key not in rowLabels: + if prevkey is None: + rowLabels.insert(lpos,key) + lpos += 1 + else: + rowLabels.insert(rowLabels.index(prevkey)+1,key) + prevkey = key + #======= Generate GUI =============================================== + for panel in HAPtabs: + if panel.GetSizer(): + panel.GetSizer().Destroy() # clear out old widgets + panel = HAPtabs[page] + bigSizer = wx.BoxSizer(wx.HORIZONTAL) + panel.SetSizer(bigSizer) + # panel for labels; show scroll bars to hold the space + lblScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, + style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) + hpad = 3 # space between rows + lblSizer = wx.FlexGridSizer(0,2,hpad,2) + lblScroll.SetSizer(lblSizer) + bigSizer.Add(lblScroll,0,wx.EXPAND) + + # Create scrolled panel to display HAP data + HAPScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, + style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) + HAPSizer = wx.FlexGridSizer(0,len(HAParray),hpad,10) + HAPScroll.SetSizer(HAPSizer) + bigSizer.Add(HAPScroll,1,wx.EXPAND) + + # Bind scroll events to synchronize scrolling + lblScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) + HAPScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) + # label columns with unique part of histogram names + for i,hist in enumerate(histLabels(G2frame)[1]): + if i == 1: + HAPSizer.Add(wx.StaticText(HAPScroll,label='Copy'), + 0,wx.ALIGN_CENTER) + HAPSizer.Add(wx.StaticText(HAPScroll,label=f"\u25A1 = {hist}"), + 0,wx.ALIGN_CENTER) + w0 = wx.StaticText(lblScroll,label=' ') + lblSizer.Add(w0) + lblSizer.Add(wx.StaticText(lblScroll,label=' Ref ')) + firstentry,lblDict = displayDataArray(rowLabels,HAParray,HAPSizer,HAPScroll, + lblRow=True,lblSizer=lblSizer,lblPanel=lblScroll) + # get row sizes in data table + HAPSizer.Layout() + rowHeights = HAPSizer.GetRowHeights() + # set row sizes in Labels + # (must be done after HAPSizer row heights are defined) + s = wx.Size(-1,rowHeights[0]) + w0.SetMinSize(s) + for i,row in enumerate(rowLabels): + s = wx.Size(-1,rowHeights[i+1]) + lblDict[row].SetMinSize(s) + # Fit the scrolled windows to their content + lblSizer.Layout() + xLbl,_ = lblSizer.GetMinSize() + xTab,yTab = HAPSizer.GetMinSize() + lblScroll.SetSize((xLbl,yTab)) + lblScroll.SetMinSize((xLbl+15,yTab)) # add room for scroll bar + lblScroll.SetVirtualSize(lblSizer.GetMinSize()) + HAPScroll.SetVirtualSize(HAPSizer.GetMinSize()) + lblScroll.SetupScrolling(scroll_x=True, scroll_y=True, rate_x=20, rate_y=20) + HAPScroll.SetupScrolling(scroll_x=True, scroll_y=True, rate_x=20, rate_y=20) + if firstentry is not None: # prevent scroll to show last entry + wx.Window.SetFocus(firstentry) + firstentry.SetInsertionPoint(0) # prevent selection of text in widget + + #G2frame.dataWindow.ClearData() + + # layout the HAP window. This has histogram and phase info, so a + # notebook is needed for phase name selection. (That could + # be omitted for single-phase refinements, but better to remind the + # user of the phase + # topSizer = G2frame.dataWindow.topBox + # topParent = G2frame.dataWindow.topPanel + midPanel = G2frame.dataWindow + mainSizer = wx.BoxSizer(wx.VERTICAL) + #botSizer = G2frame.dataWindow.bottomBox + #botParent = G2frame.dataWindow.bottomPanel + + G2G.HorizontalLine(mainSizer,midPanel) + midPanel.SetSizer(mainSizer) + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + if not Phases: + mainSizer.Add(wx.StaticText(midPanel, + label='There are no phases in use')) + G2frame.dataWindow.SetDataSize() + return + # notebook for phases + HAPBook = G2G.GSNoteBook(parent=midPanel) + mainSizer.Add(HAPBook,1,wx.ALL|wx.EXPAND,1) + HAPtabs = [] + phaseList = [] + for phaseName in Phases: + phaseList.append(phaseName) + HAPtabs.append(wx.Panel(HAPBook)) + HAPBook.AddPage(HAPtabs[-1],phaseName) + HAPBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, selectPhase) + + page = 0 + HAPBook.SetSelection(page) + selectPhase(None) + #G2frame.dataWindow.SetDataSize() + +def getHAPvals(G2frame,phase): + '''Generate the Parameter Data Table (a dict of dicts) with + all HAP values for the selected phase and all histograms in the + selected histogram group (from G2frame.GroupInfo['groupName']). + This will be used to generate the contents of the GUI for HAP values. + ''' + sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') + item, cookie = G2frame.GPXtree.GetFirstChild(sub) + PhaseData = None + while item: # loop over phases + phaseName = G2frame.GPXtree.GetItemText(item) + if phase is None: phase = phaseName + if phase == phaseName: + PhaseData = G2frame.GPXtree.GetItemPyData(item) + break + item, cookie = G2frame.GPXtree.GetNextChild(sub, cookie) + if PhaseData is None: + print(f'Unexpected: Phase {phase!r} not found') + return {} + + SGData = PhaseData['General']['SGData'] + cell = PhaseData['General']['Cell'][1:] + Amat,Bmat = G2lat.cell2AB(cell[:6]) + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + groupName = G2frame.GroupInfo['groupName'] + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + indexDict = {'_dataSource':PhaseData['Histograms']} + for hist in groupDict[groupName]: + indexDict[hist] = {} + # phase fraction + indexDict[hist]['Phase frac'] = { + 'val' : ('Scale',0), + 'ref' : ('Scale',1),} + PhaseData['Histograms'][hist]['LeBail'] = PhaseData['Histograms'][hist].get('LeBail',False) + indexDict[hist]['LeBail extract'] = { + 'str' : ('LeBail',), + 'txt' : "Yes" if PhaseData['Histograms'][hist]['LeBail'] else '(off)'} + # size values + if PhaseData['Histograms'][hist]['Size'][0] == 'isotropic': + indexDict[hist]['Size'] = { + 'val' : ('Size',1,0), + 'ref' : ('Size',2,0),} + elif PhaseData['Histograms'][hist]['Size'][0] == 'uniaxial': + indexDict[hist]['Size/Eq'] = { + 'val' : ('Size',1,0), + 'ref' : ('Size',2,0),} + indexDict[hist]['Size/Ax'] = { + 'val' : ('Size',1,1), + 'ref' : ('Size',2,1),} + indexDict[hist]['Size/dir'] = { + 'str' : ('Size',3), + 'txt' : ','.join([str(i) for i in PhaseData['Histograms'][hist]['Size'][3]])} + else: + for i,lbl in enumerate(['S11','S22','S33','S12','S13','S23']): + indexDict[hist][f'Size/{lbl}'] = { + 'val' : ('Size',4,i), + 'ref' : ('Size',5,i),} + indexDict[hist]['Size LGmix'] = { + 'val' : ('Size',1,2), + 'ref' : ('Size',2,2),} + # microstrain values + if PhaseData['Histograms'][hist]['Mustrain'][0] == 'isotropic': + indexDict[hist]['\u00B5Strain'] = { + 'val' : ('Mustrain',1,0), + 'ref' : ('Mustrain',2,0),} + elif PhaseData['Histograms'][hist]['Mustrain'][0] == 'uniaxial': + indexDict[hist]['\u00B5Strain/Eq'] = { + 'val' : ('Mustrain',1,0), + 'ref' : ('Mustrain',2,0),} + indexDict[hist]['\u00B5Strain/Ax'] = { + 'val' : ('Mustrain',1,1), + 'ref' : ('Mustrain',2,1),} + indexDict[hist]['\u00B5Strain/dir'] = { + 'str' : ('Mustrain',3), + 'txt' : ','.join([str(i) for i in PhaseData['Histograms'][hist]['Mustrain'][3]])} + else: + Snames = G2spc.MustrainNames(SGData) + for i,lbl in enumerate(Snames): + if i >= len(PhaseData['Histograms'][hist]['Mustrain'][4]): break + indexDict[hist][f'\u00B5Strain/{lbl}'] = { + 'val' : ('Mustrain',4,i), + 'ref' : ('Mustrain',5,i),} + muMean = G2spc.MuShklMean(SGData,Amat,PhaseData['Histograms'][hist]['Mustrain'][4][:len(Snames)]) + indexDict[hist]['\u00B5Strain/mean'] = { + 'str' : None, + 'txt' : f'{muMean:.2f}'} + indexDict[hist]['\u00B5Strain LGmix'] = { + 'val' : ('Mustrain',1,2), + 'ref' : ('Mustrain',2,2),} + + # Hydrostatic terms + Hsnames = G2spc.HStrainNames(SGData) + for i,lbl in enumerate(Hsnames): + if i >= len(PhaseData['Histograms'][hist]['HStrain'][0]): break + indexDict[hist][f'Size/{lbl}'] = { + 'val' : ('HStrain',0,i), + 'ref' : ('HStrain',1,i),} + + # Preferred orientation terms + if PhaseData['Histograms'][hist]['Pref.Ori.'][0] == 'MD': + indexDict[hist]['March-Dollase'] = { + 'val' : ('Pref.Ori.',1), + 'ref' : ('Pref.Ori.',2),} + indexDict[hist]['M-D/dir'] = { + 'str' : ('Pref.Ori.',3), + 'txt' : ','.join([str(i) for i in PhaseData['Histograms'][hist]['Pref.Ori.'][3]])} + else: + indexDict[hist]['Spherical harmonics'] = { + 'ref' : ('Pref.Ori.',2),} + indexDict[hist]['SH order'] = { + 'str' : str('Pref.Ori.',4)} + for lbl in arr[5]: + indexDict[hist][f'SP {lbl}']= { + 'val' : ('Pref.Ori.',5,lbl), + } + indexDict[hist]['SH text indx'] = { + 'str' : None, + 'txt' : f'{G2lat.textureIndex('Pref.Ori.'[5]):.3f}'} + + # misc: Layer Disp, Extinction + if 'Layer Disp' in PhaseData['Histograms'][hist]: + indexDict[hist]['Layer displ'] = { + 'val' : ('Layer Disp',0), + 'ref' : ('Layer Disp',1),} + if 'Extinction' in PhaseData['Histograms'][hist]: + indexDict[hist]['Extinction'] = { + 'val' : ('Extinction',0), + 'ref' : ('Extinction',1),} + if 'Babinet' in PhaseData['Histograms'][hist]: + indexDict[hist]['Babinet A'] = { + 'val' : ('Babinet','BabA',0), + 'ref' : ('Babinet','BabA',1),} + if 'Babinet' in PhaseData['Histograms'][hist]: + indexDict[hist]['Babinet U'] = { + 'val' : ('Babinet','BabU',0), + 'ref' : ('Babinet','BabU',1),} + return indexDict diff --git a/GSASII/GSASIImiscGUI.py b/GSASII/GSASIImiscGUI.py index daac1cf8..7e12fa40 100644 --- a/GSASII/GSASIImiscGUI.py +++ b/GSASII/GSASIImiscGUI.py @@ -8,8 +8,6 @@ ''' -from __future__ import division, print_function - # # Allow this to be imported without wx present. # try: # import wx @@ -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 @@ -575,6 +575,14 @@ 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') @@ -582,6 +590,8 @@ def ProjFileOpen(G2frame,showProvenance=True): 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': @@ -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)]) diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index d6332d88..fa487ffa 100644 --- a/GSASII/GSASIIplot.py +++ b/GSASII/GSASIIplot.py @@ -214,14 +214,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} @@ -395,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): '''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 @@ -409,9 +409,6 @@ 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). :returns: new,plotNum,Page,Plot,limits where * new: will be True if the tab was just created @@ -444,7 +441,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': @@ -469,6 +466,7 @@ def FindPlotTab(self,label,Type,newImage=True,publish=None): Page.helpKey = self.G2frame.dataWindow.helpKey except AttributeError: Page.helpKey = 'HelpIntro' + Page.toolbar.enableArrows() # Disable Arrow keys if present return new,plotNum,Page,Plot,limits def _addPage(self,name,page): @@ -493,9 +491,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 @@ -606,7 +604,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 @@ -631,16 +629,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) @@ -679,6 +686,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] @@ -736,6 +759,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' diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 2b05674d..af9777cf 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -83,6 +83,7 @@ plotOpt['lineWid'] = '1' plotOpt['saveCSV'] = False plotOpt['CSVfile'] = None +plotOpt['sharedX'] = False for xy in 'x','y': for minmax in 'min','max': key = f'{xy}{minmax}' @@ -123,6 +124,7 @@ def ReplotPattern(G2frame,newPlot,plotType,PatternName=None,PickName=None): def plotVline(Page,Plot,Lines,Parms,pos,color,pickrad,style='dotted'): '''shortcut to plot vertical lines for limits & Laue satellites. Was used for extrapeaks''' + if not pickrad: pickrad = 0.0 if Page.plotStyle['qPlot']: Lines.append(Plot.axvline(2.*np.pi/G2lat.Pos2dsp(Parms,pos),color=color, picker=pickrad,linestyle=style)) @@ -198,7 +200,7 @@ def OnPlotKeyPress(event): try: #one way to check if key stroke will work on plot Parms,Parms2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Instrument Parameters')) except TypeError: - G2frame.G2plotNB.status.SetStatusText('Select '+plottype+' pattern first',1) + G2frame.G2plotNB.status.SetStatusText(f'Select {plottype} pattern first',1) return newPlot = False if event.key == 'w': @@ -222,10 +224,14 @@ def OnPlotKeyPress(event): G2frame.ErrorBars = not G2frame.ErrorBars elif event.key == 'T' and 'PWDR' in plottype: Page.plotStyle['title'] = not Page.plotStyle.get('title',True) - elif event.key == 'f' and 'PWDR' in plottype: # short,full length or no tick-marks + elif event.key == 'f' and ('PWDR' in plottype or 'GROUP' in plottype): # short,full length or no tick-marks if G2frame.Contour: return Page.plotStyle['flTicks'] = (Page.plotStyle.get('flTicks',0)+1)%3 - elif event.key == 'x'and 'PWDR' in plottype: + elif event.key == 'x' and groupName is not None: # share X axis scale for Pattern Groups + plotOpt['sharedX'] = not plotOpt['sharedX'] + if not plotOpt['sharedX']: # reset scale + newPlot = True + elif event.key == 'x' and 'PWDR' in plottype: Page.plotStyle['exclude'] = not Page.plotStyle['exclude'] elif event.key == '.': Page.plotStyle['WgtDiagnostic'] = not Page.plotStyle.get('WgtDiagnostic',False) @@ -335,8 +341,8 @@ def OnPlotKeyPress(event): G2frame.Cmin = 0.0 Page.plotStyle['Offset'] = [0,0] elif event.key == 'C' and 'PWDR' in plottype and G2frame.Contour: - #G2G.makeContourSliders(G2frame,Ymax,PlotPatterns,newPlot,plotType) - G2G.makeContourSliders(G2frame,Ymax,PlotPatterns,True,plotType) # force newPlot=True, prevents blank plot on Mac + #G2G.makeContourSliders(G2frame,Ymax,PlotPatterns,newPlot,plottype) + G2G.makeContourSliders(G2frame,Ymax,PlotPatterns,True,plottype) # force newPlot=True, prevents blank plot on Mac elif event.key == 'c' and 'PWDR' in plottype: newPlot = True if not G2frame.Contour: @@ -350,8 +356,11 @@ def OnPlotKeyPress(event): Page.plotStyle['partials'] = not Page.plotStyle['partials'] elif (event.key == 'e' and 'PWDR' in plottype and G2frame.SinglePlot and ifLimits and not G2frame.Contour): + # set limits in response to the'e' key. First press sets one side + # in Page.startExclReg. Second 'e' press defines other side and + # causes region to be saved (after d/Q conversion if needed) Page.excludeMode = not Page.excludeMode - if Page.excludeMode: + if Page.excludeMode: # first key press try: # fails from key menu Page.startExclReg = event.xdata except AttributeError: @@ -367,17 +376,24 @@ def OnPlotKeyPress(event): y1, y2= Page.figure.axes[0].get_ylim() Page.vLine = Plot.axvline(Page.startExclReg,color='b',dashes=(2,3)) Page.canvas.draw() - else: + else: # second key press Page.savedplot = None - wx.CallAfter(PlotPatterns,G2frame,newPlot=False, - plotType=plottype,extraKeys=extraKeys) - if abs(Page.startExclReg - event.xdata) < 0.1: return LimitId = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Limits') limdat = G2frame.GPXtree.GetItemPyData(LimitId) mn = min(Page.startExclReg, event.xdata) mx = max(Page.startExclReg, event.xdata) + if Page.plotStyle['qPlot']: + mn = G2lat.Dsp2pos(Parms,2.0*np.pi/mn) + mx = G2lat.Dsp2pos(Parms,2.0*np.pi/mx) + elif Page.plotStyle['dPlot']: + mn = G2lat.Dsp2pos(Parms,mn) + mx = G2lat.Dsp2pos(Parms,mx) + if mx < mn: mx,mn = mn,mx + #if abs(mx - mn) < 0.1: return # very small regions are ignored limdat.append([mn,mx]) G2pdG.UpdateLimitsGrid(G2frame,limdat,plottype) + wx.CallAfter(PlotPatterns,G2frame,newPlot=False, + plotType=plottype,extraKeys=extraKeys) return elif event.key == 'a' and 'PWDR' in plottype and G2frame.SinglePlot and not ( Page.plotStyle['logPlot'] or Page.plotStyle['sqrtPlot'] or G2frame.Contour): @@ -401,9 +417,9 @@ def OnPlotKeyPress(event): Pattern[0]['Magnification'] += [[xpos,2.]] wx.CallAfter(G2gd.UpdatePWHKPlot,G2frame,plottype,G2frame.PatternId) return - elif event.key == 'q' and not ifLimits: + elif event.key == 'q': newPlot = True - if 'PWDR' in plottype: + if 'PWDR' in plottype or plottype.startswith('GROUP'): Page.plotStyle['qPlot'] = not Page.plotStyle['qPlot'] Page.plotStyle['dPlot'] = False Page.plotStyle['chanPlot'] = False @@ -417,8 +433,8 @@ def OnPlotKeyPress(event): elif event.key == 'e' and G2frame.Contour: newPlot = True G2frame.TforYaxis = not G2frame.TforYaxis - elif event.key == 't' and 'PWDR' in plottype and not ifLimits: - newPlot = True + elif event.key == 't' and ('PWDR' in plottype or plottype.startswith('GROUP')): + newPlot = True Page.plotStyle['dPlot'] = not Page.plotStyle['dPlot'] Page.plotStyle['qPlot'] = False Page.plotStyle['chanPlot'] = False @@ -429,7 +445,7 @@ def OnPlotKeyPress(event): G2frame.Contour = False newPlot = True elif event.key == 'F' and not G2frame.SinglePlot: - choices = G2gd.GetGPXtreeDataNames(G2frame,plotType) + choices = G2gd.GetGPXtreeDataNames(G2frame,plottype) dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select dataset(s) to plot\n(select all or none to reset)', 'Multidata plot selection',choices) @@ -486,7 +502,7 @@ def OnMotion(event): global PlotList G2plt.SetCursor(Page) # excluded region animation - if Page.excludeMode and Page.savedplot: + if Page.excludeMode and Page.savedplot: # defining an excluded region if event.xdata is None or G2frame.GPXtree.GetItemText( G2frame.GPXtree.GetSelection()) != 'Limits': # reset if out of bounds or not on limits Page.savedplot = None @@ -494,7 +510,7 @@ def OnMotion(event): wx.CallAfter(PlotPatterns,G2frame,newPlot=False, plotType=plottype,extraKeys=extraKeys) return - else: + else: # mouse is out of plot region, give up on this region Page.canvas.restore_region(Page.savedplot) Page.vLine.set_xdata([event.xdata,event.xdata]) if G2frame.Weight: @@ -502,7 +518,7 @@ def OnMotion(event): else: axis = Page.figure.gca() axis.draw_artist(Page.vLine) - Page.canvas.blit(axis.bbox) + Page.canvas.blit(Page.figure.bbox) return elif Page.excludeMode or Page.savedplot: # reset if out of mode somehow Page.savedplot = None @@ -642,7 +658,7 @@ def OnMotion(event): Page.SetToolTipString(s) except TypeError: - G2frame.G2plotNB.status.SetStatusText('Select '+plottype+' pattern first',1) + G2frame.G2plotNB.status.SetStatusText(f'Select {plottype} pattern first',1) def OnPress(event): #ugh - this removes a matplotlib error for mouse clicks in log plots np.seterr(invalid='ignore') @@ -702,9 +718,15 @@ def OnPickPwd(event): to create a peak or an excluded region ''' def OnDragMarker(event): - '''Respond to dragging of a plot Marker + '''Respond to dragging of a plot Marker (fixed background point) ''' - if event.xdata is None or event.ydata is None: return # ignore if cursor out of window + if event.xdata is None or event.ydata is None: # mouse is out of plot area, reset drag + G2frame.itemPicked = None + if G2frame.cid is not None: # delete drag connection + Page.canvas.mpl_disconnect(G2frame.cid) + G2frame.cid = None + wx.CallAfter(PlotPatterns,G2frame,plotType=plottype,extraKeys=extraKeys) + return if G2frame.itemPicked is None: return # not sure why this happens, if it does Page.canvas.restore_region(savedplot) G2frame.itemPicked.set_data([event.xdata], [event.ydata]) @@ -713,28 +735,40 @@ def OnDragMarker(event): else: axis = Page.figure.gca() axis.draw_artist(G2frame.itemPicked) - Page.canvas.blit(axis.bbox) + Page.canvas.blit(Page.figure.bbox) def OnDragLine(event): '''Respond to dragging of a plot line ''' - if event.xdata is None: return # ignore if cursor out of window + if event.xdata is None: # mouse is out of plot area, reset drag + G2frame.itemPicked = None + if G2frame.cid is not None: # delete drag connection + Page.canvas.mpl_disconnect(G2frame.cid) + G2frame.cid = None + wx.CallAfter(PlotPatterns,G2frame,plotType=plottype,extraKeys=extraKeys) + return if G2frame.itemPicked is None: return # not sure why this happens Page.canvas.restore_region(savedplot) coords = G2frame.itemPicked.get_data() coords[0][0] = coords[0][1] = event.xdata - coords = G2frame.itemPicked.set_data(coords) + G2frame.itemPicked.set_data(coords) if G2frame.Weight: axis = Page.figure.axes[1] else: axis = Page.figure.gca() axis.draw_artist(G2frame.itemPicked) - Page.canvas.blit(axis.bbox) + Page.canvas.blit(Page.figure.bbox) def OnDragLabel(event): '''Respond to dragging of a HKL label ''' - if event.xdata is None: return # ignore if cursor out of window + if event.ydata is None: # mouse is out of plot area, reset drag + G2frame.itemPicked = None + if G2frame.cid is not None: # delete drag connection + Page.canvas.mpl_disconnect(G2frame.cid) + G2frame.cid = None + wx.CallAfter(PlotPatterns,G2frame,plotType=plottype,extraKeys=extraKeys) + return if G2frame.itemPicked is None: return # not sure why this happens try: coords = list(G2frame.itemPicked.get_position()) @@ -753,14 +787,20 @@ def OnDragLabel(event): else: axis = Page.figure.gca() axis.draw_artist(G2frame.itemPicked) - Page.canvas.blit(axis.bbox) + Page.canvas.blit(Page.figure.bbox) except: pass def OnDragTickmarks(event): '''Respond to dragging of the reflection tick marks ''' - if event.ydata is None: return # ignore if cursor out of window + if event.ydata is None: # mouse is out of plot area, reset drag + G2frame.itemPicked = None + if G2frame.cid is not None: # delete drag connection + Page.canvas.mpl_disconnect(G2frame.cid) + G2frame.cid = None + wx.CallAfter(PlotPatterns,G2frame,plotType=plottype,extraKeys=extraKeys) + return if Page.tickDict is None: return # not sure why this happens, if it does Page.canvas.restore_region(savedplot) if Page.pickTicknum: @@ -779,12 +819,19 @@ def OnDragTickmarks(event): coords[1][:] = pos Page.tickDict[phase].set_data(coords) axis.draw_artist(Page.tickDict[phase]) - Page.canvas.blit(axis.bbox) + Page.canvas.blit(Page.figure.bbox) def OnDragDiffCurve(event): '''Respond to dragging of the difference curve. ''' - if event.ydata is None: return # ignore if cursor out of window + if event.ydata is None: # mouse is out of plot area, reset drag + G2frame.itemPicked = None + if G2frame.cid is not None: # delete drag connection + Page.canvas.mpl_disconnect(G2frame.cid) + G2frame.cid = None + wx.CallAfter(PlotPatterns,G2frame,plotType=plottype,extraKeys=extraKeys) + return + return # ignore if cursor out of window if G2frame.itemPicked is None: return # not sure why this happens Page.canvas.restore_region(savedplot) coords = G2frame.itemPicked.get_data() @@ -792,8 +839,8 @@ def OnDragDiffCurve(event): Page.diffOffset = -event.ydata G2frame.itemPicked.set_data(coords) Page.figure.gca().draw_artist(G2frame.itemPicked) # Diff curve only found in 1-window plot - Page.canvas.blit(Page.figure.gca().bbox) - + Page.canvas.blit(Page.figure.bbox) + def DeleteHKLlabel(HKLmarkers,key): '''Delete an HKL label''' del HKLmarkers[key[0]][key[1]] @@ -938,20 +985,24 @@ def DeleteHKLlabel(HKLmarkers,key): if ind.all() != [0]: #picked a data point LimitId = G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId, 'Limits') limData = G2frame.GPXtree.GetItemPyData(LimitId) - # Q & d not currently allowed on limits plot - # if Page.plotStyle['qPlot']: #qplot - convert back to 2-theta - # xy[0] = G2lat.Dsp2pos(Parms,2*np.pi/xy[0]) - # elif Page.plotStyle['dPlot']: #dplot - convert back to 2-theta - # xy[0] = G2lat.Dsp2pos(Parms,xy[0]) + # reverse setting limits for Q plot & TOF + if Page.plotStyle['qPlot'] and 'T' in Parms['Type'][0]: + if G2frame.ifSetLimitsMode == 2: + G2frame.ifSetLimitsMode = 1 + elif G2frame.ifSetLimitsMode == 1: + G2frame.ifSetLimitsMode = 2 + # set limit selected limit or excluded region after menu command if G2frame.ifSetLimitsMode == 3: # add an excluded region excl = [0,0] excl[0] = max(limData[1][0],min(xy[0],limData[1][1])) excl[1] = excl[0]+0.1 limData.append(excl) - elif G2frame.ifSetLimitsMode == 2: # set upper - limData[1][1] = max(xy[0],limData[1][0]) + elif G2frame.ifSetLimitsMode == 2: + limData[1][1] = min(limData[0][1],max(xy[0],limData[0][0])) # upper elif G2frame.ifSetLimitsMode == 1: - limData[1][0] = min(xy[0],limData[1][1]) # set lower + limData[1][0] = max(limData[0][0],min(xy[0],limData[0][1])) # lower + if limData[1][0] > limData[1][1]: + limData[1][0],limData[1][1] = limData[1][1],limData[1][0] G2frame.ifSetLimitsMode = 0 G2frame.CancelSetLimitsMode.Enable(False) G2frame.GPXtree.SetItemPyData(LimitId,limData) @@ -1087,6 +1138,7 @@ def DeleteHKLlabel(HKLmarkers,key): Page.pickTicknum = Page.phaseList.index(pick) resetlist = [] for pId,phase in enumerate(Page.phaseList): # set the tickmarks to a lighter color + if phase not in Page.tickDict: return col = Page.tickDict[phase].get_color() rgb = mpcls.ColorConverter().to_rgb(col) rgb_light = [(2 + i)/3. for i in rgb] @@ -1600,6 +1652,58 @@ def onPartialConfig(event): Page.plotStyle['partials'] = True Replot() configPartialDisplay(G2frame,Page.phaseColors,Replot) + def adjustDim(i,nx): + '''MPL creates a 1-D array when nx=1, 2-D otherwise. + This adjusts the array addressing. + ''' + if nx == 1: + return (0,1) + else: + return ((0,i),(1,i)) + def drawTicks(Phases,phaseList,group=False): + 'Draw the tickmarcks for phases in the current histogram' + l = GSASIIpath.GetConfigValue('Tick_length',8.0) + w = GSASIIpath.GetConfigValue('Tick_width',1.) + for pId,phase in enumerate(phaseList): + if 'list' in str(type(Phases[phase])): + continue + if phase in Page.phaseColors: + plcolor = Page.phaseColors[phase] + else: # how could this happen? + plcolor = 'k' + #continue + peaks = Phases[phase].get('RefList',[]) + if not len(peaks): + continue + if Phases[phase].get('Super',False): + peak = np.array([[peak[5],peak[6]] for peak in peaks]) + else: + peak = np.array([[peak[4],peak[5]] for peak in peaks]) + if group: + pos = (2.5-len(phaseList)*5 + pId*5)**np.ones_like(peak) # tick positions hard-coded + else: + pos = Page.plotStyle['refOffset']-pId*Page.plotStyle['refDelt']*np.ones_like(peak) + if Page.plotStyle['qPlot']: + xtick = 2*np.pi/peak.T[0] + elif Page.plotStyle['dPlot']: + xtick = peak.T[0] + else: + xtick = peak.T[1] + if Page.plotStyle.get('flTicks',0) == 0: # short tick-marks + Page.tickDict[phase],_ = Plot.plot( + xtick,pos,'|',mew=w,ms=l,picker=3., + label=phase,color=plcolor) + # N.B. above creates two Line2D objects, 2nd is ignored. + # Not sure what each does. + elif Page.plotStyle.get('flTicks',0) == 1: # full length tick-marks + if len(xtick) > 0: + # create an ~hidden tickmark to create a legend entry + Page.tickDict[phase] = Plot.plot(xtick[0],0,'|',mew=0.5,ms=l, + label=phase,color=plcolor)[0] + for xt in xtick: # a separate line for each reflection position + Plot.axvline(xt,color=plcolor, + picker=3., + label='_FLT_'+phase,lw=0.5) #### beginning PlotPatterns execution ##################################### global exclLines,Page @@ -1619,6 +1723,19 @@ def onPartialConfig(event): for i in 'Obs_color','Calc_color','Diff_color','Bkg_color': pwdrCol[i] = '#' + GSASIIpath.GetConfigValue(i,getDefault=True) + groupName = None + groupDict = {} + if plottype == 'GROUP': + groupName = G2frame.GroupInfo['groupName'] # set in GSASIIgroupGUI.UpdateGroup + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + # set data to first histogram in group + G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, groupDict[groupName][0]) + data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) + if not G2frame.PatternId: return if 'PKS' in plottype: # This is probably not used anymore; PlotPowderLines seems to be called directly @@ -1630,7 +1747,16 @@ def onPartialConfig(event): publish = PublishPlot else: publish = None - new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab('Powder Patterns','mpl',publish=publish) + if G2frame.Contour: publish = None + + new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab('Powder Patterns','mpl') + Page.toolbar.setPublish(publish) + Page.toolbar.arrows['_groupMode'] = None + # if we are changing histogram types (including group to individual, reset plot) + if not new and hasattr(Page,'prevPlotType'): + if Page.prevPlotType != plottype: new = True + Page.prevPlotType = plottype + if G2frame.ifSetLimitsMode and G2frame.GPXtree.GetItemText(G2frame.GPXtree.GetSelection()) == 'Limits': # note mode if G2frame.ifSetLimitsMode == 1: @@ -1644,7 +1770,7 @@ def onPartialConfig(event): Page.excludeMode = False # True when defining an excluded region Page.savedplot = None #patch - if 'Offset' not in Page.plotStyle and plotType in ['PWDR','SASD','REFD']: #plot offset data + if 'Offset' not in Page.plotStyle and plottype in ['PWDR','SASD','REFD']: #plot offset data Ymax = max(data[1][1]) Page.plotStyle.update({'Offset':[0.0,0.0],'delOffset':float(0.02*Ymax), 'refOffset':float(-0.1*Ymax),'refDelt':float(0.1*Ymax),}) @@ -1656,7 +1782,8 @@ def onPartialConfig(event): G2frame.lastPlotType except: G2frame.lastPlotType = None - if plotType == 'PWDR': + + if plottype == 'PWDR' or plottype == 'GROUP': try: Parms,Parms2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, G2frame.PatternId, 'Instrument Parameters')) @@ -1714,7 +1841,7 @@ def onPartialConfig(event): G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, plottingItem) data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) G2frame.GPXtree.SelectItem(G2frame.PatternId) - PlotPatterns(G2frame,True,plotType,None,extraKeys) + PlotPatterns(G2frame,True,plottype,None,extraKeys) #===================================================================================== elif 'PlotDefaults' in data[0] and fromTree: # set style from defaults saved with '!' #print('setting plot style defaults') @@ -1732,17 +1859,21 @@ def onPartialConfig(event): newPlot = True G2frame.Cmin = 0.0 G2frame.Cmax = 1.0 - Page.canvas.mpl_connect('motion_notify_event', OnMotion) - Page.canvas.mpl_connect('pick_event', OnPickPwd) - Page.canvas.mpl_connect('button_release_event', OnRelease) - Page.canvas.mpl_connect('button_press_event',OnPress) - Page.bindings = [] - # redo OnPlotKeyPress binding each time the Plot is updated - # since needs values that may have been changed after 1st call - for b in Page.bindings: + # redo plot binding each time the Plot is updated since values + # may have been changed after 1st call + try: + G2frame.PlotBindings + except: + G2frame.PlotBindings = [] + for b in G2frame.PlotBindings: Page.canvas.mpl_disconnect(b) - Page.bindings = [] - Page.bindings.append(Page.canvas.mpl_connect('key_press_event', OnPlotKeyPress)) + G2frame.PlotBindings = [] + for e,r in [('motion_notify_event', OnMotion), + ('pick_event', OnPickPwd), + ('button_release_event', OnRelease), + ('button_press_event',OnPress), + ('key_press_event', OnPlotKeyPress)]: + G2frame.PlotBindings.append(Page.canvas.mpl_connect(e,r)) if not G2frame.PickId: print('No plot, G2frame.PickId,G2frame.PatternId=',G2frame.PickId,G2frame.PatternId) return @@ -1781,7 +1912,8 @@ def onPartialConfig(event): Phases = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PatternId,'Reflection Lists')) Page.phaseList = sorted(Phases.keys()) # define an order for phases (once!) else: - Page.phaseList = Phases = [] + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + Page.phaseList = Phases # assemble a list of validated colors for tickmarks valid_colors = [] invalid_colors = [] @@ -1809,7 +1941,7 @@ def onPartialConfig(event): if G2frame.PickId: kwargs['PickName'] = G2frame.GPXtree.GetItemText(G2frame.PickId) wx.CallAfter(G2frame.G2plotNB.RegisterRedrawRoutine(G2frame.G2plotNB.lastRaisedPlotTab,ReplotPattern, - (G2frame,newPlot,plotType),kwargs)) + (G2frame,newPlot,plottype),kwargs)) except: #skip a C++ error pass # now start plotting @@ -1822,8 +1954,6 @@ def onPartialConfig(event): ifLimits = False if G2frame.GPXtree.GetItemText(G2frame.PickId) == 'Limits': ifLimits = True - Page.plotStyle['qPlot'] = False - Page.plotStyle['dPlot'] = False # keys in use for graphics control: # a,b,c,d,e,f,g,i,l,m,n,o,p,q,r,s,t,u,w,x, (unused: j, k, y, z) # also: +,/, C,D,S,U @@ -1839,6 +1969,7 @@ def onPartialConfig(event): 'C: contour plot control window', ) else: +# Page.toolbar.updateActions = (PlotPatterns,G2frame) # command used to refresh after arrow key is pressed if 'PWDR' in plottype: Page.Choice = [' key press', 'a: add magnification region','b: toggle subtract background', @@ -1857,6 +1988,7 @@ def onPartialConfig(event): Page.Choice += [f'L: {addrem} {what} in legend',] if ifLimits: Page.Choice += ['e: create excluded region', + 'q: toggle Q plot','t: toggle d-spacing plot', 's: toggle sqrt plot','w: toggle (Io-Ic)/sig plot', '+: toggle obs line plot'] else: @@ -1895,7 +2027,7 @@ def onPartialConfig(event): for KeyItem in extraKeys: Page.Choice = Page.Choice + [KeyItem[0] + ': '+KeyItem[2],] magLineList = [] # null value indicates no magnification - Page.toolbar.updateActions = None # no update actions + #Page.toolbar.updateActions = None # no update actions, used with the arrow keys G2frame.cid = None Page.keyPress = OnPlotKeyPress # assemble a list of validated colors (not currently needed) @@ -1943,7 +2075,7 @@ def onPartialConfig(event): else: #G2frame.selection Title = os.path.split(G2frame.GSASprojectfile)[1] if G2frame.selections is None: - choices = G2gd.GetGPXtreeDataNames(G2frame,plotType) + choices = G2gd.GetGPXtreeDataNames(G2frame,plottype) else: choices = G2frame.selections PlotList = [] @@ -1999,8 +2131,7 @@ def onPartialConfig(event): if Ymax is None: Ymax = max(xye[1]) Ymax = max(Ymax,max(xye[1])) if Ymax is None: return # nothing to plot - offsetX = Page.plotStyle['Offset'][1] - offsetY = Page.plotStyle['Offset'][0] + offsetY,offsetX = Page.plotStyle.get('Offset',(0,0))[:2] if Page.plotStyle['logPlot']: Title = 'log('+Title+')' elif Page.plotStyle['sqrtPlot']: @@ -2010,9 +2141,9 @@ def onPartialConfig(event): Title = 'Scaling diagnostic for '+Title if G2frame.SubBack: Title += ' - background' - if Page.plotStyle['qPlot'] or plottype in ['SASD','REFD'] and not G2frame.Contour and not ifLimits: + if Page.plotStyle['qPlot'] or plottype in ['SASD','REFD'] and not G2frame.Contour: xLabel = r'$Q, \AA^{-1}$' - elif Page.plotStyle['dPlot'] and 'PWDR' in plottype and not ifLimits: + elif Page.plotStyle['dPlot'] and 'PWDR' in plottype: xLabel = r'$d, \AA$' elif Page.plotStyle['chanPlot'] and G2frame.Contour: xLabel = 'Channel no.' @@ -2023,8 +2154,158 @@ def onPartialConfig(event): xLabel = 'E, keV' else: xLabel = r'$\mathsf{2\theta}$' + if groupName is not None: + # plot a group of histograms + Page.toolbar.arrows['_groupMode'] = Page # set up use of arrow keys +# Page.toolbar.updateActions = (PlotPatterns,G2frame,False,plotType,data +# ) # command used to refresh after arrow key is pressed + Page.toolbar.enableArrows('group',(PlotPatterns,G2frame,False,plotType,data)) - if G2frame.Weight and not G2frame.Contour: + Page.Choice = [' key press', + 'f: toggle full-length ticks', + 'g: toggle grid', + 's: toggle sqrt plot', + 'q: toggle Q plot', + 't: toggle d-spacing plot', + 'x: share x-axes (Q/d only)'] + Plot.set_visible(False) # removes "big" plot + gXmin = {} + gXmax = {} + gYmin = {} + gYmax = {} + gX = {} + gdat = {} + totalrange = 0 + DZmax = 0 + DZmin = 0 + RefTbl = {} + histlbl = {} + # find portion of hist name that is the same and different + l = max([len(i) for i in groupDict[groupName]]) + h0 = groupDict[groupName][0].ljust(l) + msk = [True] * l + for h in groupDict[groupName][1:]: + msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h.ljust(l),msk)] + if not hasattr(Page,'groupMax'): Page.groupMax = min(10,len(groupDict[groupName])) + if not hasattr(Page,'groupOff'): Page.groupOff = 0 + groupPlotList = groupDict[groupName][Page.groupOff:] + groupPlotList += groupDict[groupName] # pad with more from beginning + groupPlotList = groupPlotList[:Page.groupMax] + Page.groupN = len(groupPlotList) + # place centered-dot in loc of non-common letters + #commonltrs = ''.join([h0i if m else '\u00B7' for (h0i,m) in zip(h0,msk)]) + # place rectangular box in the loc of non-common letter(s) + commonltrs = ''.join([h0i if m else '\u25A1' for (h0i,m) in zip(h0,msk)]) + for i,h in enumerate(groupPlotList): + histlbl[i] = ''.join([hi for (hi,m) in zip(h,msk) if not m]) # unique letters + gPatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, h) + gParms,_ = G2frame.GPXtree.GetItemPyData( + G2gd.GetGPXtreeItemId(G2frame,gPatternId, + 'Instrument Parameters')) + LimitId = G2gd.GetGPXtreeItemId(G2frame,gPatternId, 'Limits') + limdat = G2frame.GPXtree.GetItemPyData(LimitId) + gd = G2frame.GPXtree.GetItemPyData(gPatternId) + RefTbl[i] = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,gPatternId,'Reflection Lists')) + # drop data outside limits + mask = (limdat[1][0] <= gd[1][0]) & (gd[1][0] <= limdat[1][1]) + gdat[i] = {} + for j in range(6): + y = gd[1][j][mask] + if Page.plotStyle['sqrtPlot']: + gdat[i][j] = np.where(y>=0.,np.sqrt(y),-np.sqrt(-y)) + else: + gdat[i][j] = y + gYmax[i] = max(max(gdat[i][1]),max(gdat[i][3])) + gYmin[i] = min(min(gdat[i][1]),min(gdat[i][3])) + if Page.plotStyle['qPlot']: + gX[i] = 2.*np.pi/G2lat.Pos2dsp(gParms,gdat[i][0]) + elif Page.plotStyle['dPlot']: + gX[i] = G2lat.Pos2dsp(gParms,gdat[i][0]) + else: + gX[i] = gdat[i][0] + gXmin[i] = min(gX[i]) + gXmax[i] = max(gX[i]) + # obs-calc/sigma + DZ = (gdat[i][1]-gdat[i][3])*np.sqrt(gdat[i][2]) + DZmin = min(DZmin,DZ.min()) + DZmax = max(DZmax,DZ.max()) + totalrange += gXmax[i]-gXmin[i] + # apportion axes lengths so that units are equal + xfrac = [(gXmax[i]-gXmin[i])/totalrange for i in range(Page.groupN)] + GS_kw = {'height_ratios':[4, 1], 'width_ratios':xfrac,} + if plotOpt['sharedX'] and ( + Page.plotStyle['qPlot'] or Page.plotStyle['dPlot']): + Page.figure.text(0.001,0.94,'X shared',fontsize=11, + color='g') + Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True, + gridspec_kw=GS_kw) + else: + Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex='col', + gridspec_kw=GS_kw) + Page.figure.subplots_adjust(left=5/100.,bottom=16/150., + right=.99,top=1.-3/200.,hspace=0,wspace=0) + for i in range(Page.groupN): + up,down = adjustDim(i,Page.groupN) + Plots[up].set_xlim(gXmin[i],gXmax[i]) + Plots[down].set_xlim(gXmin[i],gXmax[i]) + Plots[down].set_ylim(DZmin,DZmax) + if not Page.plotStyle.get('flTicks',False): + Plots[up].set_ylim(-len(RefTbl[i])*5,102) + else: + Plots[up].set_ylim(-1,102) + + # pretty up the tick labels + up,down = adjustDim(0,Page.groupN) + Plots[up].tick_params(axis='y', direction='inout', left=True, right=True) + Plots[down].tick_params(axis='y', direction='inout', left=True, right=True) + if Page.groupN > 1: + for ax in Plots[:,1:].ravel(): + ax.tick_params(axis='y', direction='in', left=True, right=True) + # remove 1st upper y-label so that it does not overlap with lower box + Plots[up].get_yticklabels()[0].set_visible(False) + Plots[down].set_ylabel(r'$\mathsf{\Delta I/\sigma_I}$',fontsize=12) + if Page.plotStyle['sqrtPlot']: + Plots[up].set_ylabel(r'$\rm\sqrt{Normalized\ intensity}$',fontsize=12) + else: + Plots[up].set_ylabel('Normalized Intensity',fontsize=12) + Page.figure.text(0.001,0.03,commonltrs,fontsize=13) + Page.figure.supxlabel(xLabel) + for i,h in enumerate(groupPlotList): + up,down = adjustDim(i,Page.groupN) + Plot = Plots[up] + Plot1 = Plots[down] + if Page.plotStyle['qPlot']: + pos = 0.98 + ha = 'right' + else: + pos = 0.02 + ha = 'left' + Plot.text(pos,0.98,histlbl[i], + transform=Plot.transAxes, + verticalalignment='top', + horizontalalignment=ha, + fontsize=14) + xye = gdat[i] + DZ = (xye[1]-xye[3])*np.sqrt(xye[2]) + DifLine = Plot1.plot(gX[i],DZ,pwdrCol['Diff_color']) #,picker=1.,label=incCptn('diff')) #(Io-Ic)/sig(Io) + pP = '+' + lW = 1.5 + scaleY = lambda Y: (Y-gYmin[i])/(gYmax[i]-gYmin[i])*100 + Plot.plot(gX[i],scaleY(xye[1]),marker=pP,color=pwdrCol['Obs_color'],linewidth=lW,# picker=3., + clip_on=Clip_on,label=incCptn('obs')) + Plot.plot(gX[i],scaleY(xye[3]),pwdrCol['Calc_color'],picker=0.,label=incCptn('calc'),linewidth=1.5) + Plot.plot(gX[i],scaleY(xye[4]),pwdrCol['Bkg_color'],picker=0.,label=incCptn('bkg'),linewidth=1.5) #background + drawTicks(RefTbl[i],list(RefTbl[i].keys()),True) + try: # try used as in PWDR menu not Groups + # Not sure if this does anything + G2frame.dataWindow.moveTickLoc.Enable(False) + G2frame.dataWindow.moveTickSpc.Enable(False) + # G2frame.dataWindow.moveDiffCurve.Enable(True) + except: + pass + Page.canvas.draw() + return + elif G2frame.Weight and not G2frame.Contour: Plot.set_visible(False) #hide old plot frame, will get replaced below GS_kw = {'height_ratios':[4, 1],} # try: @@ -2037,6 +2318,8 @@ def onPartialConfig(event): right=.98,top=1.-16/200.,hspace=0) else: Plot.set_xlabel(xLabel,fontsize=16) + if not G2frame.Contour: + Page.toolbar.enableArrows('',(PlotPatterns,G2frame)) if G2frame.Weight and G2frame.Contour: Title = r'$\mathsf{\Delta(I)/\sigma(I)}$ for '+Title if 'T' in ParmList[0]['Type'][0] or (Page.plotStyle['Normalize'] and not G2frame.SinglePlot): @@ -2086,9 +2369,9 @@ def onPartialConfig(event): if G2frame.Contour: # detect unequally spaced points in a contour plot for N,Pattern in enumerate(PlotList): xye = np.array(ma.getdata(Pattern[1])) # strips mask = X,Yo,W,Yc,Yb,Yd - if Page.plotStyle['qPlot'] and 'PWDR' in plottype and not ifLimits: + if Page.plotStyle['qPlot'] and 'PWDR' in plottype: X = 2.*np.pi/G2lat.Pos2dsp(Parms,xye[0]) - elif Page.plotStyle['dPlot'] and 'PWDR' in plottype and not ifLimits: + elif Page.plotStyle['dPlot'] and 'PWDR' in plottype: X = G2lat.Pos2dsp(Parms,xye[0]) else: X = copy.deepcopy(xye[0]) @@ -2138,9 +2421,9 @@ def onPartialConfig(event): # convert all X values and then reapply mask if xye0 is a masked array mask = None if hasattr(xye0,'mask'): mask = xye0.mask - if Page.plotStyle['qPlot'] and 'PWDR' in plottype and not ifLimits: + if Page.plotStyle['qPlot'] and 'PWDR' in plottype: X = ma.array(2.*np.pi/G2lat.Pos2dsp(Parms,xye0.data),mask=mask) - elif Page.plotStyle['dPlot'] and 'PWDR' in plottype and not ifLimits: + elif Page.plotStyle['dPlot'] and 'PWDR' in plottype: X = ma.array(G2lat.Pos2dsp(Parms,xye0.data),mask=mask) else: X = copy.deepcopy(xye0) @@ -2200,7 +2483,6 @@ def onPartialConfig(event): lbl = Plot.annotate("x{}".format(ml0), xy=(tcorner, tpos), xycoords="axes fraction", verticalalignment='bottom',horizontalalignment=halign,label='_maglbl') Plot.magLbls.append(lbl) - Page.toolbar.updateActions = (PlotPatterns,G2frame) multArray = ma.getdata(multArray) if 'PWDR' in plottype: YI = copy.copy(xye[1]) #yo @@ -2231,9 +2513,9 @@ def onPartialConfig(event): if ifpicked and not G2frame.Contour: # draw limit & excluded region lines lims = limits[1:] - if Page.plotStyle['qPlot'] and 'PWDR' in plottype and not ifLimits: + if Page.plotStyle['qPlot'] and 'PWDR' in plottype: lims = 2.*np.pi/G2lat.Pos2dsp(Parms,lims) - elif Page.plotStyle['dPlot'] and 'PWDR' in plottype and not ifLimits: + elif Page.plotStyle['dPlot'] and 'PWDR' in plottype: lims = G2lat.Pos2dsp(Parms,lims) # limit lines Lines.append(Plot.axvline(lims[0][0],color='g',dashes=(5,5),picker=3.)) @@ -2438,12 +2720,12 @@ def onPartialConfig(event): else: Plot.plot(X,YB,color=pwdrCol['Obs_color'],marker=pP,linewidth=lW, picker=3.,clip_on=Clip_on,label=incCptn('obs')) - Plot.plot(X,ZB,pwdrCol['Bkg_color'],picker=False,label=incCptn('calc'),linewidth=1.5) + Plot.plot(X,ZB,pwdrCol['Bkg_color'],picker=0.,label=incCptn('calc'),linewidth=1.5) if 'PWDR' in plottype and (G2frame.SinglePlot and G2frame.plusPlot): - BackLine = Plot.plot(X,W/ymax,pwdrCol['Bkg_color'],picker=False,label=incCptn('bkg'),linewidth=1.5) #Ib + BackLine = Plot.plot(X,W/ymax,pwdrCol['Bkg_color'],picker=0.,label=incCptn('bkg'),linewidth=1.5) #Ib if not G2frame.Weight and np.any(Z): DifLine = Plot.plot(X,D/ymax,pwdrCol['Diff_color'],linewidth=1.5, - picker=True,pickradius=1.,label=incCptn('diff')) #Io-Ic + picker=1.,label=incCptn('diff')) #Io-Ic Plot.axhline(0.,color='k',label='_zero') Plot.tick_params(labelsize=14) @@ -2550,7 +2832,7 @@ def onPartialConfig(event): # waterfall mode=3: name in legend? name = Pattern[2] if Pattern[0].get('histTitle'): name = Pattern[0]['histTitle'] - Plot.plot(X,Y/ymax,color=mcolors.cmap(icolor),picker=False,label=incCptn(name)) + Plot.plot(X,Y/ymax,color=mcolors.cmap(icolor),picker=0.,label=incCptn(name)) elif plottype in ['SASD','REFD']: try: Plot.loglog(X,Y,mcolors.cmap(icolor),nonpositive='mask',linewidth=1.5) @@ -2600,7 +2882,7 @@ def onPartialConfig(event): if Page.plotStyle['sqrtPlot']: ypos = np.sqrt(abs(ypos))*np.sign(ypos) artist = Plot.text(xpos,ypos,lbl,fontsize=font,c=color,ha='center', - va='top',bbox=props,picker=True,rotation=angle,label='_'+ph) + va='top',bbox=props,picker=1.,rotation=angle,label='_'+ph) artist.key = (ph,key) #============================================================ if timeDebug: @@ -2672,42 +2954,7 @@ def onPartialConfig(event): or (inXtraPeakMode and G2frame.GPXtree.GetItemText(G2frame.PickId) == 'Peak List') ): - l = GSASIIpath.GetConfigValue('Tick_length',8.0) - w = GSASIIpath.GetConfigValue('Tick_width',1.) - for pId,phase in enumerate(Page.phaseList): - if 'list' in str(type(Phases[phase])): - continue - if phase in Page.phaseColors: - plcolor = Page.phaseColors[phase] - else: # how could this happen? - plcolor = 'k' - #continue - peaks = Phases[phase].get('RefList',[]) - if not len(peaks): - continue - if Phases[phase].get('Super',False): - peak = np.array([[peak[5],peak[6]] for peak in peaks]) - else: - peak = np.array([[peak[4],peak[5]] for peak in peaks]) - pos = Page.plotStyle['refOffset']-pId*Page.plotStyle['refDelt']*np.ones_like(peak) - if Page.plotStyle['qPlot']: - xtick = 2*np.pi/peak.T[0] - elif Page.plotStyle['dPlot']: - xtick = peak.T[0] - else: - xtick = peak.T[1] - if Page.plotStyle.get('flTicks',0) == 0: # short tick-marks - Page.tickDict[phase],_ = Plot.plot( - xtick,pos,'|',mew=w,ms=l,picker=3.,label=phase,color=plcolor) - # N.B. above creates two Line2D objects, 2nd is ignored. - # Not sure what each does. - elif Page.plotStyle.get('flTicks',0) == 1: # full length tick-marks - if len(xtick) > 0: - # create an ~hidden tickmark to create a legend entry - Page.tickDict[phase] = Plot.plot(xtick[0],0,'|',mew=0.5,ms=l, - label=phase,color=plcolor)[0] - for xt in xtick: # a separate line for each reflection position - Plot.axvline(xt,color=plcolor,picker=3.,label='_FLT_'+phase,lw=0.5) + drawTicks(Phases,Page.phaseList) handles,legends = Plot.get_legend_handles_labels() if handles: labels = dict(zip(legends,handles)) # remove duplicate phase entries diff --git a/GSASII/meson.build b/GSASII/meson.build index b9514904..e6c5cb7f 100644 --- a/GSASII/meson.build +++ b/GSASII/meson.build @@ -9,7 +9,6 @@ py.install_sources([ 'GSASIIGUI.py', 'GSASIIElem.py', 'GSASIIElemGUI.py', - # 'GSASIIIO.py', 'GSASIImiscGUI.py', 'GSASIIIntPDFtool.py', 'GSASIIconstrGUI.py', @@ -20,11 +19,11 @@ py.install_sources([ 'GSASIIexprGUI.py', 'GSASIIfiles.py', 'GSASIIfpaGUI.py', + 'GSASIIgroupGUI.py', 'GSASIIimage.py', 'GSASIIimgGUI.py', 'GSASIIindex.py', 'GSASIIlattice.py', - # 'GSASIIlog.py', 'GSASIImapvars.py', 'GSASIImath.py', 'GSASIImpsubs.py',