From bb5039364ded9c8e7ab7d5834a093c859c7536ec Mon Sep 17 00:00:00 2001 From: BHT Date: Sat, 6 Sep 2025 12:04:07 -0500 Subject: [PATCH 01/30] code to create groups; remove CIF Author --- GSASII/GSASIIctrlGUI.py | 76 ++++++++++++++++++++++++++- GSASII/GSASIIdataGUI.py | 114 ++++++++++++++++++++++++++++++++++------ 2 files changed, 173 insertions(+), 17 deletions(-) diff --git a/GSASII/GSASIIctrlGUI.py b/GSASII/GSASIIctrlGUI.py index 5172f03c..e7bfd6f1 100644 --- a/GSASII/GSASIIctrlGUI.py +++ b/GSASII/GSASIIctrlGUI.py @@ -1687,7 +1687,7 @@ def _OnCopyButton(self,event): val = d[k] continue d[k] = val - ctrl.SetValue(val) + ctrl.ChangeValue(val) for i in range(len(self.checkdictlst)): if i < n: continue self.checkdictlst[i][self.checkelemlst[i]] = self.checkdictlst[n][self.checkelemlst[n]] @@ -10111,6 +10111,80 @@ def SelectPkgInstall(event): print ('exiting GSAS-II') sys.exit() +def StringSearchTemplate(parent,title,prompt,start,help=None): + '''Dialog to obtain a single string value from user + + :param wx.Frame parent: name of parent frame + :param str title: title string for dialog + :param str prompt: string to tell use what they are inputting + :param str start: default input value, if any + :param str help: if supplied, a help button is added to the dialog that + can be used to display the supplied help text/URL for setting this + variable. (Default is '', which is ignored.) + ''' + def on_invalid(): + G2MessageBox(parent, + 'The pattern must retain some non-blank characters from the original string. Resetting so you can start again.','Try again') + valItem.SetValue(start) + return + def on_char_typed(event): + keycode = event.GetKeyCode() + if keycode == 32 or keycode == 63: # ' ' or '?' - replace with '?' + #has a range been selected? + sel = valItem.GetSelection() + if sel[0] == sel[1]: + insertion_point = valItem.GetInsertionPoint() + sel = (insertion_point,insertion_point+1) + for i in range(*sel): + valItem.Replace(i, i + 1, '?') + # Move the insertion point forward one character + valItem.SetInsertionPoint(i + 1) + # make sure some characters remain + if len(valItem.GetValue().replace('?','').strip()) == 0: + wx.CallAfter(on_invalid) + event.Skip(False) + elif keycode >= wx.WXK_SPACE: # anything else printable, ignore + event.Skip(False) + else: # arrows etc are processed naturally + event.Skip(True) + dlg = wx.Dialog(parent,wx.ID_ANY,title,pos=wx.DefaultPosition, + style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) + dlg.CenterOnParent() + mainSizer = wx.BoxSizer(wx.VERTICAL) + sizer1 = wx.BoxSizer(wx.HORIZONTAL) + txt = wx.StaticText(dlg,wx.ID_ANY,prompt) + sizer1.Add((10,-1),1,wx.EXPAND) + txt.Wrap(500) + sizer1.Add(txt,0,wx.ALIGN_CENTER) + sizer1.Add((10,-1),1,wx.EXPAND) + if help: + sizer1.Add(HelpButton(dlg,help),0,wx.ALL) + mainSizer.Add(sizer1,0,wx.EXPAND) + sizer1 = wx.BoxSizer(wx.HORIZONTAL) + valItem = wx.TextCtrl(dlg,wx.ID_ANY,value=start,style=wx.TE_PROCESS_ENTER) + valItem.Bind(wx.EVT_CHAR, on_char_typed) + valItem.Bind(wx.EVT_TEXT_ENTER, lambda event: event.Skip(False)) + wx.CallAfter(valItem.SetSelection,0,0) # clear the initial selection + mainSizer.Add(valItem,1,wx.EXPAND) + btnsizer = wx.StdDialogButtonSizer() + OKbtn = wx.Button(dlg, wx.ID_OK) + OKbtn.SetDefault() + btnsizer.AddButton(OKbtn) + btn = wx.Button(dlg, wx.ID_CANCEL) + btnsizer.AddButton(btn) + btnsizer.Realize() + mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER) + dlg.SetSizer(mainSizer) + mainSizer.Fit(dlg) + ans = dlg.ShowModal() + if ans != wx.ID_OK: + dlg.Destroy() + return + else: + val = valItem.GetValue() + dlg.Destroy() + return val + if __name__ == '__main__': app = wx.App() GSASIIpath.InvokeDebugOpts() diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index e8958fc3..95f69d42 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -7899,25 +7899,79 @@ 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) + 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 PWDR histograms found to group', + 'Cannot group') + return + 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.') + res = G2G.StringSearchTemplate(G2frame,'Set match template',msg,hist[5:]) + re_res = re.compile(res.replace('.',r'\.').replace('?','.')) + setDict = {} + keyList = [] + noMatchCount = 0 + for hist in Histograms: + if hist.startswith('PWDR '): + m = re_res.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]) + + msg = f'With template {res!r} found ' + if min(groupCount.values()) == max(groupCount.values()): + msg += f'{len(groupDict)} groups with {min(groupCount.values())} histograms each' + else: + msg += (f'{len(groupDict)} groups with between {min(groupCount.values())}' + f' and {min(groupCount.values())} histograms in each') + if noMatchCount: + msg += f"\n\nNote that {noMatchCount} PWDR histograms were not included in any groups" + G2G.G2MessageBox(G2frame,msg,'Grouping result') + data['Groups'] = {'groupDict':groupDict,'notGrouped':noMatchCount, + 'template':res} + wx.CallAfter(UpdateControls,G2frame,data) + # 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) @@ -7959,11 +8013,39 @@ 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) 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'): From 6d5470b395f4cca80a6707aafd4d1fa9984dadec Mon Sep 17 00:00:00 2001 From: BHT Date: Tue, 9 Sep 2025 20:48:03 -0500 Subject: [PATCH 02/30] Add section for plotting grouped histograms. Still need to figure out how this will get accessed --- GSASII/GSASIIpwdplot.py | 152 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 36deb388..239a78ae 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -1068,6 +1068,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] @@ -1635,6 +1636,7 @@ def onPartialConfig(event): G2frame.lastPlotType except: G2frame.lastPlotType = None + groupDict = {} if plotType == 'PWDR': try: Parms,Parms2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, @@ -1655,6 +1657,8 @@ def onPartialConfig(event): G2frame.lastPlotType = Parms['Type'][1] except TypeError: #bad id from GetGPXtreeItemId - skip pass + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupDict = Controls.get('Groups',{}).get('groupDict',{}) try: G2frame.FixedLimits except: @@ -1994,8 +1998,154 @@ def onPartialConfig(event): xLabel = 'E, keV' else: xLabel = r'$\mathsf{2\theta}$' + # working here + groupName = None + if groupDict: + histname = G2frame.GPXtree.GetItemText(G2frame.PatternId) + for key in groupDict: + if histname in groupDict[key]: + groupName = key + break + else: + print('No group for histogram',histname) + + if groupName is not None: + # plot a group of histograms + 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'] + Plot.set_visible(False) # removes "big" plot + gXmin = {} + gXmax = {} + gYmin = {} + gYmax = {} + gX = {} + gdat = {} + totalrange = 0 + DZmax = 0 + DZmin = 0 + nx = len(groupDict[groupName]) + for i,h in enumerate(groupDict[groupName]): + 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) + # 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(nx)] + GS_kw = {'height_ratios':[4, 1], 'width_ratios':xfrac,} + + Plots = Page.figure.subplots(2,nx,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(nx): + Plots[0,i].set_xlim(gXmin[i],gXmax[i]) + Plots[1,i].set_xlim(gXmin[i],gXmax[i]) + Plots[1,i].set_ylim(DZmin,DZmax) + Plots[0,i].set_ylim(-len(Page.phaseList)*5,102) + + # pretty up the tick labels + Plots[0,0].tick_params(axis='y', direction='inout', left=True, right=True) + Plots[1,0].tick_params(axis='y', direction='inout', left=True, right=True) + 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[0,0].get_yticklabels()[0].set_visible(False) + Plots[1,0].set_ylabel(r'$\mathsf{\Delta I/\sigma_I}$',fontsize=12) + if Page.plotStyle['sqrtPlot']: + Plots[0,0].set_ylabel(r'$\rm\sqrt{Normalized\ intensity}$',fontsize=12) + else: + Plots[0,0].set_ylabel('Normalized Intensity',fontsize=12) + Page.figure.supxlabel(xLabel) + + for i,h in enumerate(groupDict[groupName]): + Plot = Plots[0,i] + Plot1 = Plots[1,i] + xye = gdat[i] + DZ = (xye[1]-xye[3])*np.sqrt(xye[2]) + DifLine = Plot1.plot(gX[i],DZ,pwdrCol['Diff_color']) #,picker=True,pickradius=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=True,pickradius=3., + clip_on=Clip_on,label=incCptn('obs')) + Plot.plot(gX[i],scaleY(xye[3]),pwdrCol['Calc_color'],picker=False,label=incCptn('calc'),linewidth=1.5) + Plot.plot(gX[i],scaleY(xye[4]),pwdrCol['Bkg_color'],picker=False,label=incCptn('bkg'),linewidth=1.5) #background - if G2frame.Weight and not G2frame.Contour: + 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) + pos = 2.5-len(Page.phaseList)*5 + pId*5 + pos = pos * 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 not Page.plotStyle.get('flTicks',False): # short tick-marks + Plot.plot( + xtick,pos,'|',mew=w,ms=l,picker=True,pickradius=3., + label=phase,color=plcolor) + else: # 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=True,pickradius=3., + label='_FLT_'+phase,lw=0.5) + # Not sure if this does anything + G2frame.dataWindow.moveTickLoc.Enable(False) + G2frame.dataWindow.moveTickSpc.Enable(False) + # G2frame.dataWindow.moveDiffCurve.Enable(True) + 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: From 0c00d5116e6b38a95ac8bbdbbc6f845397205a30 Mon Sep 17 00:00:00 2001 From: BHT Date: Wed, 10 Sep 2025 15:07:04 -0500 Subject: [PATCH 03/30] Label plots using common & unique part of hist name; use reflection table for tick marks; (see issue #6) --- GSASII/GSASIIpwdplot.py | 50 ++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 239a78ae..f360e0c5 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -2025,8 +2025,18 @@ def onPartialConfig(event): totalrange = 0 DZmax = 0 DZmin = 0 + RefTbl = {} + histlbl = {} nx = len(groupDict[groupName]) + # find portion of hist name that is the same and different + h0 = groupDict[groupName][0] + msk = [True] * len(h0) + for h in groupDict[groupName][1:]: + msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h,msk)] + # place centered-dot in loc of non-common letters + commonltrs = ''.join([h0i if m else '\u00B7' for (h0i,m) in zip(h0,msk)]) for i,h in enumerate(groupDict[groupName]): + 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, @@ -2034,6 +2044,7 @@ def onPartialConfig(event): 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] = {} @@ -2070,8 +2081,11 @@ def onPartialConfig(event): Plots[0,i].set_xlim(gXmin[i],gXmax[i]) Plots[1,i].set_xlim(gXmin[i],gXmax[i]) Plots[1,i].set_ylim(DZmin,DZmax) - Plots[0,i].set_ylim(-len(Page.phaseList)*5,102) - + if not Page.plotStyle.get('flTicks',False): + Plots[0,i].set_ylim(-len(Page.phaseList)*5,102) + else: + Plots[0,i].set_ylim(-1,102) + # pretty up the tick labels Plots[0,0].tick_params(axis='y', direction='inout', left=True, right=True) Plots[1,0].tick_params(axis='y', direction='inout', left=True, right=True) @@ -2084,18 +2098,29 @@ def onPartialConfig(event): Plots[0,0].set_ylabel(r'$\rm\sqrt{Normalized\ intensity}$',fontsize=12) else: Plots[0,0].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(groupDict[groupName]): Plot = Plots[0,i] Plot1 = Plots[1,i] + 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=True,pickradius=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=True,pickradius=3., + Plot.plot(gX[i],scaleY(xye[1]),marker=pP,color=pwdrCol['Obs_color'],linewidth=lW,# picker=True,pickradius=3., clip_on=Clip_on,label=incCptn('obs')) Plot.plot(gX[i],scaleY(xye[3]),pwdrCol['Calc_color'],picker=False,label=incCptn('calc'),linewidth=1.5) Plot.plot(gX[i],scaleY(xye[4]),pwdrCol['Bkg_color'],picker=False,label=incCptn('bkg'),linewidth=1.5) #background @@ -2110,16 +2135,20 @@ def onPartialConfig(event): else: # how could this happen? plcolor = 'k' #continue - peaks = Phases[phase].get('RefList',[]) + peaks = [] + if phase in RefTbl[i]: + peaks = RefTbl[i][phase].get('RefList',[]) + super = RefTbl[i][phase].get('Super',False) + # else: + # peaks = Phases[phase].get('RefList',[]) + # super = Phases[phase].get('Super',False) if not len(peaks): continue - if Phases[phase].get('Super',False): + if super: 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) - pos = 2.5-len(Page.phaseList)*5 + pId*5 - pos = pos * np.ones_like(peak) + pos = 2.5-len(Page.phaseList)*5 + pId*5 # tick positions hard-coded if Page.plotStyle['qPlot']: xtick = 2*np.pi/peak.T[0] elif Page.plotStyle['dPlot']: @@ -2128,7 +2157,8 @@ def onPartialConfig(event): xtick = peak.T[1] if not Page.plotStyle.get('flTicks',False): # short tick-marks Plot.plot( - xtick,pos,'|',mew=w,ms=l,picker=True,pickradius=3., + xtick,pos * np.ones_like(peak), + '|',mew=w,ms=l, # picker=True,pickradius=3., label=phase,color=plcolor) else: # full length tick-marks if len(xtick) > 0: From 3985cbec4d9f49f801c6f030fd7e0a85d5541b27 Mon Sep 17 00:00:00 2001 From: BHT Date: Thu, 18 Sep 2025 15:03:20 -0500 Subject: [PATCH 04/30] make sharing X-axes an option for grouped plots --- GSASII/GSASIIpwdplot.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index f360e0c5..a007ceb1 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -83,6 +83,7 @@ plotOpt['lineWid'] = '1' plotOpt['saveCSV'] = False plotOpt['CSVfile'] = None +plotOpt['GroupedX'] = False for xy in 'x','y': for minmax in 'min','max': key = f'{xy}{minmax}' @@ -224,6 +225,10 @@ def OnPlotKeyPress(event): Page.plotStyle['title'] = not Page.plotStyle.get('title',True) elif event.key == 'f' and 'PWDR' in plottype: # short or full length tick-marks Page.plotStyle['flTicks'] = not Page.plotStyle.get('flTicks',False) + elif event.key == 'x'and groupName is not None: + plotOpt['GroupedX'] = not plotOpt['GroupedX'] + if not plotOpt['GroupedX']: # reset scale + newPlot = True elif event.key == 'x'and 'PWDR' in plottype: Page.plotStyle['exclude'] = not Page.plotStyle['exclude'] elif event.key == '.': @@ -2014,7 +2019,8 @@ def onPartialConfig(event): 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'] + 'q: toggle Q plot','t: toggle d-spacing plot', + 'x: share x-axes'] Plot.set_visible(False) # removes "big" plot gXmin = {} gXmax = {} @@ -2072,8 +2078,11 @@ def onPartialConfig(event): # apportion axes lengths so that units are equal xfrac = [(gXmax[i]-gXmin[i])/totalrange for i in range(nx)] GS_kw = {'height_ratios':[4, 1], 'width_ratios':xfrac,} - - Plots = Page.figure.subplots(2,nx,sharey='row',sharex='col', + if plotOpt['GroupedX']: + Plots = Page.figure.subplots(2,nx,sharey='row',sharex=True, + gridspec_kw=GS_kw) + else: + Plots = Page.figure.subplots(2,nx,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) From e99db49bb78f98ed4f192328a8ae1a6cf44b38d7 Mon Sep 17 00:00:00 2001 From: BHT Date: Tue, 21 Oct 2025 21:54:46 -0500 Subject: [PATCH 05/30] WIP: adding group tree items & plot groups --- GSASII/GSASIIdataGUI.py | 12 ++++++++ GSASII/GSASIIgroupGUI.py | 59 ++++++++++++++++++++++++++++++++++++++++ GSASII/GSASIImiscGUI.py | 14 ++++++++-- GSASII/GSASIIpwdplot.py | 37 ++++++++++++++----------- GSASII/meson.build | 3 +- 5 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 GSASII/GSASIIgroupGUI.py diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index 395321ec..f6aa6210 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 @@ -8956,6 +8957,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)) ############################################################################ @@ -9149,6 +9155,12 @@ 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/'): + # groupDict is defined. + 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/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py new file mode 100644 index 00000000..bbb664a8 --- /dev/null +++ b/GSASII/GSASIIgroupGUI.py @@ -0,0 +1,59 @@ +# -*- 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 + +See SearchGroups in :func:`GSASIIdataGUI.UpdateControls`. +''' + +# 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 +# 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 UpdateGroup(G2frame,item): + G2gd.SetDataMenuBar(G2frame) + G2frame.dataWindow.helpKey = "Groups/PWDR" + topSizer = G2frame.dataWindow.topBox + parent = G2frame.dataWindow.topPanel + topSizer.Add(wx.StaticText(parent,label=' Group edit goes here someday'),0,WACV) + topSizer.Add((-1,-1),1,wx.EXPAND) + topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) + G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) + #G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) + G2frame.groupName = G2frame.GPXtree.GetItemText(item) + G2pwpl.PlotPatterns(G2frame,plotType='GROUP') diff --git a/GSASII/GSASIImiscGUI.py b/GSASII/GSASIImiscGUI.py index 25ec5c95..76321733 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/PWDR') + 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': diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index a007ceb1..d2d41ee7 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -199,7 +199,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': @@ -1602,6 +1602,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.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 @@ -1614,6 +1627,11 @@ def onPartialConfig(event): else: publish = None new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab('Powder Patterns','mpl',publish=publish) + # 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: @@ -1641,8 +1659,8 @@ def onPartialConfig(event): G2frame.lastPlotType except: G2frame.lastPlotType = None - groupDict = {} - if plotType == 'PWDR': + + if plotType == 'PWDR' or plotType == 'GROUP': try: Parms,Parms2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, G2frame.PatternId, 'Instrument Parameters')) @@ -1662,8 +1680,6 @@ def onPartialConfig(event): G2frame.lastPlotType = Parms['Type'][1] except TypeError: #bad id from GetGPXtreeItemId - skip pass - Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) - groupDict = Controls.get('Groups',{}).get('groupDict',{}) try: G2frame.FixedLimits except: @@ -2003,17 +2019,6 @@ def onPartialConfig(event): xLabel = 'E, keV' else: xLabel = r'$\mathsf{2\theta}$' - # working here - groupName = None - if groupDict: - histname = G2frame.GPXtree.GetItemText(G2frame.PatternId) - for key in groupDict: - if histname in groupDict[key]: - groupName = key - break - else: - print('No group for histogram',histname) - if groupName is not None: # plot a group of histograms Page.Choice = [' key press', 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', From 751d211140e76fd7792188e9495b05b5dba87bd3 Mon Sep 17 00:00:00 2001 From: BHT Date: Wed, 22 Oct 2025 19:04:27 -0500 Subject: [PATCH 06/30] WIP: get plotting working, fix display of groups. Next: table for edit of params --- GSASII/GSASIIdataGUI.py | 125 ++++++++++++++++++++++++++++------------ GSASII/GSASIImiscGUI.py | 6 +- GSASII/GSASIIpwdplot.py | 54 ++++++++++------- 3 files changed, 125 insertions(+), 60 deletions(-) diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index 11239bd5..986f93ab 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -4300,13 +4300,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 +4327,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 +4336,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() @@ -7924,6 +7935,8 @@ def SearchGroups(event): is judged by a common string that matches a template supplied by the user ''' + ans = G2frame.OnFileSave(None) + if not ans: return Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() for hist in Histograms: if hist.startswith('PWDR '): @@ -7932,6 +7945,8 @@ def SearchGroups(event): G2G.G2MessageBox(G2frame,'No PWDR histograms found to group', 'Cannot group') return + 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 ' @@ -7940,40 +7955,71 @@ def SearchGroups(event): 'vary within a histogram group (e.g. Bank 1). ' 'Be sure to leave enough characters so the string ' 'can be uniquely matched.') - res = G2G.StringSearchTemplate(G2frame,'Set match template',msg,hist[5:]) - re_res = re.compile(res.replace('.',r'\.').replace('?','.')) - setDict = {} - keyList = [] - noMatchCount = 0 - for hist in Histograms: - if hist.startswith('PWDR '): - m = re_res.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]) + 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]) - msg = f'With template {res!r} found ' - if min(groupCount.values()) == max(groupCount.values()): - msg += f'{len(groupDict)} groups with {min(groupCount.values())} histograms each' - else: - msg += (f'{len(groupDict)} groups with between {min(groupCount.values())}' - f' and {min(groupCount.values())} histograms in each') - if noMatchCount: - msg += f"\n\nNote that {noMatchCount} PWDR histograms were not included in any groups" - G2G.G2MessageBox(G2frame,msg,'Grouping result') - data['Groups'] = {'groupDict':groupDict,'notGrouped':noMatchCount, - 'template':res} - wx.CallAfter(UpdateControls,G2frame,data) + msg1 = f'With template {srchStr!r} found ' + if min(groupCount.values()) == max(groupCount.values()): + msg1 += f'{len(groupDict)} groups with {min(groupCount.values())} histograms each' + else: + msg1 += (f'{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" + #G2G.G2MessageBox(G2frame,msg1,'Grouping result') + res = G2G.ShowScrolledInfo(G2frame,msg1,header='Grouping result', + buttonlist=[ + ('OK', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK)), + ('try again', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_CANCEL)) + ], + height=150) + if res == wx.ID_OK: + repeat = False - # start of UpdateControls + data['Groups'] = {'groupDict':groupDict,'notGrouped':noMatchCount, + 'template':srchStr} +# 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: @@ -8037,6 +8083,11 @@ def SearchGroups(event): 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((-1,8)) diff --git a/GSASII/GSASIImiscGUI.py b/GSASII/GSASIImiscGUI.py index b7574dbd..fc1bfde6 100644 --- a/GSASII/GSASIImiscGUI.py +++ b/GSASII/GSASIImiscGUI.py @@ -22,7 +22,7 @@ import os import re import copy -import platform +#import platform import pickle import sys import random as ran @@ -719,7 +719,7 @@ def ProjFileSave(G2frame): commit = g2repo.head.commit Controls['LastSavedUsing'] += f" git {commit.hexsha[:8]}" else: - gv = getSavedVersionInfo() + gv = GSASIIpath.getSavedVersionInfo() if gv is not None: Controls['LastSavedUsing'] += f" static {gv.git_version[:8]}" except: @@ -730,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/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 27caea77..4a84bb87 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -2020,8 +2020,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']: @@ -2116,32 +2115,44 @@ def onPartialConfig(event): gridspec_kw=GS_kw) Page.figure.subplots_adjust(left=5/100.,bottom=16/150., right=.99,top=1.-3/200.,hspace=0,wspace=0) + 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)) for i in range(nx): - Plots[0,i].set_xlim(gXmin[i],gXmax[i]) - Plots[1,i].set_xlim(gXmin[i],gXmax[i]) - Plots[1,i].set_ylim(DZmin,DZmax) + up,down = adjustDim(i,nx) + 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[0,i].set_ylim(-len(Page.phaseList)*5,102) + Plots[up].set_ylim(-len(Page.phaseList)*5,102) else: - Plots[0,i].set_ylim(-1,102) + Plots[up].set_ylim(-1,102) # pretty up the tick labels - Plots[0,0].tick_params(axis='y', direction='inout', left=True, right=True) - Plots[1,0].tick_params(axis='y', direction='inout', left=True, right=True) - for ax in Plots[:,1:].ravel(): - ax.tick_params(axis='y', direction='in', left=True, right=True) + up,down = adjustDim(0,nx) + 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 nx > 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[0,0].get_yticklabels()[0].set_visible(False) - Plots[1,0].set_ylabel(r'$\mathsf{\Delta I/\sigma_I}$',fontsize=12) + 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[0,0].set_ylabel(r'$\rm\sqrt{Normalized\ intensity}$',fontsize=12) + Plots[up].set_ylabel(r'$\rm\sqrt{Normalized\ intensity}$',fontsize=12) else: - Plots[0,0].set_ylabel('Normalized Intensity',fontsize=12) + 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(groupDict[groupName]): - Plot = Plots[0,i] - Plot1 = Plots[1,i] + up,down = adjustDim(i,nx) + Plot = Plots[up] + Plot1 = Plots[down] if Page.plotStyle['qPlot']: pos = 0.98 ha = 'right' @@ -2208,10 +2219,13 @@ def onPartialConfig(event): Plot.axvline(xt,color=plcolor, picker=True,pickradius=3., label='_FLT_'+phase,lw=0.5) - # Not sure if this does anything - G2frame.dataWindow.moveTickLoc.Enable(False) - G2frame.dataWindow.moveTickSpc.Enable(False) + 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: From 68b0c0806db960911a2f368de743602d299dcdf6 Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 26 Oct 2025 20:59:49 -0500 Subject: [PATCH 07/30] WIP: shows table of HAP values, but can we make row labels not scroll? --- GSASII/GSASIIgroupGUI.py | 366 +++++++++++++++++++++++++++++++++++++-- GSASII/GSASIImiscGUI.py | 2 +- GSASII/GSASIIpwdplot.py | 30 ++-- 3 files changed, 376 insertions(+), 22 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index bbb664a8..ce8da8ba 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -38,8 +38,8 @@ # 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 GSASIIspc as G2spc +from . import GSASIIlattice as G2lat # from . import GSASIIpwd as G2pwd from . import GSASIIctrlGUI as G2G from . import GSASIIpwdplot as G2pwpl @@ -47,13 +47,359 @@ def UpdateGroup(G2frame,item): G2gd.SetDataMenuBar(G2frame) - G2frame.dataWindow.helpKey = "Groups/PWDR" - topSizer = G2frame.dataWindow.topBox - parent = G2frame.dataWindow.topPanel - topSizer.Add(wx.StaticText(parent,label=' Group edit goes here someday'),0,WACV) - topSizer.Add((-1,-1),1,wx.EXPAND) - topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) - G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) - #G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) + G2frame.dataWindow.helpKey = "Groups/Powder" + # topSizer = G2frame.dataWindow.topBox + # parent = G2frame.dataWindow.topPanel + # topSizer.Add(wx.StaticText(parent,label=' Group edit goes here someday'),0,WACV) + # topSizer.Add((-1,-1),1,wx.EXPAND) + # topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) + # G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) + # #G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) G2frame.groupName = G2frame.GPXtree.GetItemText(item) + HAPframe(G2frame) G2pwpl.PlotPatterns(G2frame,plotType='GROUP') + +def histLabels(G2frame): + # find portion of hist name that is the same and different + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupName = G2frame.groupName + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + h0 = groupDict[groupName][0] + msk = [True] * len(h0) + for h in groupDict[groupName][1:]: + msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h,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(groupDict[groupName]): + # 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 HAPframe(G2frame): + def OnPageChanged(event): + 'respond to a notebook tab' + if event: + page = event.GetSelection() + print('page selected',page,phaseList[page]) + else: + page = 0 + print('no page selected',phaseList[page]) + HAPtable = getHAPvals(G2frame,phaseList[page]) + #for hist in HAPtable: + # showTable(phaseList[page],hist,HAPtable[hist]) + # clear out old widgets + for panel in HAPtabs: + if panel.GetSizer(): + panel.GetSizer().Destroy() + panel = HAPtabs[page] + HAPSizer = wx.FlexGridSizer(0,len(HAPtable)+1,2,10) + # construct a list of row labels, attempting to keep the + # order they appear in the original array + rowsLbls = [] + lpos = 0 + for hist in HAPtable: + prevkey = None + for key in HAPtable[hist]: + if key not in rowsLbls: + if prevkey is None: + rowsLbls.insert(lpos,key) + lpos += 1 + else: + rowsLbls.insert(rowsLbls.index(prevkey)+1,key) + prevkey = key + # label columns with histograms + common,hLbl = histLabels(G2frame) + HAPSizer.Add((-1,-1)) + for hist in hLbl: + HAPSizer.Add(wx.StaticText(panel,label=f"\u25A1 = {hist}"),0, + wx.ALIGN_CENTER) + for row in rowsLbls: + HAPSizer.Add(wx.StaticText(panel,label=row),0,WACV) + for hist in HAPtable: + if row not in HAPtable[hist]: + HAPSizer.Add((-1,-1)) + elif 'val' in HAPtable[hist][row] and 'ref' in HAPtable[hist][row]: + valrefsiz = wx.BoxSizer(wx.HORIZONTAL) + arr,indx = HAPtable[hist][row]['ref'] + valrefsiz.Add(G2G.G2CheckBox(panel,'',arr,indx),0,WACV) + arr,indx = HAPtable[hist][row]['val'] + valrefsiz.Add(G2G.ValidatedTxtCtrl(panel, + arr,indx,size=(75,-1)),0,WACV) + HAPSizer.Add(valrefsiz,0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + elif 'val' in HAPtable[hist][row]: + arr,indx = HAPtable[hist][row]['val'] + HAPSizer.Add(G2G.ValidatedTxtCtrl(panel, + arr,indx,size=(75,-1)),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + elif 'ref' in HAPtable[hist][row]: + arr,indx = HAPtable[hist][row]['ref'] + HAPSizer.Add(G2G.G2CheckBox(panel,'',arr,indx),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + elif 'str' in HAPtable[hist][row]: + HAPSizer.Add(wx.StaticText(panel, + label=HAPtable[hist][row]['str']),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) +# wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + else: + print('Should not happen',HAPtable[hist][row],hist,row) + panel.SetSizer(HAPSizer) + HAPSizer.Fit(panel) + panel.SetScrollbars(1, 1, HAPSizer.GetMinSize().width, + HAPSizer.GetMinSize().height) + + #breakpoint() + + #wx.CallAfter(FillDDataWindow,page) + + G2frame.dataWindow.ClearData() + + topSizer = G2frame.dataWindow.topBox + topParent = G2frame.dataWindow.topPanel + botSizer = G2frame.dataWindow.bottomBox + botParent = G2frame.dataWindow.bottomPanel + common,_ = histLabels(G2frame) + topSizer.Add(wx.StaticText(topParent,label=f'HAP parameters for group "{common}"'),0,WACV) + topSizer.Add((-1,-1),1,wx.EXPAND) + topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) + + # based on GSASIIddataGUI.MakeHistPhaseWin + midPanel = G2frame.dataWindow + mainSizer = wx.BoxSizer(wx.VERTICAL) + G2G.HorizontalLine(mainSizer,midPanel) + midPanel.SetSizer(mainSizer) + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() # reindex + if not Phases: + mainSizer.Add(wx.StaticText(midPanel,label='There are no phases in use')) + G2frame.dataWindow.SetDataSize() + return + 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.ScrolledWindow(HAPBook)) + HAPBook.AddPage(HAPtabs[-1],phaseName) + HAPBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged) + #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) + # fill the 'Select tab' menu + # mid = G2frame.dataWindow.DataMenu.FindMenu('Select tab') + # menu = G2frame.dataWindow.DataMenu.GetMenu(mid) + # items = menu.GetMenuItems() + # for item in items: + # menu.Remove(item) + # if len(phaseList) == 0: return + # for i,page in enumerate(phaseList): + # Id = wx.NewId() + # if menu.FindItem(page) >= 0: continue # is tab already in menu? + # menu.Append(Id,page,'') + # TabSelectionIdDict[Id] = page + # G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) + page = 0 + HAPBook.SetSelection(page) + OnPageChanged(None) + #wx.CallAfter(FillDDataWindow,page) + # done + G2frame.dataWindow.SetDataSize() + +def getHAPvals(G2frame,phase=None): + 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 + + Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + groupName = G2frame.groupName + groupDict = Controls.get('Groups',{}).get('groupDict',{}) + if groupName not in groupDict: + print(f'Unexpected: {groupName} not in groupDict') + return + # loop over histograms in group + parmDict = {} + for hist in groupDict[groupName]: + parmDict[hist] = makeHAPtbl(G2frame,phase,PhaseData,hist,Controls) + #showTable(phase,hist,parmDict[hist]) + return parmDict + +def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): + '''Construct a dict pointing to the contents of the HAP + variables. The contents of the dict will be: + + 'label' : innerdict + + where innerdict can contain the following elements: + + 'val' : (array, key) + 'ref' : (array, key) + 'str' : string + + One of these will be present. + + The 'str' value is something that cannot be edited; If 'str' is + present, it will be the only entry in innerdict. + + The 'val' tuple provides a reference to the float value for the + defined quantity, array[key] + + The 'ref' tuple provides a reference to the bool value, array[key] + for the refine flag defined quantity + + Both 'ref' and 'val' are usually defined together, but either may + occur alone. + + :return: the dict, as described above. + ''' + SGData = PhaseData['General']['SGData'] + cell = PhaseData['General']['Cell'][1:] + Amat,Bmat = G2lat.cell2AB(cell[:6]) + G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, hist) + data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) + HAPdict = PhaseData['Histograms'][hist] + + parmDict = {} + # phase fraction + parmDict['Phase fraction'] = { + 'val' : (HAPdict['Scale'],0), + 'ref' : (HAPdict['Scale'],1),} + + parmDict['LeBail extraction'] = { + 'str' : "Yes" if HAPdict.get('LeBail') else '(off)' + } + + # size values + arr = HAPdict['Size'] + if arr[0] == 'isotropic': + parmDict['Size'] = { + 'val' : (arr[1],0), + 'ref' : (arr[2],0),} + elif arr[0] == 'uniaxial': + parmDict['Size/Eq'] = { + 'val' : (arr[1],0), + 'ref' : (arr[2],0),} + parmDict['Size/Ax'] = { + 'val' : (arr[1],1), + 'ref' : (arr[2],1),} + parmDict['Size/dir'] = { + 'str' : ','.join([str(i) for i in arr[3]])} + else: + for i,lbl in enumerate(['S11','S22','S33','S12','S13','S23']): + parmDict[f'Size/{lbl}'] = { + 'val' : (arr[4],i), + 'ref' : (arr[5],i),} + parmDict['Size LGmix'] = { + 'val' : (arr[1],2), + 'ref' : (arr[2],2),} + + # microstrain values + arr = HAPdict['Mustrain'] + if arr[0] == 'isotropic': + parmDict['muStrain'] = { + 'val' : (arr[1],0), + 'ref' : (arr[2],0),} + elif arr[0] == 'uniaxial': + parmDict['muStrain/Eq'] = { + 'val' : (arr[1],0), + 'ref' : (arr[2],0),} + parmDict['muStrain/Ax'] = { + 'val' : (arr[1],1), + 'ref' : (arr[2],1),} + parmDict['muStrain/dir'] = { + 'str' : ','.join([str(i) for i in arr[3]])} + else: + Snames = G2spc.MustrainNames(SGData) + for i,lbl in enumerate(Snames): + if i >= len(arr[4]): break + parmDict[f'muStrain/{lbl}'] = { + 'val' : (arr[4],i), + 'ref' : (arr[5],i),} + muMean = G2spc.MuShklMean(SGData,Amat,arr[4][:len(Snames)]) + parmDict['muStrain/mean'] = { + 'str' : f'{muMean:.2f}'} + parmDict['muStrain LGmix'] = { + 'val' : (arr[1],2), + 'ref' : (arr[2],2),} + + # Hydrostatic terms + Hsnames = G2spc.HStrainNames(SGData) + arr = HAPdict['HStrain'] + for i,lbl in enumerate(Hsnames): + if i >= len(arr[0]): break + parmDict[f'Size/{lbl}'] = { + 'val' : (arr[0],i), + 'ref' : (arr[1],i),} + + # Preferred orientation terms + arr = HAPdict['Pref.Ori.'] + if arr[0] == 'MD': + parmDict['March-Dollase'] = { + 'val' : (arr,1), + 'ref' : (arr,2),} + parmDict['M-D/dir'] = { + 'str' : ','.join([str(i) for i in arr[3]])} + else: + parmDict['Spherical harmonics'] = { + 'ref' : (arr,2),} + parmDict['SH order'] = { + 'str' : str(arr[4])} + for lbl in arr[5]: + parmDict[f'SP {lbl}']= { + 'val' : (arr[5],lbl), + } + parmDict['SH text indx'] = { + 'str' : f'{G2lat.textureIndex(arr[5]):.3f}'} + + # misc: Layer Disp, Extinction + try: + parmDict['Layer displacement'] = { + 'val' : (HAPdict['Layer Disp'],0), + 'ref' : (HAPdict['Layer Disp'],1),} + except KeyError: + pass + try: + parmDict['Extinction'] = { + 'val' : (HAPdict['Extinction'],0), + 'ref' : (HAPdict['Extinction'],1),} + except KeyError: + pass + try: + parmDict['Babinet A'] = { + 'val' : (HAPdict['Babinet']['BabA'],0), + 'ref' : (HAPdict['Babinet']['BabA'],1),} + except KeyError: + pass + try: + parmDict['Babinet U'] = { + 'val' : (HAPdict['Babinet']['BabU'],0), + 'ref' : (HAPdict['Babinet']['BabU'],1),} + except KeyError: + pass + return parmDict + +def showTable(phase,hist,parmDict): + # show the variables and values + print(phase,hist) + for sel in parmDict: + arr = parmDict[sel] + val = 'N/A' + if 'val' in arr: + val = arr['val'][0] [arr['val'][1]] + if 'str' in arr: + val = f'"{arr['str']}"' + ref = 'N/A' + if 'ref' in arr: + ref = arr['ref'][0] [arr['ref'][1]] + print(f'{sel!r:20s} {val} {ref}') + print('\n') + #break diff --git a/GSASII/GSASIImiscGUI.py b/GSASII/GSASIImiscGUI.py index fc1bfde6..7e12fa40 100644 --- a/GSASII/GSASIImiscGUI.py +++ b/GSASII/GSASIImiscGUI.py @@ -577,7 +577,7 @@ def ProjFileOpen(G2frame,showProvenance=True): # 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/PWDR') + 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) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 4a84bb87..a0c93996 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -83,7 +83,7 @@ plotOpt['lineWid'] = '1' plotOpt['saveCSV'] = False plotOpt['CSVfile'] = None -plotOpt['GroupedX'] = False +plotOpt['sharedX'] = False for xy in 'x','y': for minmax in 'min','max': key = f'{xy}{minmax}' @@ -226,9 +226,9 @@ def OnPlotKeyPress(event): elif event.key == 'f' and 'PWDR' 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 groupName is not None: - plotOpt['GroupedX'] = not plotOpt['GroupedX'] - if not plotOpt['GroupedX']: # reset scale + 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'] @@ -408,7 +408,7 @@ def OnPlotKeyPress(event): return elif event.key == 'q' and not ifLimits: 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 @@ -422,7 +422,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: + elif event.key == 't' and ('PWDR' in plottype or plottype.startswith('GROUP') + ) and not ifLimits: newPlot = True Page.plotStyle['dPlot'] = not Page.plotStyle['dPlot'] Page.plotStyle['qPlot'] = False @@ -2046,10 +2047,12 @@ def onPartialConfig(event): if groupName is not None: # plot a group of histograms Page.Choice = [' key press', - 'f: toggle full-length ticks','g: toggle grid', + '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: toggle Q plot', + 't: toggle d-spacing plot', + 'x: share x-axes (Q/d only)'] Plot.set_visible(False) # removes "big" plot gXmin = {} gXmax = {} @@ -2069,7 +2072,9 @@ def onPartialConfig(event): for h in groupDict[groupName][1:]: msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h,msk)] # place centered-dot in loc of non-common letters - commonltrs = ''.join([h0i if m else '\u00B7' for (h0i,m) in zip(h0,msk)]) + #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(groupDict[groupName]): histlbl[i] = ''.join([hi for (hi,m) in zip(h,msk) if not m]) # unique letters gPatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, h) @@ -2107,7 +2112,10 @@ def onPartialConfig(event): # apportion axes lengths so that units are equal xfrac = [(gXmax[i]-gXmin[i])/totalrange for i in range(nx)] GS_kw = {'height_ratios':[4, 1], 'width_ratios':xfrac,} - if plotOpt['GroupedX']: + 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,nx,sharey='row',sharex=True, gridspec_kw=GS_kw) else: From 789a02e22b2585d2de00a9494d4cdc29bd4d9579 Mon Sep 17 00:00:00 2001 From: BHT Date: Tue, 28 Oct 2025 22:13:15 -0500 Subject: [PATCH 08/30] working implementation with unscrolled labels not 100% ideal and not cleaned up --- GSASII/GSASIIgroupGUI.py | 104 +++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index ce8da8ba..6eb878d8 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -79,6 +79,16 @@ def histLabels(G2frame): def HAPframe(G2frame): def OnPageChanged(event): 'respond to a notebook tab' + 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() + if event: page = event.GetSelection() print('page selected',page,phaseList[page]) @@ -93,7 +103,35 @@ def OnPageChanged(event): if panel.GetSizer(): panel.GetSizer().Destroy() panel = HAPtabs[page] - HAPSizer = wx.FlexGridSizer(0,len(HAPtable)+1,2,10) + bigSizer = wx.BoxSizer(wx.HORIZONTAL) + panel.SetSizer(bigSizer) + + # Create scrolled window for labels + #lblScroll = wx.ScrolledWindow(panel, style=wx.VSCROLL) + lblScroll = wx.ScrolledWindow(panel, style=wx.VSCROLL|wx.HSCROLL + |wx.ALWAYS_SHOW_SB + ) + lblScroll.SetScrollRate(0, 10) + hpad = 10 + lblSizer = wx.FlexGridSizer(0,1,hpad,10) + lblScroll.SetSizer(lblSizer) + bigSizer.Add(lblScroll,0,wx.EXPAND,0) +# bigSizer.Add(lblScroll) + + # Create scrolled window for data + HAPScroll = wx.ScrolledWindow(panel, style=wx.VSCROLL|wx.HSCROLL + |wx.ALWAYS_SHOW_SB + ) + #HAPScroll.ShowScrollbars(wx.SHOW_SB_ALWAYS, wx.SHOW_SB_ALWAYS) + HAPScroll.SetScrollRate(10, 10) + HAPSizer = wx.FlexGridSizer(0,len(HAPtable),hpad,10) + HAPScroll.SetSizer(HAPSizer) + bigSizer.Add(HAPScroll,1,wx.EXPAND,1) +# bigSizer.Add(HAPScroll) + + # Bind scroll events to synchronize scrolling + lblScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) + HAPScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) # construct a list of row labels, attempting to keep the # order they appear in the original array rowsLbls = [] @@ -110,45 +148,80 @@ def OnPageChanged(event): prevkey = key # label columns with histograms common,hLbl = histLabels(G2frame) - HAPSizer.Add((-1,-1)) + #HAPSizer.Add((-1,-1)) for hist in hLbl: - HAPSizer.Add(wx.StaticText(panel,label=f"\u25A1 = {hist}"),0, + HAPSizer.Add(wx.StaticText(HAPScroll,label=f"\u25A1 = {hist}"),0, wx.ALIGN_CENTER) + #for i in range(len(rowsLbls)+1): + # HAPSizer.AddGrowableRow(i) for row in rowsLbls: - HAPSizer.Add(wx.StaticText(panel,label=row),0,WACV) + #HAPSizer.Add(wx.StaticText(panel,label=row),0,WACV) for hist in HAPtable: if row not in HAPtable[hist]: HAPSizer.Add((-1,-1)) elif 'val' in HAPtable[hist][row] and 'ref' in HAPtable[hist][row]: valrefsiz = wx.BoxSizer(wx.HORIZONTAL) arr,indx = HAPtable[hist][row]['ref'] - valrefsiz.Add(G2G.G2CheckBox(panel,'',arr,indx),0,WACV) + valrefsiz.Add(G2G.G2CheckBox(HAPScroll,'',arr,indx),0,WACV) arr,indx = HAPtable[hist][row]['val'] - valrefsiz.Add(G2G.ValidatedTxtCtrl(panel, + valrefsiz.Add(G2G.ValidatedTxtCtrl(HAPScroll, arr,indx,size=(75,-1)),0,WACV) HAPSizer.Add(valrefsiz,0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) elif 'val' in HAPtable[hist][row]: arr,indx = HAPtable[hist][row]['val'] - HAPSizer.Add(G2G.ValidatedTxtCtrl(panel, + HAPSizer.Add(G2G.ValidatedTxtCtrl(HAPScroll, arr,indx,size=(75,-1)),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) elif 'ref' in HAPtable[hist][row]: arr,indx = HAPtable[hist][row]['ref'] - HAPSizer.Add(G2G.G2CheckBox(panel,'',arr,indx),0, + HAPSizer.Add(G2G.G2CheckBox(HAPScroll,'',arr,indx),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) elif 'str' in HAPtable[hist][row]: - HAPSizer.Add(wx.StaticText(panel, + HAPSizer.Add(wx.StaticText(HAPScroll, label=HAPtable[hist][row]['str']),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) # wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) else: print('Should not happen',HAPtable[hist][row],hist,row) - panel.SetSizer(HAPSizer) - HAPSizer.Fit(panel) - panel.SetScrollbars(1, 1, HAPSizer.GetMinSize().width, - HAPSizer.GetMinSize().height) + HAPSizer.Layout() + rowHeights = HAPSizer.GetRowHeights() + # label rows (must be done after HAPSizer row heights are defined) + s = wx.Size(-1,rowHeights[0]) + lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=s)) + #lblSizer.AddGrowableRow(0) + for i,row in enumerate(rowsLbls): + s = wx.Size(-1,rowHeights[i+1]) + lblSizer.Add(wx.StaticText(lblScroll,label=row,size=s),0,WACV|wx.ALIGN_RIGHT) + # lblSizer.AddGrowableRow(i+1) + #lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=(-1,10))) # pad for scrollbar + + # Fit the scrolled windows to their content + xWin,yWin = panel.GetSize() + xLbl,_ = lblSizer.GetMinSize() + xTab,yTab = HAPSizer.GetMinSize() + #lblScroll.SetSize((xLbl,-1)) + #lblScroll.SetMinSize((xLbl,-1)) + #HAPScroll.SetSize((min(xTab,xWin-xLbl),yTab)) + #lblSizer.Fit(lblScroll) + #HAPSizer.Fit(HAPScroll) + #lblScroll.SetVirtualSize(lblSizer.GetMinSize()[0],yTab) + lblScroll.SetVirtualSize(lblSizer.GetMinSize()) + HAPScroll.SetVirtualSize(HAPSizer.GetMinSize()) + print('panel',xWin,yWin) + print('lblScroll.SetSize',xLbl,yTab) + print('HAPScroll.SetSize',min(xTab,xWin-xLbl),yTab) + print('lblScroll.SetVirtualSize',lblSizer.GetMinSize()) + print('HAPScroll.SetVirtualSize',HAPSizer.GetMinSize()) + #lblScroll.ShowScrollbars(wx.SHOW_SB_DEFAULT, wx.SHOW_SB_NEVER) + G2frame.SendSizeEvent() + pass + +# panel.SetSizer(HAPSizer) +# HAPSizer.Fit(panel) +# panel.SetScrollbars(1, 1, HAPSizer.GetMinSize().width, +# HAPSizer.GetMinSize().height) #breakpoint() @@ -181,7 +254,8 @@ def OnPageChanged(event): phaseList = [] for phaseName in Phases: phaseList.append(phaseName) - HAPtabs.append(wx.ScrolledWindow(HAPBook)) + #HAPtabs.append(wx.ScrolledWindow(HAPBook)) + HAPtabs.append(wx.Panel(HAPBook)) HAPBook.AddPage(HAPtabs[-1],phaseName) HAPBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged) #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) @@ -198,12 +272,12 @@ def OnPageChanged(event): # menu.Append(Id,page,'') # TabSelectionIdDict[Id] = page # G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) + G2frame.dataWindow.SetDataSize() page = 0 HAPBook.SetSelection(page) OnPageChanged(None) #wx.CallAfter(FillDDataWindow,page) # done - G2frame.dataWindow.SetDataSize() def getHAPvals(G2frame,phase=None): sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') From 88d1f19c322430bc4e5db7301c1e040308257abf Mon Sep 17 00:00:00 2001 From: BHT Date: Wed, 29 Oct 2025 19:57:14 -0500 Subject: [PATCH 09/30] clean implementation of group HAP display --- GSASII/GSASIIgroupGUI.py | 311 ++++++++++++++++++++------------------- 1 file changed, 157 insertions(+), 154 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 6eb878d8..47ce62d8 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -24,11 +24,11 @@ # import sys # import random as ran -import numpy as np +#import numpy as np # import numpy.ma as ma import wx -from . import GSASIIpath +# from . import GSASIIpath from . import GSASIIdataGUI as G2gd # from . import GSASIIobj as G2obj # import GSASIIpwdGUI as G2pdG @@ -60,8 +60,20 @@ def UpdateGroup(G2frame,item): G2pwpl.PlotPatterns(G2frame,plotType='GROUP') def histLabels(G2frame): - # find portion of hist name that is the same and different - Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) + '''Find portion of the set of hist names that are the same for all + histograms in the current group (determined by ``G2frame.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.groupName groupDict = Controls.get('Groups',{}).get('groupDict',{}) h0 = groupDict[groupName][0] @@ -70,15 +82,55 @@ def histLabels(G2frame): msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h,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(groupDict[groupName]): # 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 displayDataTable(rowLabels,Table,Sizer,Panel): + '''Displays the data table in `Table` in Scrolledpanel `Panel` + with wx.FlexGridSizer `Sizer`. + ''' + for row in rowLabels: + for hist in Table: + # format the entry depending on what is defined + if row not in Table[hist]: + Sizer.Add((-1,-1)) + elif 'val' in Table[hist][row] and 'ref' in Table[hist][row]: + valrefsiz = wx.BoxSizer(wx.HORIZONTAL) + arr,indx = Table[hist][row]['ref'] + valrefsiz.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, + wx.ALIGN_CENTER_VERTICAL) + arr,indx = Table[hist][row]['val'] + valrefsiz.Add(G2G.ValidatedTxtCtrl(Panel, + arr,indx,size=(75,-1)),0,WACV) + Sizer.Add(valrefsiz,0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + elif 'val' in Table[hist][row]: + arr,indx = Table[hist][row]['val'] + Sizer.Add(G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + elif 'ref' in Table[hist][row]: + arr,indx = Table[hist][row]['ref'] + Sizer.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + elif 'str' in Table[hist][row]: + Sizer.Add(wx.StaticText(Panel,label=Table[hist][row]['str']),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) + else: + print('Should not happen',Table[hist][row],hist,row) + def HAPframe(G2frame): - def OnPageChanged(event): - 'respond to a notebook tab' + '''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() @@ -88,176 +140,123 @@ def OnScroll(event): 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: + #print('page selected',page,phaseList[page]) + else: # initial call when window is created page = 0 - print('no page selected',phaseList[page]) + #print('no page selected',phaseList[page]) + # generate a dict with HAP values for each phase (may not be the same) HAPtable = getHAPvals(G2frame,phaseList[page]) - #for hist in HAPtable: - # showTable(phaseList[page],hist,HAPtable[hist]) - # clear out old widgets + #debug# for hist in HAPtable: printTable(phaseList[page],hist,HAPtable[hist]) # see the dict + # construct a list of row labels, attempting to keep the + # order they appear in the original array + rowLabels = [] + lpos = 0 + for hist in HAPtable: + prevkey = None + for key in HAPtable[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() + panel.GetSizer().Destroy() # clear out old widgets panel = HAPtabs[page] bigSizer = wx.BoxSizer(wx.HORIZONTAL) panel.SetSizer(bigSizer) - - # Create scrolled window for labels - #lblScroll = wx.ScrolledWindow(panel, style=wx.VSCROLL) - lblScroll = wx.ScrolledWindow(panel, style=wx.VSCROLL|wx.HSCROLL - |wx.ALWAYS_SHOW_SB - ) - lblScroll.SetScrollRate(0, 10) - hpad = 10 + # 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,1,hpad,10) lblScroll.SetSizer(lblSizer) - bigSizer.Add(lblScroll,0,wx.EXPAND,0) -# bigSizer.Add(lblScroll) - - # Create scrolled window for data - HAPScroll = wx.ScrolledWindow(panel, style=wx.VSCROLL|wx.HSCROLL - |wx.ALWAYS_SHOW_SB - ) - #HAPScroll.ShowScrollbars(wx.SHOW_SB_ALWAYS, wx.SHOW_SB_ALWAYS) - HAPScroll.SetScrollRate(10, 10) + 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(HAPtable),hpad,10) HAPScroll.SetSizer(HAPSizer) - bigSizer.Add(HAPScroll,1,wx.EXPAND,1) -# bigSizer.Add(HAPScroll) + bigSizer.Add(HAPScroll,1,wx.EXPAND) # Bind scroll events to synchronize scrolling lblScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) HAPScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) - # construct a list of row labels, attempting to keep the - # order they appear in the original array - rowsLbls = [] - lpos = 0 - for hist in HAPtable: - prevkey = None - for key in HAPtable[hist]: - if key not in rowsLbls: - if prevkey is None: - rowsLbls.insert(lpos,key) - lpos += 1 - else: - rowsLbls.insert(rowsLbls.index(prevkey)+1,key) - prevkey = key - # label columns with histograms - common,hLbl = histLabels(G2frame) - #HAPSizer.Add((-1,-1)) - for hist in hLbl: - HAPSizer.Add(wx.StaticText(HAPScroll,label=f"\u25A1 = {hist}"),0, - wx.ALIGN_CENTER) - #for i in range(len(rowsLbls)+1): - # HAPSizer.AddGrowableRow(i) - for row in rowsLbls: - #HAPSizer.Add(wx.StaticText(panel,label=row),0,WACV) - for hist in HAPtable: - if row not in HAPtable[hist]: - HAPSizer.Add((-1,-1)) - elif 'val' in HAPtable[hist][row] and 'ref' in HAPtable[hist][row]: - valrefsiz = wx.BoxSizer(wx.HORIZONTAL) - arr,indx = HAPtable[hist][row]['ref'] - valrefsiz.Add(G2G.G2CheckBox(HAPScroll,'',arr,indx),0,WACV) - arr,indx = HAPtable[hist][row]['val'] - valrefsiz.Add(G2G.ValidatedTxtCtrl(HAPScroll, - arr,indx,size=(75,-1)),0,WACV) - HAPSizer.Add(valrefsiz,0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - elif 'val' in HAPtable[hist][row]: - arr,indx = HAPtable[hist][row]['val'] - HAPSizer.Add(G2G.ValidatedTxtCtrl(HAPScroll, - arr,indx,size=(75,-1)),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - - elif 'ref' in HAPtable[hist][row]: - arr,indx = HAPtable[hist][row]['ref'] - HAPSizer.Add(G2G.G2CheckBox(HAPScroll,'',arr,indx),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - elif 'str' in HAPtable[hist][row]: - HAPSizer.Add(wx.StaticText(HAPScroll, - label=HAPtable[hist][row]['str']),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) -# wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - else: - print('Should not happen',HAPtable[hist][row],hist,row) + # label columns with unique part of histogram names + for hist in histLabels(G2frame)[1]: + HAPSizer.Add(wx.StaticText(HAPScroll,label=f"\u25A1 = {hist}"), + 0,wx.ALIGN_CENTER) + displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll) + # get row sizes in data table HAPSizer.Layout() rowHeights = HAPSizer.GetRowHeights() - # label rows (must be done after HAPSizer row heights are defined) + # match rose sizes in Labels + # (must be done after HAPSizer row heights are defined) s = wx.Size(-1,rowHeights[0]) lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=s)) - #lblSizer.AddGrowableRow(0) - for i,row in enumerate(rowsLbls): + for i,row in enumerate(rowLabels): s = wx.Size(-1,rowHeights[i+1]) - lblSizer.Add(wx.StaticText(lblScroll,label=row,size=s),0,WACV|wx.ALIGN_RIGHT) - # lblSizer.AddGrowableRow(i+1) - #lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=(-1,10))) # pad for scrollbar - + lblSizer.Add(wx.StaticText(lblScroll,label=row,size=s),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) # Fit the scrolled windows to their content - xWin,yWin = panel.GetSize() + lblSizer.Layout() xLbl,_ = lblSizer.GetMinSize() xTab,yTab = HAPSizer.GetMinSize() - #lblScroll.SetSize((xLbl,-1)) - #lblScroll.SetMinSize((xLbl,-1)) - #HAPScroll.SetSize((min(xTab,xWin-xLbl),yTab)) - #lblSizer.Fit(lblScroll) - #HAPSizer.Fit(HAPScroll) - #lblScroll.SetVirtualSize(lblSizer.GetMinSize()[0],yTab) + lblScroll.SetSize((xLbl,yTab)) + lblScroll.SetMinSize((xLbl+15,yTab)) # add room for scroll bar lblScroll.SetVirtualSize(lblSizer.GetMinSize()) HAPScroll.SetVirtualSize(HAPSizer.GetMinSize()) - print('panel',xWin,yWin) - print('lblScroll.SetSize',xLbl,yTab) - print('HAPScroll.SetSize',min(xTab,xWin-xLbl),yTab) - print('lblScroll.SetVirtualSize',lblSizer.GetMinSize()) - print('HAPScroll.SetVirtualSize',HAPSizer.GetMinSize()) - #lblScroll.ShowScrollbars(wx.SHOW_SB_DEFAULT, wx.SHOW_SB_NEVER) - G2frame.SendSizeEvent() - pass - -# panel.SetSizer(HAPSizer) -# HAPSizer.Fit(panel) -# panel.SetScrollbars(1, 1, HAPSizer.GetMinSize().width, -# HAPSizer.GetMinSize().height) - - #breakpoint() - - #wx.CallAfter(FillDDataWindow,page) + 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) + wx.CallAfter(G2frame.SendSizeEvent) 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 - botSizer = G2frame.dataWindow.bottomBox - botParent = G2frame.dataWindow.bottomPanel - common,_ = histLabels(G2frame) - topSizer.Add(wx.StaticText(topParent,label=f'HAP parameters for group "{common}"'),0,WACV) + midPanel = G2frame.dataWindow + mainSizer = wx.BoxSizer(wx.VERTICAL) + #botSizer = G2frame.dataWindow.bottomBox + #botParent = G2frame.dataWindow.bottomPanel + # label with shared portion of histogram name + topSizer.Add(wx.StaticText(topParent, + label=f'HAP parameters for group "{histLabels(G2frame)[0]}"'), + 0,WACV) topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) - # based on GSASIIddataGUI.MakeHistPhaseWin - midPanel = G2frame.dataWindow - mainSizer = wx.BoxSizer(wx.VERTICAL) G2G.HorizontalLine(mainSizer,midPanel) midPanel.SetSizer(mainSizer) - Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() # reindex + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() if not Phases: - mainSizer.Add(wx.StaticText(midPanel,label='There are no phases in use')) + 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.ScrolledWindow(HAPBook)) HAPtabs.append(wx.Panel(HAPBook)) HAPBook.AddPage(HAPtabs[-1],phaseName) - HAPBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged) + HAPBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, selectPhase) + + # code to set menu bar contents #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) # fill the 'Select tab' menu # mid = G2frame.dataWindow.DataMenu.FindMenu('Select tab') @@ -275,11 +274,13 @@ def OnScroll(event): G2frame.dataWindow.SetDataSize() page = 0 HAPBook.SetSelection(page) - OnPageChanged(None) - #wx.CallAfter(FillDDataWindow,page) - # done + selectPhase(None) -def getHAPvals(G2frame,phase=None): +def getHAPvals(G2frame,phase): + '''Generate a dict of dicts with all HAP values for the selected phase + and all histograms in the selected histogram group (from G2frame.groupName). + This will be used to generate the contents of the is what will be + ''' sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Phases') item, cookie = G2frame.GPXtree.GetFirstChild(sub) PhaseData = None @@ -304,16 +305,18 @@ def getHAPvals(G2frame,phase=None): parmDict = {} for hist in groupDict[groupName]: parmDict[hist] = makeHAPtbl(G2frame,phase,PhaseData,hist,Controls) - #showTable(phase,hist,parmDict[hist]) + #printTable(phase,hist,parmDict[hist]) return parmDict def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): '''Construct a dict pointing to the contents of the HAP - variables. The contents of the dict will be: + variables for one phase and histogram. + + The contents of the dict will be: - 'label' : innerdict + 'label' : `innerdict` - where innerdict can contain the following elements: + where `innerdict` can contain the following elements: 'val' : (array, key) 'ref' : (array, key) @@ -322,7 +325,7 @@ def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): One of these will be present. The 'str' value is something that cannot be edited; If 'str' is - present, it will be the only entry in innerdict. + present, it will be the only entry in `innerdict`. The 'val' tuple provides a reference to the float value for the defined quantity, array[key] @@ -339,16 +342,16 @@ def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): cell = PhaseData['General']['Cell'][1:] Amat,Bmat = G2lat.cell2AB(cell[:6]) G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, hist) - data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) + #data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) HAPdict = PhaseData['Histograms'][hist] parmDict = {} # phase fraction - parmDict['Phase fraction'] = { + parmDict['Phase frac'] = { 'val' : (HAPdict['Scale'],0), 'ref' : (HAPdict['Scale'],1),} - parmDict['LeBail extraction'] = { + parmDict['LeBail extract'] = { 'str' : "Yes" if HAPdict.get('LeBail') else '(off)' } @@ -379,29 +382,29 @@ def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): # microstrain values arr = HAPdict['Mustrain'] if arr[0] == 'isotropic': - parmDict['muStrain'] = { + parmDict['\u00B5Strain'] = { 'val' : (arr[1],0), 'ref' : (arr[2],0),} elif arr[0] == 'uniaxial': - parmDict['muStrain/Eq'] = { + parmDict['\u00B5Strain/Eq'] = { 'val' : (arr[1],0), 'ref' : (arr[2],0),} - parmDict['muStrain/Ax'] = { + parmDict['\u00B5Strain/Ax'] = { 'val' : (arr[1],1), 'ref' : (arr[2],1),} - parmDict['muStrain/dir'] = { + parmDict['\u00B5Strain/dir'] = { 'str' : ','.join([str(i) for i in arr[3]])} else: Snames = G2spc.MustrainNames(SGData) for i,lbl in enumerate(Snames): if i >= len(arr[4]): break - parmDict[f'muStrain/{lbl}'] = { + parmDict[f'\u00B5Strain/{lbl}'] = { 'val' : (arr[4],i), 'ref' : (arr[5],i),} muMean = G2spc.MuShklMean(SGData,Amat,arr[4][:len(Snames)]) - parmDict['muStrain/mean'] = { + parmDict['\u00B5Strain/mean'] = { 'str' : f'{muMean:.2f}'} - parmDict['muStrain LGmix'] = { + parmDict['\u00B5Strain LGmix'] = { 'val' : (arr[1],2), 'ref' : (arr[2],2),} @@ -436,7 +439,7 @@ def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): # misc: Layer Disp, Extinction try: - parmDict['Layer displacement'] = { + parmDict['Layer displ'] = { 'val' : (HAPdict['Layer Disp'],0), 'ref' : (HAPdict['Layer Disp'],1),} except KeyError: @@ -461,8 +464,8 @@ def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): pass return parmDict -def showTable(phase,hist,parmDict): - # show the variables and values +def printTable(phase,hist,parmDict): + # show the variables and values in data table array -- debug use only print(phase,hist) for sel in parmDict: arr = parmDict[sel] @@ -470,10 +473,10 @@ def showTable(phase,hist,parmDict): if 'val' in arr: val = arr['val'][0] [arr['val'][1]] if 'str' in arr: - val = f'"{arr['str']}"' + v = arr['str'] + val = f'"{v}"' ref = 'N/A' if 'ref' in arr: ref = arr['ref'][0] [arr['ref'][1]] print(f'{sel!r:20s} {val} {ref}') print('\n') - #break From df236d1b991fddff363243c828ff73fddae73519 Mon Sep 17 00:00:00 2001 From: BHT Date: Sat, 1 Nov 2025 13:00:51 -0500 Subject: [PATCH 10/30] WIP: now sample param works --- GSASII/GSASIIgroupGUI.py | 398 ++++++++++++++++++++++++++++++++++----- 1 file changed, 355 insertions(+), 43 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 47ce62d8..d4e67dec 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -13,6 +13,47 @@ the string used to set the grouping See SearchGroups in :func:`GSASIIdataGUI.UpdateControls`. + + +** Parameter Data Table ** +Prior to display in the GUI, parameters are organized in a dict where each +dict entry has contents of form: + + * 'label' : `innerdict` + +where `innerdict` can contain the following elements: + + * 'val' : (array, key) + * 'ref' : (array, key) + * 'str' : string + * 'init' : float + +One of these elements will be present. + + * The 'str' value is something that cannot be edited; If 'str' is + present, it will be the only entry in `innerdict`. It is used for + a parameter value that is typically computed or must be edited in + the histogram section. + + * The 'init' value is also something that cannot be edited. + These 'init' values are used for Instrument Parameters + where there is both a cuurent value for the parameter as + well as an initial value (usually read from the instrument + parameters file whne 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 'val' tuple provides a reference to the float value for the + defined quantity, where array[key] provides r/w access to the + parameter. + + * The 'ref' tuple provides a reference to the bool value, where + array[key] 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. ''' # import math @@ -31,7 +72,7 @@ # from . import GSASIIpath from . import GSASIIdataGUI as G2gd # from . import GSASIIobj as G2obj -# import GSASIIpwdGUI as G2pdG +from . import GSASIIpwdGUI as G2pdG # from . import GSASIIimgGUI as G2imG # from . import GSASIIElem as G2el # from . import GSASIIfiles as G2fil @@ -56,7 +97,9 @@ def UpdateGroup(G2frame,item): # G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) # #G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) G2frame.groupName = G2frame.GPXtree.GetItemText(item) - HAPframe(G2frame) + #HAPframe(G2frame) + HistFrame(G2frame) + G2pwpl.PlotPatterns(G2frame,plotType='GROUP') def histLabels(G2frame): @@ -87,11 +130,15 @@ def histLabels(G2frame): for h in groupDict[groupName]] return commonltrs,histlbls -def displayDataTable(rowLabels,Table,Sizer,Panel): +def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False): '''Displays the data table in `Table` in Scrolledpanel `Panel` with wx.FlexGridSizer `Sizer`. ''' + firstentry = None for row in rowLabels: + if lblRow: + Sizer.Add(wx.StaticText(Panel,label=row),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) for hist in Table: # format the entry depending on what is defined if row not in Table[hist]: @@ -102,14 +149,16 @@ def displayDataTable(rowLabels,Table,Sizer,Panel): valrefsiz.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, wx.ALIGN_CENTER_VERTICAL) arr,indx = Table[hist][row]['val'] - valrefsiz.Add(G2G.ValidatedTxtCtrl(Panel, - arr,indx,size=(75,-1)),0,WACV) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)) + valrefsiz.Add(w,0,WACV) + if firstentry is None: firstentry = w Sizer.Add(valrefsiz,0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) elif 'val' in Table[hist][row]: arr,indx = Table[hist][row]['val'] - Sizer.Add(G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)) + Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + if firstentry is None: firstentry = w elif 'ref' in Table[hist][row]: arr,indx = Table[hist][row]['ref'] @@ -120,7 +169,154 @@ def displayDataTable(rowLabels,Table,Sizer,Panel): wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) else: print('Should not happen',Table[hist][row],hist,row) + return firstentry + +def HistFrame(G2frame): + '''Give up on side-by-side scrolled panels + Put everything in a single FlexGridSizer. + ''' + #--------------------------------------------------------------------- + # generate a dict with values for each histogram + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + tblType = 'Sample' + prmTable = getSampleVals(G2frame,Histograms) + #debug# for hist in prmTable: printTable(phaseList[page],hist,prmTable[hist]) # see the dict + # construct a list of row labels, attempting to keep the + # order they appear in the original array + rowLabels = [] + lpos = 0 + for hist in prmTable: + prevkey = None + for key in prmTable[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 =============================================== + G2frame.dataWindow.ClearData() + + # layout the window + topSizer = G2frame.dataWindow.topBox + topParent = G2frame.dataWindow.topPanel + midPanel = G2frame.dataWindow + # label with shared portion of histogram name + topSizer.Add(wx.StaticText(topParent, + label=f'{tblType} parameters for group "{histLabels(G2frame)[0]}"'), + 0,WACV) + topSizer.Add((-1,-1),1,wx.EXPAND) + topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) + + panel = midPanel + mainSizer = wx.BoxSizer(wx.VERTICAL) + G2G.HorizontalLine(mainSizer,panel) + panel.SetSizer(mainSizer) + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + + # set menu bar contents + #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) + + valSizer = wx.FlexGridSizer(0,len(prmTable)+1,3,10) + mainSizer.Add(valSizer,1,wx.EXPAND) + valSizer.Add(wx.StaticText(midPanel,label=' ')) + for hist in histLabels(G2frame)[1]: + valSizer.Add(wx.StaticText(midPanel, + label=f"\u25A1 = {hist}"), + 0,wx.ALIGN_CENTER) + firstentry = displayDataTable(rowLabels,prmTable,valSizer,midPanel,True) + G2frame.dataWindow.SetDataSize() + if firstentry is not None: # prevent scroll to show last entry + wx.Window.SetFocus(firstentry) + firstentry.SetInsertionPoint(0) # prevent selection of text in widget + wx.CallLater(100,G2frame.SendSizeEvent) + +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.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.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 = [] + parmDict = {} + # loop over histograms in group + for hist in groupDict[groupName]: + histdata = Histograms[hist] + hpD = {} + hpD['Inst. name'] = { + 'str' : histdata['Sample Parameters']['InstrName']} + # need to make blank allowed before we can do this: + # 'val' : (histdata['Sample Parameters'],'InstrName')} + hpD['Diff type'] = { + 'str' : histdata['Sample Parameters']['Type']} + arr = histdata['Sample Parameters']['Scale'] + hpD['Scale factor'] = { + 'val' : (arr,0), + 'ref' : (arr,1),} + # make a list of parameters to show + histType = histdata['Instrument Parameters'][0]['Type'][0] + dataType = histdata['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]) + for key in ('FreePrm1','FreePrm2','FreePrm3'): + parms.append([key,Controls[key],None]) + #parms.append([key,[Controls,key],None]) + + # and loop over them + for key,lbl,fmt in parms: + if fmt is None and type(histdata['Sample Parameters'][key]) is list: + arr = histdata['Sample Parameters'][key] + hpD[lbl] = { + 'val' : (arr,0), + 'ref' : (arr,1),} + elif fmt is None: + hpD[lbl] = { + 'val' : (histdata['Sample Parameters'],key)} + elif type(fmt) is str: + hpD[lbl] = { + 'str' : f'{histdata['Sample Parameters'][key]:{fmt}}'} + + # parametric parameters + #breakpoint() + #break + parmDict[hist] = hpD + #printTable("",hist,parmDict[hist]) + #breakpoint() + return parmDict + def HAPframe(G2frame): '''This creates two side-by-side scrolled panels, each containing a FlexGridSizer. @@ -216,7 +412,7 @@ def OnScroll(event): 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) - wx.CallAfter(G2frame.SendSizeEvent) + wx.CallLater(100,G2frame.SendSizeEvent) G2frame.dataWindow.ClearData() @@ -277,9 +473,10 @@ def OnScroll(event): selectPhase(None) def getHAPvals(G2frame,phase): - '''Generate a dict of dicts with all HAP values for the selected phase - and all histograms in the selected histogram group (from G2frame.groupName). - This will be used to generate the contents of the is what will be + '''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.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) @@ -293,7 +490,7 @@ def getHAPvals(G2frame,phase): item, cookie = G2frame.GPXtree.GetNextChild(sub, cookie) if PhaseData is None: print(f'Unexpected: Phase {phase!r} not found') - return + return {} Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) groupName = G2frame.groupName @@ -304,44 +501,20 @@ def getHAPvals(G2frame,phase): # loop over histograms in group parmDict = {} for hist in groupDict[groupName]: - parmDict[hist] = makeHAPtbl(G2frame,phase,PhaseData,hist,Controls) + parmDict[hist] = makeHAPtbl(G2frame,phase,PhaseData,hist) #printTable(phase,hist,parmDict[hist]) return parmDict -def makeHAPtbl(G2frame,phase,PhaseData,hist,Controls): - '''Construct a dict pointing to the contents of the HAP - variables for one phase and histogram. - - The contents of the dict will be: - - 'label' : `innerdict` - - where `innerdict` can contain the following elements: - - 'val' : (array, key) - 'ref' : (array, key) - 'str' : string - - One of these will be present. - - The 'str' value is something that cannot be edited; If 'str' is - present, it will be the only entry in `innerdict`. +def makeHAPtbl(G2frame,phase,PhaseData,hist): + '''Construct a Parameter Data Table dict, providing access + to the HAP variables for one phase and histogram. - The 'val' tuple provides a reference to the float value for the - defined quantity, array[key] - - The 'ref' tuple provides a reference to the bool value, array[key] - for the refine flag defined quantity - - Both 'ref' and 'val' are usually defined together, but either may - occur alone. - - :return: the dict, as described above. + :return: the Parameter Data Table dict, as described above. ''' SGData = PhaseData['General']['SGData'] cell = PhaseData['General']['Cell'][1:] Amat,Bmat = G2lat.cell2AB(cell[:6]) - G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, hist) + #G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, hist) #data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) HAPdict = PhaseData['Histograms'][hist] @@ -479,4 +652,143 @@ def printTable(phase,hist,parmDict): if 'ref' in arr: ref = arr['ref'][0] [arr['ref'][1]] print(f'{sel!r:20s} {val} {ref}') - print('\n') + print('\n') + +# code that did not work and is being abandoned for now +# +# def HistFrame(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 OnScroll(event): +# 'Synchronize vertical scrolling between the two scrolled windows' +# obj = event.GetEventObject() +# pos = obj.GetViewStart()[1] +# if obj == lblScroll: +# SampleScroll.Scroll(-1, pos) +# else: +# lblScroll.Scroll(-1, pos) +# event.Skip() +# #--------------------------------------------------------------------- +# # generate a dict with Sample values for each histogram +# Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() +# HAPtable = getSampleVals(G2frame,Histograms) +# #debug# for hist in HAPtable: printTable(phaseList[page],hist,HAPtable[hist]) # see the dict +# # construct a list of row labels, attempting to keep the +# # order they appear in the original array +# rowLabels = [] +# lpos = 0 +# for hist in HAPtable: +# prevkey = None +# for key in HAPtable[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 =============================================== +# #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 +# # this causes a crash, but somehow I need to clean up the old contents +# #if midPanel.GetSizer(): +# # for i in midPanel.GetSizer().GetChildren(): +# # i.Destroy() # clear out old widgets +# #botSizer = G2frame.dataWindow.bottomBox +# #botParent = G2frame.dataWindow.bottomPanel +# # label with shared portion of histogram name +# topSizer.Add(wx.StaticText(topParent, +# label=f'Sample parameters for group "{histLabels(G2frame)[0]}"'), +# 0,WACV) +# topSizer.Add((-1,-1),1,wx.EXPAND) +# topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) + +# panel = wx.Panel(midPanel) +# panel.SetSize(midPanel.GetSize()) +# mainSizer = wx.BoxSizer(wx.VERTICAL) +# G2G.HorizontalLine(mainSizer,panel) +# panel.SetSizer(mainSizer) +# Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + +# # code to set menu bar contents +# #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) +# # fill the 'Select tab' menu +# # mid = G2frame.dataWindow.DataMenu.FindMenu('Select tab') +# # menu = G2frame.dataWindow.DataMenu.GetMenu(mid) +# # items = menu.GetMenuItems() +# # for item in items: +# # menu.Remove(item) +# # if len(phaseList) == 0: return +# # for i,page in enumerate(phaseList): +# # Id = wx.NewId() +# # if menu.FindItem(page) >= 0: continue # is tab already in menu? +# # menu.Append(Id,page,'') +# # TabSelectionIdDict[Id] = page +# # G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) +# #page = 0 +# #HAPBook.SetSelection(page) +# #selectPhase(None) + +# bigSizer = wx.BoxSizer(wx.HORIZONTAL) +# mainSizer.Add(bigSizer,1,wx.EXPAND) +# if True: +# # 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,1,hpad,10) +# lblScroll.SetSizer(lblSizer) +# bigSizer.Add(lblScroll,0,wx.EXPAND) + +# # Create scrolled panel to display HAP data +# SampleScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, +# style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) +# SampleSizer = wx.FlexGridSizer(0,len(HAPtable),hpad,10) +# SampleScroll.SetSizer(SampleSizer) +# bigSizer.Add(SampleScroll,1,wx.EXPAND) + +# # Bind scroll events to synchronize scrolling +# lblScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) +# SampleScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) +# # label columns with unique part of histogram names +# for hist in histLabels(G2frame)[1]: +# SampleSizer.Add(wx.StaticText(SampleScroll,label=f"\u25A1 = {hist}"), +# 0,wx.ALIGN_CENTER) + +# displayDataTable(rowLabels,HAPtable,SampleSizer,SampleScroll) +# # get row sizes in data table +# SampleSizer.Layout() +# rowHeights = SampleSizer.GetRowHeights() +# # match rose sizes in Labels +# # (must be done after SampleSizer row heights are defined) +# s = wx.Size(-1,rowHeights[0]) +# lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=s)) +# for i,row in enumerate(rowLabels): +# s = wx.Size(-1,rowHeights[i+1]) +# lblSizer.Add(wx.StaticText(lblScroll,label=row,size=s),0, +# wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) +# # Fit the scrolled windows to their content +# lblSizer.Layout() +# xLbl,_ = lblSizer.GetMinSize() +# xTab,yTab = SampleSizer.GetMinSize() +# lblScroll.SetSize((xLbl,yTab)) +# lblScroll.SetMinSize((xLbl+15,yTab)) # add room for scroll bar +# lblScroll.SetVirtualSize(lblSizer.GetMinSize()) +# SampleScroll.SetVirtualSize(SampleSizer.GetMinSize()) +# lblScroll.SetupScrolling(scroll_x=True, scroll_y=True, rate_x=20, rate_y=20) +# SampleScroll.SetupScrolling(scroll_x=True, scroll_y=True, rate_x=20, rate_y=20) +# breakpoint() +# wx.CallLater(100,G2frame.SendSizeEvent) +# G2frame.dataWindow.SetDataSize() From 1f3458e00f7c2d1d7bb070749315e574cdd6226c Mon Sep 17 00:00:00 2001 From: BHT Date: Sat, 1 Nov 2025 13:53:47 -0500 Subject: [PATCH 11/30] WIP: Edit parametric name; move refine after value --- GSASII/GSASIIgroupGUI.py | 73 +++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index d4e67dec..25c5e6d4 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -27,22 +27,15 @@ * 'ref' : (array, key) * 'str' : string * 'init' : float + * 'rowlbl' : (array, key) -One of these elements will be present. +One of 'val', 'ref' or 'str' elements will be present. * The 'str' value is something that cannot be edited; If 'str' is present, it will be the only entry in `innerdict`. It is used for a parameter value that is typically computed or must be edited in the histogram section. - * The 'init' value is also something that cannot be edited. - These 'init' values are used for Instrument Parameters - where there is both a cuurent value for the parameter as - well as an initial value (usually read from the instrument - parameters file whne 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 'val' tuple provides a reference to the float value for the defined quantity, where array[key] provides r/w access to the parameter. @@ -53,7 +46,20 @@ 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. + refine flag is used for a group of parameters. + + * The 'init' value is also something that cannot be edited. + These 'init' values are used for Instrument Parameters + where there is both a cuurent value for the parameter as + well as an initial value (usually read from the instrument + parameters file whne 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 (FreePrm sample parametric + values). + ''' # import math @@ -136,34 +142,45 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False): ''' firstentry = None for row in rowLabels: - if lblRow: - Sizer.Add(wx.StaticText(Panel,label=row),0, + if lblRow: # show the row label, when not in a separate sizer + arr = None + for hist in Table: + if row not in Table[hist]: continue + if 'rowlbl' in Table[hist][row]: + arr,key = Table[hist][row]['rowlbl'] + break + if arr is None: + Sizer.Add(wx.StaticText(Panel,label=row),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + else: + Sizer.Add( + G2G.ValidatedTxtCtrl(Panel,arr,key,size=(125,-1)), + 0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) for hist in Table: # format the entry depending on what is defined if row not in Table[hist]: Sizer.Add((-1,-1)) elif 'val' in Table[hist][row] and 'ref' in Table[hist][row]: valrefsiz = wx.BoxSizer(wx.HORIZONTAL) - arr,indx = Table[hist][row]['ref'] - valrefsiz.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, - wx.ALIGN_CENTER_VERTICAL) arr,indx = Table[hist][row]['val'] w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)) valrefsiz.Add(w,0,WACV) if firstentry is None: firstentry = w + arr,indx = Table[hist][row]['ref'] + valrefsiz.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, + wx.ALIGN_CENTER_VERTICAL) Sizer.Add(valrefsiz,0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) elif 'val' in Table[hist][row]: arr,indx = Table[hist][row]['val'] - w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)) - Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1),notBlank=False) + Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) if firstentry is None: firstentry = w elif 'ref' in Table[hist][row]: arr,indx = Table[hist][row]['ref'] Sizer.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) elif 'str' in Table[hist][row]: Sizer.Add(wx.StaticText(Panel,label=Table[hist][row]['str']),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) @@ -253,9 +270,7 @@ def getSampleVals(G2frame,Histograms): histdata = Histograms[hist] hpD = {} hpD['Inst. name'] = { - 'str' : histdata['Sample Parameters']['InstrName']} - # need to make blank allowed before we can do this: - # 'val' : (histdata['Sample Parameters'],'InstrName')} + 'val' : (histdata['Sample Parameters'],'InstrName')} hpD['Diff type'] = { 'str' : histdata['Sample Parameters']['Type']} arr = histdata['Sample Parameters']['Scale'] @@ -291,9 +306,6 @@ def getSampleVals(G2frame,Histograms): parms.append(['Time','time',None]) parms.append(['Temperature','Sample T',None]) parms.append(['Pressure','Sample P',None]) - for key in ('FreePrm1','FreePrm2','FreePrm3'): - parms.append([key,Controls[key],None]) - #parms.append([key,[Controls,key],None]) # and loop over them for key,lbl,fmt in parms: @@ -309,12 +321,13 @@ def getSampleVals(G2frame,Histograms): hpD[lbl] = { 'str' : f'{histdata['Sample Parameters'][key]:{fmt}}'} - # parametric parameters - #breakpoint() - #break + for key in ('FreePrm1','FreePrm2','FreePrm3'): + lbl = Controls[key] + hpD[lbl] = { + 'val' : (histdata['Sample Parameters'],key), + 'rowlbl' : (Controls,key) + } parmDict[hist] = hpD - #printTable("",hist,parmDict[hist]) - #breakpoint() return parmDict def HAPframe(G2frame): From 2019dbb0b606030bb9b15a824400404ac0d0e5df Mon Sep 17 00:00:00 2001 From: BHT Date: Tue, 4 Nov 2025 10:21:22 -0600 Subject: [PATCH 12/30] Full working version to display HAP, Sample & Inst vals --- GSASII/GSASIIgroupGUI.py | 225 ++++++++++++++++++++++++++++++--------- GSASII/GSASIIpwdplot.py | 2 +- 2 files changed, 173 insertions(+), 54 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 25c5e6d4..47e0c36e 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -57,7 +57,7 @@ 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 (FreePrm sample parametric + will be an editable row label (FreePrmX sample parametric values). ''' @@ -92,25 +92,54 @@ from . import GSASIIpwdplot as G2pwpl WACV = wx.ALIGN_CENTER_VERTICAL -def UpdateGroup(G2frame,item): +def UpdateGroup(G2frame,item,plot=True): + def onDisplaySel(event): + G2frame.GroupInfo['displayMode'] = dsplType.GetValue() + wx.CallAfter(UpdateGroup,G2frame,item,False) + #UpdateGroup(G2frame,item) + + 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.ClearData() G2frame.dataWindow.helpKey = "Groups/Powder" - # topSizer = G2frame.dataWindow.topBox + 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'], + 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)) + # parent = G2frame.dataWindow.topPanel # topSizer.Add(wx.StaticText(parent,label=' Group edit goes here someday'),0,WACV) # topSizer.Add((-1,-1),1,wx.EXPAND) # topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) # G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) # #G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) - G2frame.groupName = G2frame.GPXtree.GetItemText(item) - #HAPframe(G2frame) - HistFrame(G2frame) + G2frame.GroupInfo['groupName'] = G2frame.GPXtree.GetItemText(item) + if G2frame.GroupInfo['displayMode'].startswith('Hist'): + HAPframe(G2frame) + else: + HistFrame(G2frame) + if plot: G2pwpl.PlotPatterns(G2frame,plotType='GROUP') + #print('CallLater') + G2frame.dataWindow.SetDataSize() + #wx.CallLater(100,G2frame.SendSizeEvent) + wx.CallAfter(G2frame.SendSizeEvent) - G2pwpl.PlotPatterns(G2frame,plotType='GROUP') - 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.groupName``) + histograms in the current group (determined by ``G2frame.GroupInfo['groupName']``) and then for each histogram, the characters that are different. :Returns: commonltrs, histlbls where @@ -123,7 +152,7 @@ def histLabels(G2frame): ''' Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId( G2frame,G2frame.root, 'Controls')) - groupName = G2frame.groupName + groupName = G2frame.GroupInfo['groupName'] groupDict = Controls.get('Groups',{}).get('groupDict',{}) h0 = groupDict[groupName][0] msk = [True] * len(h0) @@ -136,13 +165,14 @@ def histLabels(G2frame): for h in groupDict[groupName]] return commonltrs,histlbls -def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False): +def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): '''Displays the data table in `Table` in Scrolledpanel `Panel` with wx.FlexGridSizer `Sizer`. ''' firstentry = None + for row in rowLabels: - if lblRow: # show the row label, when not in a separate sizer + if lblRow: # show the row labels, when not in a separate sizer arr = None for hist in Table: if row not in Table[hist]: continue @@ -160,10 +190,34 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False): # format the entry depending on what is defined if row not in Table[hist]: Sizer.Add((-1,-1)) + elif ('init' in Table[hist][row] and + deltaMode and 'ref' in Table[hist][row]): + arr,indx = Table[hist][row]['val'] + delta = arr[indx] + arr,indx = Table[hist][row]['init'] + delta -= arr[indx] + if abs(delta) < 1e-9: delta = 0. + deltaS = f"\u0394 {delta:.4g} " + valrefsiz = wx.BoxSizer(wx.HORIZONTAL) + valrefsiz.Add(wx.StaticText(Panel,label=deltaS),0) + arr,indx = Table[hist][row]['ref'] + w = G2G.G2CheckBox(Panel,'',arr,indx) + valrefsiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL) + Sizer.Add(valrefsiz,0, + wx.EXPAND|wx.ALIGN_RIGHT) + elif 'init' in Table[hist][row] and deltaMode: + # does this ever happen? + arr,indx = Table[hist][row]['val'] + delta = arr[indx] + arr,indx = Table[hist][row]['init'] + delta -= arr[indx] + deltaS = f"\u0394 {delta:.4g}" + Sizer.Add(wx.StaticText(Panel,label=deltaS),0, + wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) elif 'val' in Table[hist][row] and 'ref' in Table[hist][row]: valrefsiz = wx.BoxSizer(wx.HORIZONTAL) arr,indx = Table[hist][row]['val'] - w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1)) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1)) valrefsiz.Add(w,0,WACV) if firstentry is None: firstentry = w arr,indx = Table[hist][row]['ref'] @@ -173,7 +227,7 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False): wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) elif 'val' in Table[hist][row]: arr,indx = Table[hist][row]['val'] - w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(75,-1),notBlank=False) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1),notBlank=False) Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) if firstentry is None: firstentry = w @@ -189,15 +243,19 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False): return firstentry def HistFrame(G2frame): - '''Give up on side-by-side scrolled panels - - Put everything in a single FlexGridSizer. + '''Give up on side-by-side scrolled panels. Put everything + in a single FlexGridSizer. ''' #--------------------------------------------------------------------- # generate a dict with values for each histogram Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() - tblType = 'Sample' - prmTable = getSampleVals(G2frame,Histograms) + if G2frame.GroupInfo['displayMode'].startswith('Sample'): + prmTable = getSampleVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): + prmTable = getInstVals(G2frame,Histograms) + else: + print('Unexpected', G2frame.GroupInfo['displayMode']) + return #debug# for hist in prmTable: printTable(phaseList[page],hist,prmTable[hist]) # see the dict # construct a list of row labels, attempting to keep the # order they appear in the original array @@ -214,28 +272,13 @@ def HistFrame(G2frame): rowLabels.insert(rowLabels.index(prevkey)+1,key) prevkey = key #======= Generate GUI =============================================== - G2frame.dataWindow.ClearData() - # layout the window - topSizer = G2frame.dataWindow.topBox - topParent = G2frame.dataWindow.topPanel - midPanel = G2frame.dataWindow - # label with shared portion of histogram name - topSizer.Add(wx.StaticText(topParent, - label=f'{tblType} parameters for group "{histLabels(G2frame)[0]}"'), - 0,WACV) - topSizer.Add((-1,-1),1,wx.EXPAND) - topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) - - panel = midPanel + panel = midPanel = G2frame.dataWindow mainSizer = wx.BoxSizer(wx.VERTICAL) G2G.HorizontalLine(mainSizer,panel) panel.SetSizer(mainSizer) Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() - # set menu bar contents - #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) - valSizer = wx.FlexGridSizer(0,len(prmTable)+1,3,10) mainSizer.Add(valSizer,1,wx.EXPAND) valSizer.Add(wx.StaticText(midPanel,label=' ')) @@ -243,21 +286,22 @@ def HistFrame(G2frame): valSizer.Add(wx.StaticText(midPanel, label=f"\u25A1 = {hist}"), 0,wx.ALIGN_CENTER) - firstentry = displayDataTable(rowLabels,prmTable,valSizer,midPanel,True) - G2frame.dataWindow.SetDataSize() + deltaMode = "\u0394" in G2frame.GroupInfo['displayMode'] + firstentry = displayDataTable(rowLabels,prmTable,valSizer,midPanel, + True,deltaMode) + #G2frame.dataWindow.SetDataSize() if firstentry is not None: # prevent scroll to show last entry wx.Window.SetFocus(firstentry) firstentry.SetInsertionPoint(0) # prevent selection of text in widget - wx.CallLater(100,G2frame.SendSizeEvent) 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.groupName). + 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.groupName + groupName = G2frame.GroupInfo['groupName'] groupDict = Controls.get('Groups',{}).get('groupDict',{}) if groupName not in groupDict: print(f'Unexpected: {groupName} not in groupDict') @@ -329,6 +373,79 @@ def getSampleVals(G2frame,Histograms): } parmDict[hist] = hpD return parmDict + +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 + parms = [] + parmDict = {} + # loop over histograms in group + for hist in groupDict[groupName]: + histdata = Histograms[hist] + insVal = Histograms[hist]['Instrument Parameters'][0] + hpD = {} + insType = insVal['Type'][1] + try: + hpD['Bank'] = { + 'str' : str(int(insVal['Bank'][1]))} + except: + pass + hpD['Hist type'] = { + 'str' : insType} + 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: + arr = insVal[key] + if fmt is None: + hpD[lbl] = { + 'init' : (arr,0), + 'val' : (arr,1), + 'ref' : (arr,2),} + else: + hpD[lbl] = { + 'str' : f'{arr[1]:{fmt}}'} + parmDict[hist] = hpD + return parmDict def HAPframe(G2frame): '''This creates two side-by-side scrolled panels, each containing @@ -403,7 +520,7 @@ def OnScroll(event): for hist in histLabels(G2frame)[1]: HAPSizer.Add(wx.StaticText(HAPScroll,label=f"\u25A1 = {hist}"), 0,wx.ALIGN_CENTER) - displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll) + firstentry = displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll) # get row sizes in data table HAPSizer.Layout() rowHeights = HAPSizer.GetRowHeights() @@ -425,26 +542,28 @@ def OnScroll(event): 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) - wx.CallLater(100,G2frame.SendSizeEvent) + 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() + #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 + # topSizer = G2frame.dataWindow.topBox + # topParent = G2frame.dataWindow.topPanel midPanel = G2frame.dataWindow mainSizer = wx.BoxSizer(wx.VERTICAL) #botSizer = G2frame.dataWindow.bottomBox #botParent = G2frame.dataWindow.bottomPanel # label with shared portion of histogram name - topSizer.Add(wx.StaticText(topParent, - label=f'HAP parameters for group "{histLabels(G2frame)[0]}"'), - 0,WACV) - topSizer.Add((-1,-1),1,wx.EXPAND) - topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) + # topSizer.Add(wx.StaticText(topParent, + # label=f'HAP parameters for group "{histLabels(G2frame)[0]}"'), + # 0,WACV) + # topSizer.Add((-1,-1),1,wx.EXPAND) + # topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) G2G.HorizontalLine(mainSizer,midPanel) midPanel.SetSizer(mainSizer) @@ -480,15 +599,15 @@ def OnScroll(event): # menu.Append(Id,page,'') # TabSelectionIdDict[Id] = page # G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) - G2frame.dataWindow.SetDataSize() 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.groupName). + 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') @@ -506,8 +625,8 @@ def getHAPvals(G2frame,phase): return {} Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) - groupName = G2frame.groupName groupDict = Controls.get('Groups',{}).get('groupDict',{}) + groupName = G2frame.GroupInfo['groupName'] if groupName not in groupDict: print(f'Unexpected: {groupName} not in groupDict') return diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index a0c93996..5c0d9be4 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -1629,7 +1629,7 @@ def onPartialConfig(event): groupName = None groupDict = {} if plotType == 'GROUP': - groupName = G2frame.groupName # set in GSASIIgroupGUI.UpdateGroup + 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: From 98f2a431dfcda8c2fb1b4fc5e19c8404f8a4bd34 Mon Sep 17 00:00:00 2001 From: BHT Date: Fri, 7 Nov 2025 20:13:29 -0600 Subject: [PATCH 13/30] Fix tickmarks in plot --- GSASII/GSASIIpwdplot.py | 153 +++++++++++++++------------------------- 1 file changed, 58 insertions(+), 95 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 5c0d9be4..4ad09442 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -223,7 +223,7 @@ 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 groupName is not None: # share X axis scale for Pattern Groups @@ -1607,6 +1607,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=True,pickradius=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=True,pickradius=3., + label='_FLT_'+phase,lw=0.5) #### beginning PlotPatterns execution ##################################### global exclLines,Page @@ -1807,7 +1859,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 = [] @@ -2123,21 +2176,13 @@ def onPartialConfig(event): gridspec_kw=GS_kw) Page.figure.subplots_adjust(left=5/100.,bottom=16/150., right=.99,top=1.-3/200.,hspace=0,wspace=0) - 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)) for i in range(nx): up,down = adjustDim(i,nx) 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(Page.phaseList)*5,102) + Plots[up].set_ylim(-len(RefTbl[i])*5,102) else: Plots[up].set_ylim(-1,102) @@ -2182,51 +2227,7 @@ def adjustDim(i,nx): clip_on=Clip_on,label=incCptn('obs')) Plot.plot(gX[i],scaleY(xye[3]),pwdrCol['Calc_color'],picker=False,label=incCptn('calc'),linewidth=1.5) Plot.plot(gX[i],scaleY(xye[4]),pwdrCol['Bkg_color'],picker=False,label=incCptn('bkg'),linewidth=1.5) #background - - 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 = [] - if phase in RefTbl[i]: - peaks = RefTbl[i][phase].get('RefList',[]) - super = RefTbl[i][phase].get('Super',False) - # else: - # peaks = Phases[phase].get('RefList',[]) - # super = Phases[phase].get('Super',False) - if not len(peaks): - continue - if super: - peak = np.array([[peak[5],peak[6]] for peak in peaks]) - else: - peak = np.array([[peak[4],peak[5]] for peak in peaks]) - pos = 2.5-len(Page.phaseList)*5 + pId*5 # tick positions hard-coded - 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 not Page.plotStyle.get('flTicks',False): # short tick-marks - Plot.plot( - xtick,pos * np.ones_like(peak), - '|',mew=w,ms=l, # picker=True,pickradius=3., - label=phase,color=plcolor) - else: # 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=True,pickradius=3., - label='_FLT_'+phase,lw=0.5) + 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) @@ -2894,45 +2895,7 @@ def adjustDim(i,nx): 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=True,pickradius=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=True,pickradius=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 From 99cdfa559089e5325024f9630c0ebe082ef4d16a Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 9 Nov 2025 19:56:46 -0600 Subject: [PATCH 14/30] fix bindings in plotting so that plotType etc is current --- GSASII/GSASIIpwdplot.py | 48 ++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 4ad09442..37657e91 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -340,8 +340,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: @@ -435,7 +435,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) @@ -648,7 +648,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') @@ -1680,7 +1680,7 @@ def drawTicks(Phases,phaseList,group=False): groupName = None groupDict = {} - if plotType == 'GROUP': + 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',{}) @@ -1705,8 +1705,8 @@ def drawTicks(Phases,phaseList,group=False): new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab('Powder Patterns','mpl',publish=publish) # 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 Page.prevPlotType != plottype: new = True + Page.prevPlotType = plottype if G2frame.ifSetLimitsMode and G2frame.GPXtree.GetItemText(G2frame.GPXtree.GetSelection()) == 'Limits': # note mode @@ -1721,7 +1721,7 @@ def drawTicks(Phases,phaseList,group=False): 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),}) @@ -1734,7 +1734,7 @@ def drawTicks(Phases,phaseList,group=False): except: G2frame.lastPlotType = None - if plotType == 'PWDR' or plotType == 'GROUP': + if plottype == 'PWDR' or plottype == 'GROUP': try: Parms,Parms2 = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, G2frame.PatternId, 'Instrument Parameters')) @@ -1792,7 +1792,7 @@ def drawTicks(Phases,phaseList,group=False): 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') @@ -1810,17 +1810,21 @@ def drawTicks(Phases,phaseList,group=False): 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 @@ -1888,7 +1892,7 @@ def drawTicks(Phases,phaseList,group=False): 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 @@ -2022,7 +2026,7 @@ def drawTicks(Phases,phaseList,group=False): 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 = [] From 47b48580bba3b56f347ad50d73922c26a8c3046e Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 9 Nov 2025 19:58:42 -0600 Subject: [PATCH 15/30] WIP: implement Limits page; start on copy & ref/all --- GSASII/GSASIIgroupGUI.py | 173 +++++++++++++++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 15 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 47e0c36e..4a6aa4b2 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -24,6 +24,7 @@ where `innerdict` can contain the following elements: * 'val' : (array, key) + * 'range' : (float,float) * 'ref' : (array, key) * 'str' : string * 'init' : float @@ -40,6 +41,10 @@ defined quantity, where array[key] provides r/w access to the parameter. + * 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 + * The 'ref' tuple provides a reference to the bool value, where array[key] provides r/w access to the refine flag for the labeled quantity @@ -110,7 +115,8 @@ def onDisplaySel(event): dsplType = wx.ComboBox(topParent,wx.ID_ANY, value=G2frame.GroupInfo['displayMode'], choices=['Hist/Phase','Sample','Instrument', - 'Instrument-\u0394'], + 'Instrument-\u0394', + 'Limits','Background'], style=wx.CB_READONLY|wx.CB_DROPDOWN) dsplType.Bind(wx.EVT_COMBOBOX, onDisplaySel) topSizer.Add(dsplType,0,WACV) @@ -132,7 +138,6 @@ def onDisplaySel(event): else: HistFrame(G2frame) if plot: G2pwpl.PlotPatterns(G2frame,plotType='GROUP') - #print('CallLater') G2frame.dataWindow.SetDataSize() #wx.CallLater(100,G2frame.SendSizeEvent) wx.CallAfter(G2frame.SendSizeEvent) @@ -165,14 +170,45 @@ def histLabels(G2frame): for h in groupDict[groupName]] return commonltrs,histlbls +def onRefineAll(event): + but = event.GetEventObject() + refList = but.refList + checkButList = but.checkButList + + print('before',[item[0][item[1]] for item in refList]) + if but.GetLabelText() == 'S': + setting = True + but.SetLabelText('C') + else: + setting = False + but.SetLabelText('S') + for c in checkButList: + c.SetValue(setting) + for item in refList: + item[0][item[1]] = setting + print('after ',[item[0][item[1]] for item in refList]) + def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): '''Displays the data table in `Table` in Scrolledpanel `Panel` with wx.FlexGridSizer `Sizer`. ''' firstentry = None + checkButList = {} for row in rowLabels: - if lblRow: # show the row labels, when not in a separate sizer + checkButList[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 Table: + if row not in Table[hist]: continue + if 'val' in Table[hist][row]: + valList.append(Table[hist][row]['val']) + if 'ref' in Table[hist][row]: + refList.append(Table[hist][row]['ref']) + arr = None for hist in Table: if row not in Table[hist]: continue @@ -185,24 +221,50 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): else: Sizer.Add( G2G.ValidatedTxtCtrl(Panel,arr,key,size=(125,-1)), - 0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + 0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + if len(refList) > 2: + refAll = wx.Button(Panel,label='S',style=wx.BU_EXACTFIT) + refAll.refList = refList + refAll.Bind(wx.EVT_BUTTON,onRefineAll) + refAll.checkButList = checkButList[row] + Sizer.Add(refAll,0,wx.ALIGN_CENTER_VERTICAL) + else: + Sizer.Add((-1,-1)) + if len(valList) > 2: + but = wx.Button(Panel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) + but.valList = valList + Sizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) + else: + Sizer.Add((-1,-1)) + for hist in Table: + minval = None + maxval = None # format the entry depending on what is defined if row not in Table[hist]: Sizer.Add((-1,-1)) - elif ('init' in Table[hist][row] and + continue + elif 'range' in Table[hist][row]: + minval, maxval = Table[hist][row]['range'] + if ('init' in Table[hist][row] and deltaMode and 'ref' in Table[hist][row]): arr,indx = Table[hist][row]['val'] delta = arr[indx] arr,indx = Table[hist][row]['init'] delta -= arr[indx] if abs(delta) < 1e-9: delta = 0. - deltaS = f"\u0394 {delta:.4g} " + if delta == 0: +# deltaS = "0.0 " + deltaS = "" + else: + deltaS = f"\u0394 {delta:.4g} " valrefsiz = wx.BoxSizer(wx.HORIZONTAL) valrefsiz.Add(wx.StaticText(Panel,label=deltaS),0) arr,indx = Table[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 Table[hist][row] and deltaMode: @@ -211,30 +273,38 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): delta = arr[indx] arr,indx = Table[hist][row]['init'] delta -= arr[indx] - deltaS = f"\u0394 {delta:.4g}" + if delta == 0: +# deltaS = "0.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 Table[hist][row] and 'ref' in Table[hist][row]: valrefsiz = wx.BoxSizer(wx.HORIZONTAL) arr,indx = Table[hist][row]['val'] - w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1)) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1), + xmin=minval,xmax=maxval) valrefsiz.Add(w,0,WACV) if firstentry is None: firstentry = w arr,indx = Table[hist][row]['ref'] - valrefsiz.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, - wx.ALIGN_CENTER_VERTICAL) + 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 Table[hist][row]: arr,indx = Table[hist][row]['val'] - w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1),notBlank=False) + w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1), + xmin=minval,xmax=maxval,notBlank=False) Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) if firstentry is None: firstentry = w elif 'ref' in Table[hist][row]: arr,indx = Table[hist][row]['ref'] - Sizer.Add(G2G.G2CheckBox(Panel,'',arr,indx),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) + w = G2G.G2CheckBox(Panel,'',arr,indx) + valrefsiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) + checkButList[row].append(w) elif 'str' in Table[hist][row]: Sizer.Add(wx.StaticText(Panel,label=Table[hist][row]['str']),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) @@ -253,6 +323,10 @@ def HistFrame(G2frame): prmTable = getSampleVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): prmTable = getInstVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Limits'): + prmTable = getLimitVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Background'): + prmTable = getBkgVals(G2frame,Histograms) else: print('Unexpected', G2frame.GroupInfo['displayMode']) return @@ -279,9 +353,11 @@ def HistFrame(G2frame): panel.SetSizer(mainSizer) Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() - valSizer = wx.FlexGridSizer(0,len(prmTable)+1,3,10) + valSizer = wx.FlexGridSizer(0,len(prmTable)+3,3,10) mainSizer.Add(valSizer,1,wx.EXPAND) valSizer.Add(wx.StaticText(midPanel,label=' ')) + valSizer.Add(wx.StaticText(midPanel,label=' Ref ')) + valSizer.Add(wx.StaticText(midPanel,label=' Copy ')) for hist in histLabels(G2frame)[1]: valSizer.Add(wx.StaticText(midPanel, label=f"\u25A1 = {hist}"), @@ -446,7 +522,74 @@ def getInstVals(G2frame,Histograms): 'str' : f'{arr[1]:{fmt}}'} parmDict[hist] = hpD return parmDict - + +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 + parms = [] + parmDict = {} + # loop over histograms in group + for hist in groupDict[groupName]: + histdata = Histograms[hist] + hpD = {} + #breakpoint() + hpD['Tmin'] = { + 'val' : (histdata['Limits'][1],0), + 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]] + } + hpD['Tmax'] = { + 'val' : (histdata['Limits'][1],1), + 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]] + } + for i,item in enumerate(histdata['Limits'][2:]): + hpD[f'excl Low {i+1}'] = { + 'val' : (item,0), + 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]]} + hpD[f'excl High {i+1}'] = { + 'val' : (item,1), + 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]]} + parmDict[hist] = hpD + return parmDict + + +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 + parms = [] + parmDict = {} + # loop over histograms in group + for hist in groupDict[groupName]: + histdata = Histograms[hist] + hpD = {} + hpD['Inst. name'] = { + 'val' : (histdata['Sample Parameters'],'InstrName')} + + #... + parmDict[hist] = hpD + return parmDict + + def HAPframe(G2frame): '''This creates two side-by-side scrolled panels, each containing a FlexGridSizer. From df1849dbf71baa45a87241a0ec03903dca9fe399 Mon Sep 17 00:00:00 2001 From: BHT Date: Mon, 10 Nov 2025 13:22:03 -0600 Subject: [PATCH 16/30] finish copy/ref button implementation --- GSASII/GSASIIgroupGUI.py | 93 +++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 4a6aa4b2..eb0dac5d 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -171,11 +171,17 @@ def histLabels(G2frame): return commonltrs,histlbls 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() refList = but.refList checkButList = but.checkButList - print('before',[item[0][item[1]] for item in refList]) + #print('before',[item[0][item[1]] for item in refList]) if but.GetLabelText() == 'S': setting = True but.SetLabelText('C') @@ -186,17 +192,36 @@ def onRefineAll(event): c.SetValue(setting) for item in refList: item[0][item[1]] = setting - print('after ',[item[0][item[1]] for item in refList]) + #print('after ',[item[0][item[1]] for item in refList]) + +def onSetAll(event): + '''Respond to the copy right button. Copies the first value to + all edit widgets + ''' + but = event.GetEventObject() + valList = but.valList + valEditList = but.valEditList + #print('before',[item[0][item[1]] for item in valList]) + firstVal = valList[0][0][valList[0][1]] + for c in valEditList: + c.ChangeValue(firstVal) + #print('after',[item[0][item[1]] for item in valList]) -def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): + +def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lblSizer=None,lblPanel=None): '''Displays the data table in `Table` 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 = {} 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? @@ -216,27 +241,28 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): arr,key = Table[hist][row]['rowlbl'] break if arr is None: - Sizer.Add(wx.StaticText(Panel,label=row),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + w = wx.StaticText(lblPanel,label=row) else: - Sizer.Add( - G2G.ValidatedTxtCtrl(Panel,arr,key,size=(125,-1)), - 0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + 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: - refAll = wx.Button(Panel,label='S',style=wx.BU_EXACTFIT) + refAll = wx.Button(lblPanel,label='S',style=wx.BU_EXACTFIT) refAll.refList = refList - refAll.Bind(wx.EVT_BUTTON,onRefineAll) refAll.checkButList = checkButList[row] - Sizer.Add(refAll,0,wx.ALIGN_CENTER_VERTICAL) + lblSizer.Add(refAll,0,wx.ALIGN_CENTER_VERTICAL) + refAll.Bind(wx.EVT_BUTTON,onRefineAll) else: - Sizer.Add((-1,-1)) + lblSizer.Add((-1,-1)) if len(valList) > 2: - but = wx.Button(Panel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) + but = wx.Button(lblPanel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) but.valList = valList - Sizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) + but.valEditList = valEditList[row] + lblSizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) + but.Bind(wx.EVT_BUTTON,onSetAll) else: - Sizer.Add((-1,-1)) + lblSizer.Add((-1,-1)) for hist in Table: minval = None @@ -285,6 +311,7 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): arr,indx = Table[hist][row]['val'] w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1), xmin=minval,xmax=maxval) + valEditList[row].append(w) valrefsiz.Add(w,0,WACV) if firstentry is None: firstentry = w arr,indx = Table[hist][row]['ref'] @@ -297,6 +324,7 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): arr,indx = Table[hist][row]['val'] w = G2G.ValidatedTxtCtrl(Panel,arr,indx,size=(80,-1), 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 @@ -310,7 +338,8 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False): wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) else: print('Should not happen',Table[hist][row],hist,row) - return firstentry + return firstentry,lblDict + def HistFrame(G2frame): '''Give up on side-by-side scrolled panels. Put everything @@ -363,8 +392,9 @@ def HistFrame(G2frame): label=f"\u25A1 = {hist}"), 0,wx.ALIGN_CENTER) deltaMode = "\u0394" in G2frame.GroupInfo['displayMode'] - firstentry = displayDataTable(rowLabels,prmTable,valSizer,midPanel, - True,deltaMode) + firstentry,lblDict = displayDataTable(rowLabels,prmTable,valSizer,midPanel, + lblRow=True, + deltaMode=deltaMode) #G2frame.dataWindow.SetDataSize() if firstentry is not None: # prevent scroll to show last entry wx.Window.SetFocus(firstentry) @@ -645,7 +675,7 @@ def OnScroll(event): lblScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) hpad = 3 # space between rows - lblSizer = wx.FlexGridSizer(0,1,hpad,10) + lblSizer = wx.FlexGridSizer(0,3,hpad,2) lblScroll.SetSizer(lblSizer) bigSizer.Add(lblScroll,0,wx.EXPAND) @@ -663,18 +693,31 @@ def OnScroll(event): for hist in histLabels(G2frame)[1]: HAPSizer.Add(wx.StaticText(HAPScroll,label=f"\u25A1 = {hist}"), 0,wx.ALIGN_CENTER) - firstentry = displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll) + w0 = wx.StaticText(lblScroll,label=' ') + lblSizer.Add(w0) + lblSizer.Add(wx.StaticText(lblScroll,label=' Ref ')) + lblSizer.Add(wx.StaticText(lblScroll,label=' Copy ')) + firstentry,lblDict = displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll, + lblRow=True,lblSizer=lblSizer,lblPanel=lblScroll) # get row sizes in data table HAPSizer.Layout() rowHeights = HAPSizer.GetRowHeights() - # match rose sizes in Labels + # set row sizes in Labels # (must be done after HAPSizer row heights are defined) s = wx.Size(-1,rowHeights[0]) - lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=s)) + w0.SetMinSize(s) + # for i,row in enumerate(rowLabels): + # s = wx.Size(-1,rowHeights[i+1]) + # w = wx.StaticText(lblScroll,label=row,size=s) + # lblSizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + # lblDict = {} + # for i,row in enumerate(rowLabels): + # w = wx.StaticText(lblScroll,label=row) + # lblDict[row] = w + # lblSizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) for i,row in enumerate(rowLabels): s = wx.Size(-1,rowHeights[i+1]) - lblSizer.Add(wx.StaticText(lblScroll,label=row,size=s),0, - wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + lblDict[row].SetMinSize(s) # Fit the scrolled windows to their content lblSizer.Layout() xLbl,_ = lblSizer.GetMinSize() From 6c44c16d88a92436253ea44e1eefc85b8b309eec Mon Sep 17 00:00:00 2001 From: BHT Date: Sat, 29 Nov 2025 16:31:41 -0600 Subject: [PATCH 17/30] allow limits to be set in Q & d; reset drags when mouse is out of plot; fix blit so dragged objects move over entire plot --- GSASII/GSASIIpwdplot.py | 129 ++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 37657e91..2469d953 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -355,8 +355,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: @@ -372,17 +375,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): @@ -406,7 +416,7 @@ 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 or plottype.startswith('GROUP'): Page.plotStyle['qPlot'] = not Page.plotStyle['qPlot'] @@ -422,9 +432,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 or plottype.startswith('GROUP') - ) 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 @@ -492,7 +501,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 @@ -500,7 +509,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: @@ -508,7 +517,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 @@ -708,9 +717,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]) @@ -719,28 +734,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()) @@ -759,14 +786,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: @@ -785,12 +818,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() @@ -798,8 +838,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]] @@ -944,20 +984,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) @@ -1905,8 +1949,6 @@ def drawTicks(Phases,phaseList,group=False): 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 @@ -1940,6 +1982,7 @@ def drawTicks(Phases,phaseList,group=False): 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: @@ -2088,9 +2131,9 @@ def drawTicks(Phases,phaseList,group=False): 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.' @@ -2303,9 +2346,9 @@ def drawTicks(Phases,phaseList,group=False): 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]) @@ -2355,9 +2398,9 @@ def drawTicks(Phases,phaseList,group=False): # 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) @@ -2449,9 +2492,9 @@ def drawTicks(Phases,phaseList,group=False): 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=True,pickradius=3.)) From 26fd2341c6fc8ddaeec6b17f35fc075ab76f6bd8 Mon Sep 17 00:00:00 2001 From: BHT Date: Mon, 1 Dec 2025 16:51:56 -0600 Subject: [PATCH 18/30] move copy button to between 1st & 2nd column, but remove from limits page; set C/S by refine flags; improve float formatting & out-of-range values --- GSASII/GSASIIfiles.py | 6 ++- GSASII/GSASIIgroupGUI.py | 85 +++++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 28 deletions(-) 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 index eb0dac5d..04b93c7e 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -208,7 +208,8 @@ def onSetAll(event): #print('after',[item[0][item[1]] for item in valList]) -def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lblSizer=None,lblPanel=None): +def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, + lblSizer=None,lblPanel=None,CopyCtrl=True): '''Displays the data table in `Table` in Scrolledpanel `Panel` with wx.FlexGridSizer `Sizer`. ''' @@ -240,31 +241,41 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lb if 'rowlbl' in Table[hist][row]: arr,key = Table[hist][row]['rowlbl'] break - if arr is None: + if arr is None: # text row labels w = wx.StaticText(lblPanel,label=row) - else: + 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: - refAll = wx.Button(lblPanel,label='S',style=wx.BU_EXACTFIT) + lbl = 'S' + if all([l[i] for l,i in refList]): lbl = 'C' + refAll = wx.Button(lblPanel,label=lbl,style=wx.BU_EXACTFIT) refAll.refList = refList refAll.checkButList = checkButList[row] lblSizer.Add(refAll,0,wx.ALIGN_CENTER_VERTICAL) refAll.Bind(wx.EVT_BUTTON,onRefineAll) else: lblSizer.Add((-1,-1)) - if len(valList) > 2: - but = wx.Button(lblPanel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) + # if len(valList) > 2: + # but = wx.Button(lblPanel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) + # but.valList = valList + # but.valEditList = valEditList[row] + # lblSizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) + # but.Bind(wx.EVT_BUTTON,onSetAll) + # else: + # lblSizer.Add((-1,-1)) + + for i,hist in enumerate(Table): + 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.valList = valList - but.valEditList = valEditList[row] - lblSizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) + but.valEditList = valEditList[row] + Sizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) but.Bind(wx.EVT_BUTTON,onSetAll) - else: - lblSizer.Add((-1,-1)) - - for hist in Table: + elif i == 1 and CopyCtrl: + Sizer.Add((-1,-1)) minval = None maxval = None # format the entry depending on what is defined @@ -279,9 +290,8 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lb delta = arr[indx] arr,indx = Table[hist][row]['init'] delta -= arr[indx] - if abs(delta) < 1e-9: delta = 0. + if abs(delta) < 9e-6: delta = 0. if delta == 0: -# deltaS = "0.0 " deltaS = "" else: deltaS = f"\u0394 {delta:.4g} " @@ -300,7 +310,6 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lb arr,indx = Table[hist][row]['init'] delta -= arr[indx] if delta == 0: -# deltaS = "0.0 " deltaS = "" else: deltaS = f"\u0394 {delta:.4g} " @@ -310,6 +319,7 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lb valrefsiz = wx.BoxSizer(wx.HORIZONTAL) arr,indx = Table[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) @@ -322,7 +332,10 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False,lb wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) elif 'val' in Table[hist][row]: arr,indx = Table[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) @@ -348,11 +361,13 @@ def HistFrame(G2frame): #--------------------------------------------------------------------- # generate a dict with values for each histogram Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + CopyCtrl = True if G2frame.GroupInfo['displayMode'].startswith('Sample'): prmTable = getSampleVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): prmTable = getInstVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Limits'): + CopyCtrl = False prmTable = getLimitVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Background'): prmTable = getBkgVals(G2frame,Histograms) @@ -381,20 +396,26 @@ def HistFrame(G2frame): G2G.HorizontalLine(mainSizer,panel) panel.SetSizer(mainSizer) Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() - - valSizer = wx.FlexGridSizer(0,len(prmTable)+3,3,10) + deltaMode = "\u0394" in G2frame.GroupInfo['displayMode'] + n = 2 + if CopyCtrl: n += 1 # add column for copy + valSizer = wx.FlexGridSizer(0,len(prmTable)+n,3,10) mainSizer.Add(valSizer,1,wx.EXPAND) valSizer.Add(wx.StaticText(midPanel,label=' ')) valSizer.Add(wx.StaticText(midPanel,label=' Ref ')) - valSizer.Add(wx.StaticText(midPanel,label=' Copy ')) - for hist in histLabels(G2frame)[1]: + #valSizer.Add(wx.StaticText(midPanel,label=' Copy ')) + 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) - deltaMode = "\u0394" in G2frame.GroupInfo['displayMode'] firstentry,lblDict = displayDataTable(rowLabels,prmTable,valSizer,midPanel, lblRow=True, - deltaMode=deltaMode) + deltaMode=deltaMode,CopyCtrl=CopyCtrl) #G2frame.dataWindow.SetDataSize() if firstentry is not None: # prevent scroll to show last entry wx.Window.SetFocus(firstentry) @@ -469,7 +490,7 @@ def getSampleVals(G2frame,Histograms): 'val' : (histdata['Sample Parameters'],key)} elif type(fmt) is str: hpD[lbl] = { - 'str' : f'{histdata['Sample Parameters'][key]:{fmt}}'} + 'str' : f"{histdata['Sample Parameters'][key]:{fmt}}"} for key in ('FreePrm1','FreePrm2','FreePrm3'): lbl = Controls[key] @@ -566,7 +587,6 @@ def getLimitVals(G2frame,Histograms): print(f'Unexpected: {groupName} not in groupDict') return # parameters to include in table - parms = [] parmDict = {} # loop over histograms in group for hist in groupDict[groupName]: @@ -589,6 +609,12 @@ def getLimitVals(G2frame,Histograms): 'val' : (item,1), 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]]} parmDict[hist] = hpD + # for i in parmDict: + # print(i) + # for j in parmDict[i]: + # print('\t',j) + # for k in parmDict[i][j]: + # print('\t\t',k,parmDict[i][j][k]) return parmDict @@ -675,14 +701,16 @@ def OnScroll(event): lblScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) hpad = 3 # space between rows - lblSizer = wx.FlexGridSizer(0,3,hpad,2) + #lblSizer = wx.FlexGridSizer(0,3,hpad,2) + 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(HAPtable),hpad,10) + #HAPSizer = wx.FlexGridSizer(0,len(HAPtable),hpad,10) + HAPSizer = wx.FlexGridSizer(0,len(HAPtable)+1,hpad,10) HAPScroll.SetSizer(HAPSizer) bigSizer.Add(HAPScroll,1,wx.EXPAND) @@ -690,13 +718,16 @@ def OnScroll(event): lblScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) HAPScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) # label columns with unique part of histogram names - for hist in histLabels(G2frame)[1]: + 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 ')) - lblSizer.Add(wx.StaticText(lblScroll,label=' Copy ')) + #lblSizer.Add(wx.StaticText(lblScroll,label=' Copy ')) firstentry,lblDict = displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll, lblRow=True,lblSizer=lblSizer,lblPanel=lblScroll) # get row sizes in data table From 1a2742a5ec76ea525d447e168709942950466d5d Mon Sep 17 00:00:00 2001 From: BHT Date: Fri, 12 Dec 2025 18:28:24 -0600 Subject: [PATCH 19/30] Add background terms --- GSASII/GSASIIgroupGUI.py | 56 +++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 04b93c7e..adfc5e26 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -207,7 +207,6 @@ def onSetAll(event): c.ChangeValue(firstVal) #print('after',[item[0][item[1]] for item in valList]) - def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, lblSizer=None,lblPanel=None,CopyCtrl=True): '''Displays the data table in `Table` in Scrolledpanel `Panel` @@ -344,7 +343,7 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, elif 'ref' in Table[hist][row]: arr,indx = Table[hist][row]['ref'] w = G2G.G2CheckBox(Panel,'',arr,indx) - valrefsiz.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) + Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER) checkButList[row].append(w) elif 'str' in Table[hist][row]: Sizer.Add(wx.StaticText(Panel,label=Table[hist][row]['str']),0, @@ -371,6 +370,7 @@ def HistFrame(G2frame): prmTable = getLimitVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Background'): prmTable = getBkgVals(G2frame,Histograms) + CopyCtrl = False else: print('Unexpected', G2frame.GroupInfo['displayMode']) return @@ -634,14 +634,56 @@ def getBkgVals(G2frame,Histograms): # parameters to include in table parms = [] parmDict = {} - # loop over histograms in group + # loop over histograms in group for hist in groupDict[groupName]: histdata = Histograms[hist] hpD = {} - hpD['Inst. name'] = { - 'val' : (histdata['Sample Parameters'],'InstrName')} - - #... + hpD['Function'] = { + 'str' : histdata['Background'][0][0]} + hpD['ref flag'] = { + 'ref' : (histdata['Background'][0],1)} + hpD['# Bkg terms'] = { + 'str' : str(int(histdata['Background'][0][2]))} + hpD['# Debye terms'] = { + 'str' : str(int(histdata['Background'][1]['nDebye']))} + for i,term in enumerate(histdata['Background'][1]['debyeTerms']): + hpD[f'A #{i+1}'] = { + 'val' : (histdata['Background'][1]['debyeTerms'][i],0), + 'ref' : (histdata['Background'][1]['debyeTerms'][i],1), + } + hpD[f'R #{i+1}'] = { + 'val' : (histdata['Background'][1]['debyeTerms'][i],2), + 'ref' : (histdata['Background'][1]['debyeTerms'][i],3), + } + hpD[f'U #{i+1}'] = { + 'val' : (histdata['Background'][1]['debyeTerms'][i],4), + 'ref' : (histdata['Background'][1]['debyeTerms'][i],5), + } + hpD['# Bkg Peaks'] = { + 'str' : str(int(histdata['Background'][1]['nPeaks']))} + for i,term in enumerate(histdata['Background'][1]['peaksList']): + hpD[f'pos #{i+1}'] = { + 'val' : (histdata['Background'][1]['peaksList'][i],0), + 'ref' : (histdata['Background'][1]['peaksList'][i],1), + } + hpD[f'int #{i+1}'] = { + 'val' : (histdata['Background'][1]['peaksList'][i],2), + 'ref' : (histdata['Background'][1]['peaksList'][i],3), + } + hpD[f'sig #{i+1}'] = { + 'val' : (histdata['Background'][1]['peaksList'][i],4), + 'ref' : (histdata['Background'][1]['peaksList'][i],5), + } + hpD[f'gam #{i+1}'] = { + 'val' : (histdata['Background'][1]['peaksList'][i],6), + 'ref' : (histdata['Background'][1]['peaksList'][i],7), + } + if histdata['Background'][1]['background PWDR'][0]: + val = 'yes' + else: + val = 'no' + hpD['Fixed bkg file'] = { + 'str' : val} parmDict[hist] = hpD return parmDict From c0ae3102a8631fb0ab1cfe7731e7d8fbb60deb17 Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 14 Dec 2025 15:08:40 -0600 Subject: [PATCH 20/30] implement limits on grouped plots; rework arrows & publish plot arrows; limit hists in groups; fix selection after importing lots of histograms --- GSASII/GSASIIdataGUI.py | 23 ++++++++++----- GSASII/GSASIIplot.py | 63 ++++++++++++++++++++++++++++++++--------- GSASII/GSASIIpwdplot.py | 49 +++++++++++++++++++++----------- 3 files changed, 98 insertions(+), 37 deletions(-) diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index 8c709347..975ef98c 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -1405,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 @@ -1998,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 @@ -7981,6 +7982,8 @@ def SearchGroups(event): groupCount[k] = len(groupDict[k]) msg1 = f'With template {srchStr!r} found ' + + buttons = [] if min(groupCount.values()) == max(groupCount.values()): msg1 += f'{len(groupDict)} groups with {min(groupCount.values())} histograms each' else: @@ -7989,12 +7992,14 @@ def SearchGroups(event): if noMatchCount: msg1 += f"\n\nNote that {noMatchCount} PWDR histograms were not included in any groups" #G2G.G2MessageBox(G2frame,msg1,'Grouping result') + # place a sanity check limit on the number of histograms in a group + if max(groupCount.values()) < 150: + buttons += [('OK', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK))] + else: + msg1 += '\n\nThis exceeds the maximum group length of 150 histograms' + buttons += [('try again', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_CANCEL))] res = G2G.ShowScrolledInfo(G2frame,msg1,header='Grouping result', - buttonlist=[ - ('OK', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK)), - ('try again', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_CANCEL)) - ], - height=150) + buttonlist=buttons,height=150) if res == wx.ID_OK: repeat = False @@ -9216,7 +9221,11 @@ def OnShowShift(event): G2pdG.UpdateReflectionGrid(G2frame,data,HKLF=True,Name=name) G2frame.dataWindow.HideShow.Enable(True) elif G2frame.GPXtree.GetItemText(parentID).startswith('Groups/'): - # groupDict is defined. + # 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}', diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index 096f0a3c..121dab25 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,21 @@ 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']: + 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 +758,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 487be2f7..4d6e2364 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -1747,7 +1747,11 @@ def drawTicks(Phases,phaseList,group=False): 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 @@ -1965,6 +1969,7 @@ def drawTicks(Phases,phaseList,group=False): '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', @@ -2022,7 +2027,7 @@ def drawTicks(Phases,phaseList,group=False): 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) @@ -2151,6 +2156,11 @@ def drawTicks(Phases,phaseList,group=False): 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)) + Page.Choice = [' key press', 'f: toggle full-length ticks', 'g: toggle grid', @@ -2170,17 +2180,21 @@ def drawTicks(Phases,phaseList,group=False): DZmin = 0 RefTbl = {} histlbl = {} - nx = len(groupDict[groupName]) # find portion of hist name that is the same and different - h0 = groupDict[groupName][0] - msk = [True] * len(h0) + 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,msk)] + msk = [m & (h0i == hi) for h0i,hi,m in zip(h0,h.ljust(l),msk)] + if not hasattr(Page,'groupMax'): Page.groupMax = 10 + if not hasattr(Page,'groupOff'): Page.groupOff = 0 + groupPlotList = groupDict[groupName][Page.groupOff:][: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(groupDict[groupName]): + 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( @@ -2215,21 +2229,21 @@ def drawTicks(Phases,phaseList,group=False): 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(nx)] + 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,nx,sharey='row',sharex=True, + Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True, gridspec_kw=GS_kw) else: - Plots = Page.figure.subplots(2,nx,sharey='row',sharex='col', + 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(nx): - up,down = adjustDim(i,nx) + 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) @@ -2239,10 +2253,10 @@ def drawTicks(Phases,phaseList,group=False): Plots[up].set_ylim(-1,102) # pretty up the tick labels - up,down = adjustDim(0,nx) + 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 nx > 1: + 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 @@ -2254,8 +2268,8 @@ def drawTicks(Phases,phaseList,group=False): 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(groupDict[groupName]): - up,down = adjustDim(i,nx) + for i,h in enumerate(groupPlotList): + up,down = adjustDim(i,Page.groupN) Plot = Plots[up] Plot1 = Plots[down] if Page.plotStyle['qPlot']: @@ -2302,6 +2316,8 @@ def drawTicks(Phases,phaseList,group=False): 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): @@ -2465,7 +2481,6 @@ def drawTicks(Phases,phaseList,group=False): 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 From 8c32385cd7299ea8fe000d594256758e11224ea5 Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 14 Dec 2025 15:20:26 -0600 Subject: [PATCH 21/30] wrap group plot if too few entries --- GSASII/GSASIIplot.py | 1 + GSASII/GSASIIpwdplot.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index 121dab25..856fdbe0 100644 --- a/GSASII/GSASIIplot.py +++ b/GSASII/GSASIIplot.py @@ -695,6 +695,7 @@ def OnArrow(self,event): 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 diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 4d6e2364..af9777cf 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -2186,9 +2186,11 @@ def drawTicks(Phases,phaseList,group=False): 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 = 10 + 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:][:Page.groupMax] + 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)]) From 6a9fb4caded597934da6c182b3cf7ab2eae434ad Mon Sep 17 00:00:00 2001 From: BHT Date: Wed, 17 Dec 2025 15:23:10 -0600 Subject: [PATCH 22/30] add menu bar for groups; fix bug for 2+ digit bank number --- GSASII/GSASIIdataGUI.py | 16 ++++- GSASII/GSASIIgroupGUI.py | 125 +++++++++++++++++++++++++-------------- 2 files changed, 96 insertions(+), 45 deletions(-) diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index 975ef98c..b5bc8112 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -7508,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): @@ -7945,7 +7959,7 @@ def SearchGroups(event): if hist.startswith('PWDR '): break else: - G2G.G2MessageBox(G2frame,'No PWDR histograms found to group', + G2G.G2MessageBox(G2frame,'No used PWDR histograms found to group. Histograms must be assigned phase(s).', 'Cannot group') return repeat = True diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index adfc5e26..21d146bb 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -102,12 +102,77 @@ def onDisplaySel(event): G2frame.GroupInfo['displayMode'] = dsplType.GetValue() wx.CallAfter(UpdateGroup,G2frame,item,False) #UpdateGroup(G2frame,item) + def OnCopyAll(event): + G2G.G2MessageBox(G2frame, + f'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() + if G2frame.GroupInfo['displayMode'].startswith('Sample'): + prmTable = getSampleVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): + prmTable = getInstVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Limits'): + CopyCtrl = False + prmTable = getLimitVals(G2frame,Histograms) + elif G2frame.GroupInfo['displayMode'].startswith('Background'): + prmTable = getBkgVals(G2frame,Histograms) + CopyCtrl = False + 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) + 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 @@ -126,13 +191,6 @@ def onDisplaySel(event): topSizer.Add((-1,-1),1,wx.EXPAND) topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) - # parent = G2frame.dataWindow.topPanel - # topSizer.Add(wx.StaticText(parent,label=' Group edit goes here someday'),0,WACV) - # topSizer.Add((-1,-1),1,wx.EXPAND) - # topSizer.Add(G2G.HelpButton(parent,helpIndex=G2frame.dataWindow.helpKey)) - # G2G.HorizontalLine(G2frame.dataWindow.GetSizer(),G2frame.dataWindow) - # #G2frame.dataWindow.GetSizer().Add(text,1,wx.ALL|wx.EXPAND) - G2frame.GroupInfo['groupName'] = G2frame.GPXtree.GetItemText(item) if G2frame.GroupInfo['displayMode'].startswith('Hist'): HAPframe(G2frame) else: @@ -159,10 +217,11 @@ def histLabels(G2frame): G2frame,G2frame.root, 'Controls')) groupName = G2frame.GroupInfo['groupName'] groupDict = Controls.get('Groups',{}).get('groupDict',{}) - h0 = groupDict[groupName][0] - msk = [True] * len(h0) + 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,msk)] + 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 @@ -436,18 +495,29 @@ def getSampleVals(G2frame,Histograms): # parameters to include in table parms = [] parmDict = {} + #indexDict = {} # loop over histograms in group for hist in groupDict[groupName]: + #indexDict[hist] = {} histdata = Histograms[hist] hpD = {} hpD['Inst. name'] = { 'val' : (histdata['Sample Parameters'],'InstrName')} + #indexDict[hist]['Inst. name'] = { + # 'val' : (hist,'Sample Parameters','InstrName')} hpD['Diff type'] = { 'str' : histdata['Sample Parameters']['Type']} + #indexDict[hist]['Diff type'] = { + # 'str' : (hist,'Sample Parameters','Type')} arr = histdata['Sample Parameters']['Scale'] hpD['Scale factor'] = { 'val' : (arr,0), 'ref' : (arr,1),} + #indexDict[hist]['Scale factor'] = { + # 'val' : (hist,'Sample Parameters','Scale',0), + # 'ref' : (hist,1),} + #breakpoint() + #return # make a list of parameters to show histType = histdata['Instrument Parameters'][0]['Type'][0] dataType = histdata['Sample Parameters']['Type'] @@ -843,21 +913,6 @@ def OnScroll(event): HAPBook.AddPage(HAPtabs[-1],phaseName) HAPBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, selectPhase) - # code to set menu bar contents - #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) - # fill the 'Select tab' menu - # mid = G2frame.dataWindow.DataMenu.FindMenu('Select tab') - # menu = G2frame.dataWindow.DataMenu.GetMenu(mid) - # items = menu.GetMenuItems() - # for item in items: - # menu.Remove(item) - # if len(phaseList) == 0: return - # for i,page in enumerate(phaseList): - # Id = wx.NewId() - # if menu.FindItem(page) >= 0: continue # is tab already in menu? - # menu.Append(Id,page,'') - # TabSelectionIdDict[Id] = page - # G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) page = 0 HAPBook.SetSelection(page) selectPhase(None) @@ -1113,24 +1168,6 @@ def printTable(phase,hist,parmDict): # panel.SetSizer(mainSizer) # Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() -# # code to set menu bar contents -# #G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.DataMenu) -# # fill the 'Select tab' menu -# # mid = G2frame.dataWindow.DataMenu.FindMenu('Select tab') -# # menu = G2frame.dataWindow.DataMenu.GetMenu(mid) -# # items = menu.GetMenuItems() -# # for item in items: -# # menu.Remove(item) -# # if len(phaseList) == 0: return -# # for i,page in enumerate(phaseList): -# # Id = wx.NewId() -# # if menu.FindItem(page) >= 0: continue # is tab already in menu? -# # menu.Append(Id,page,'') -# # TabSelectionIdDict[Id] = page -# # G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id) -# #page = 0 -# #HAPBook.SetSelection(page) -# #selectPhase(None) # bigSizer = wx.BoxSizer(wx.HORIZONTAL) # mainSizer.Add(bigSizer,1,wx.EXPAND) From 784e3be844ebef74bec4570e2e2f9b302aabcf1e Mon Sep 17 00:00:00 2001 From: BHT Date: Mon, 22 Dec 2025 19:36:22 -0600 Subject: [PATCH 23/30] refactor value reference in prep for copy by group --- GSASII/GSASIIdataGUI.py | 62 +-- GSASII/GSASIIgroupGUI.py | 986 +++++++++++++++++---------------------- 2 files changed, 439 insertions(+), 609 deletions(-) diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index b5bc8112..c6facee6 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -7952,8 +7952,6 @@ def SearchGroups(event): is judged by a common string that matches a template supplied by the user ''' - ans = G2frame.OnFileSave(None) - if not ans: return Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() for hist in Histograms: if hist.startswith('PWDR '): @@ -7962,63 +7960,9 @@ def SearchGroups(event): G2G.G2MessageBox(G2frame,'No used PWDR histograms found to group. Histograms must be assigned phase(s).', 'Cannot group') return - 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 template {srchStr!r} found ' - - buttons = [] - if min(groupCount.values()) == max(groupCount.values()): - msg1 += f'{len(groupDict)} groups with {min(groupCount.values())} histograms each' - else: - msg1 += (f'{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" - #G2G.G2MessageBox(G2frame,msg1,'Grouping result') - # place a sanity check limit on the number of histograms in a group - if max(groupCount.values()) < 150: - buttons += [('OK', lambda event: event.GetEventObject().GetParent().EndModal(wx.ID_OK))] - else: - msg1 += '\n\nThis exceeds the maximum group length of 150 histograms' - 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 - - data['Groups'] = {'groupDict':groupDict,'notGrouped':noMatchCount, - 'template':srchStr} + 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 diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index 21d146bb..f3522fa7 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -12,52 +12,58 @@ * Controls['Groups']['template'] the string used to set the grouping -See SearchGroups in :func:`GSASIIdataGUI.UpdateControls`. +** 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: -** Parameter Data Table ** -Prior to display in the GUI, parameters are organized in a dict where each -dict entry has contents of form: + * dict['__dataSource'] : SourceArray - * 'label' : `innerdict` + * dict[histogram]['label'] : `innerdict` -where `innerdict` can contain the following elements: +where `label` is the text shown on the row label and `innerdict` can contain +one or more of the following elements: - * 'val' : (array, key) - * 'range' : (float,float) - * 'ref' : (array, key) - * 'str' : string - * 'init' : float + * '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 'str' value is something that cannot be edited; If 'str' is - present, it will be the only entry in `innerdict`. It is used for - a parameter value that is typically computed or must be edited in - the histogram section. - * The 'val' tuple provides a reference to the float value for the - defined quantity, where array[key] provides r/w access to the - parameter. - - * 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 + 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 - array[key] provides r/w access to the refine flag for the - labeled quantity + 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. + 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 cuurent value for the parameter as + where there is both a current value for the parameter as well as an initial value (usually read from the instrument - parameters file whne the histogram is read. If 'init' is + 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. @@ -65,11 +71,16 @@ 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 re # import copy # import platform # import pickle @@ -83,7 +94,7 @@ # from . import GSASIIpath from . import GSASIIdataGUI as G2gd # from . import GSASIIobj as G2obj -from . import GSASIIpwdGUI as G2pdG +# from . import GSASIIpwdGUI as G2pdG # from . import GSASIIimgGUI as G2imG # from . import GSASIIElem as G2el # from . import GSASIIfiles as G2fil @@ -97,6 +108,75 @@ 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() @@ -104,7 +184,7 @@ def onDisplaySel(event): #UpdateGroup(G2frame,item) def OnCopyAll(event): G2G.G2MessageBox(G2frame, - f'Sorry, not fully implemented yet', + 'Sorry, not fully implemented yet', 'In progress') return Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls')) @@ -133,28 +213,27 @@ def OnCopyAll(event): selList = matchGrps if len(selList) == 0: return Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() + prmArray = None if G2frame.GroupInfo['displayMode'].startswith('Sample'): - prmTable = getSampleVals(G2frame,Histograms) + prmArray = getSampleVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): - prmTable = getInstVals(G2frame,Histograms) + prmArray = getInstVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Limits'): - CopyCtrl = False - prmTable = getLimitVals(G2frame,Histograms) + prmArray = getLimitVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Background'): - prmTable = getBkgVals(G2frame,Histograms) - CopyCtrl = False + 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() + # 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') @@ -229,6 +308,20 @@ def histLabels(G2frame): 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 @@ -237,10 +330,9 @@ def onRefineAll(event): set). ''' but = event.GetEventObject() - refList = but.refList + dataSource = but.refDict['dataSource'] checkButList = but.checkButList - #print('before',[item[0][item[1]] for item in refList]) if but.GetLabelText() == 'S': setting = True but.SetLabelText('C') @@ -249,26 +341,26 @@ def onRefineAll(event): but.SetLabelText('S') for c in checkButList: c.SetValue(setting) - for item in refList: - item[0][item[1]] = setting - #print('after ',[item[0][item[1]] for item in refList]) + 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() - valList = but.valList - valEditList = but.valEditList - #print('before',[item[0][item[1]] for item in valList]) - firstVal = valList[0][0][valList[0][1]] + 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) - #print('after',[item[0][item[1]] for item in valList]) -def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, +def displayDataArray(rowLabels,DataArray,Sizer,Panel,lblRow=False,deltaMode=False, lblSizer=None,lblPanel=None,CopyCtrl=True): - '''Displays the data table in `Table` in Scrolledpanel `Panel` + '''Displays the data table in `DataArray` in Scrolledpanel `Panel` with wx.FlexGridSizer `Sizer`. ''' firstentry = None @@ -278,6 +370,7 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, checkButList = {} valEditList = {} lblDict = {} + dataSource = DataArray['_dataSource'] for row in rowLabels: checkButList[row] = [] valEditList[row] = [] @@ -286,18 +379,20 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, # is a copy across and/or a refine all button needed? refList = [] valList = [] - for hist in Table: - if row not in Table[hist]: continue - if 'val' in Table[hist][row]: - valList.append(Table[hist][row]['val']) - if 'ref' in Table[hist][row]: - refList.append(Table[hist][row]['ref']) + 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 - for hist in Table: - if row not in Table[hist]: continue - if 'rowlbl' in Table[hist][row]: - arr,key = Table[hist][row]['rowlbl'] + 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) @@ -308,28 +403,25 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, if len(refList) > 2: lbl = 'S' - if all([l[i] for l,i in refList]): lbl = 'C' + if all([indexArrayVal(dataSource,hist,i) for i in refList]): lbl = 'C' refAll = wx.Button(lblPanel,label=lbl,style=wx.BU_EXACTFIT) - refAll.refList = refList + 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)) - # if len(valList) > 2: - # but = wx.Button(lblPanel,wx.ID_ANY,'\u2192',style=wx.BU_EXACTFIT) - # but.valList = valList - # but.valEditList = valEditList[row] - # lblSizer.Add(but,0,wx.ALIGN_CENTER_VERTICAL) - # but.Bind(wx.EVT_BUTTON,onSetAll) - # else: - # lblSizer.Add((-1,-1)) - - for i,hist in enumerate(Table): + + 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.valList = valList - but.valEditList = valEditList[row] + 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: @@ -337,16 +429,16 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, minval = None maxval = None # format the entry depending on what is defined - if row not in Table[hist]: + if row not in DataArray[hist]: Sizer.Add((-1,-1)) continue - elif 'range' in Table[hist][row]: - minval, maxval = Table[hist][row]['range'] - if ('init' in Table[hist][row] and - deltaMode and 'ref' in Table[hist][row]): - arr,indx = Table[hist][row]['val'] + 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 = Table[hist][row]['init'] + arr,indx = DataArray[hist][row]['init'] delta -= arr[indx] if abs(delta) < 9e-6: delta = 0. if delta == 0: @@ -355,17 +447,17 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, deltaS = f"\u0394 {delta:.4g} " valrefsiz = wx.BoxSizer(wx.HORIZONTAL) valrefsiz.Add(wx.StaticText(Panel,label=deltaS),0) - arr,indx = Table[hist][row]['ref'] + 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 Table[hist][row] and deltaMode: + elif 'init' in DataArray[hist][row] and deltaMode: # does this ever happen? - arr,indx = Table[hist][row]['val'] + arr,indx = indexArrayRef(dataSource,hist,DataArray[hist][row]['val']) delta = arr[indx] - arr,indx = Table[hist][row]['init'] + arr,indx = DataArray[hist][row]['init'] delta -= arr[indx] if delta == 0: deltaS = "" @@ -373,23 +465,23 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, deltaS = f"\u0394 {delta:.4g} " Sizer.Add(wx.StaticText(Panel,label=deltaS),0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - elif 'val' in Table[hist][row] and 'ref' in Table[hist][row]: + elif 'val' in DataArray[hist][row] and 'ref' in DataArray[hist][row]: valrefsiz = wx.BoxSizer(wx.HORIZONTAL) - arr,indx = Table[hist][row]['val'] + 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 = Table[hist][row]['ref'] + 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 Table[hist][row]: - arr,indx = Table[hist][row]['val'] + 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), @@ -399,48 +491,59 @@ def displayDataTable(rowLabels,Table,Sizer,Panel,lblRow=False,deltaMode=False, Sizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) if firstentry is None: firstentry = w - elif 'ref' in Table[hist][row]: - arr,indx = Table[hist][row]['ref'] + 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 Table[hist][row]: - Sizer.Add(wx.StaticText(Panel,label=Table[hist][row]['str']),0, + 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',Table[hist][row],hist,row) + print('Should not happen',DataArray[hist][row],hist,row) return firstentry,lblDict - def HistFrame(G2frame): - '''Give up on side-by-side scrolled panels. Put everything - in a single FlexGridSizer. + '''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'): - prmTable = getSampleVals(G2frame,Histograms) + prmArray = getSampleVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): - prmTable = getInstVals(G2frame,Histograms) + prmArray = getInstVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Limits'): CopyCtrl = False - prmTable = getLimitVals(G2frame,Histograms) + prmArray = getLimitVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Background'): - prmTable = getBkgVals(G2frame,Histograms) + prmArray = getBkgVals(G2frame,Histograms) CopyCtrl = False else: print('Unexpected', G2frame.GroupInfo['displayMode']) return - #debug# for hist in prmTable: printTable(phaseList[page],hist,prmTable[hist]) # see the dict - # construct a list of row labels, attempting to keep the - # order they appear in the original array rowLabels = [] lpos = 0 - for hist in prmTable: + nonZeroRows = [] + dataSource = prmArray['_dataSource'] + for hist in prmArray: + if hist == '_dataSource': continue + cols = len(prmArray) prevkey = None - for key in prmTable[hist]: + 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) @@ -448,6 +551,9 @@ def HistFrame(G2frame): 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 @@ -457,12 +563,11 @@ def HistFrame(G2frame): Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() deltaMode = "\u0394" in G2frame.GroupInfo['displayMode'] n = 2 - if CopyCtrl: n += 1 # add column for copy - valSizer = wx.FlexGridSizer(0,len(prmTable)+n,3,10) + 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 ')) - #valSizer.Add(wx.StaticText(midPanel,label=' Copy ')) for i,hist in enumerate(histLabels(G2frame)[1]): if i == 1 and CopyCtrl: if deltaMode: @@ -472,10 +577,9 @@ def HistFrame(G2frame): valSizer.Add(wx.StaticText(midPanel, label=f"\u25A1 = {hist}"), 0,wx.ALIGN_CENTER) - firstentry,lblDict = displayDataTable(rowLabels,prmTable,valSizer,midPanel, + firstentry,lblDict = displayDataArray(rowLabels,prmArray,valSizer,midPanel, lblRow=True, deltaMode=deltaMode,CopyCtrl=CopyCtrl) - #G2frame.dataWindow.SetDataSize() if firstentry is not None: # prevent scroll to show last entry wx.Window.SetFocus(firstentry) firstentry.SetInsertionPoint(0) # prevent selection of text in widget @@ -494,33 +598,25 @@ def getSampleVals(G2frame,Histograms): return # parameters to include in table parms = [] - parmDict = {} - #indexDict = {} + 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] = {} - histdata = Histograms[hist] - hpD = {} - hpD['Inst. name'] = { - 'val' : (histdata['Sample Parameters'],'InstrName')} - #indexDict[hist]['Inst. name'] = { - # 'val' : (hist,'Sample Parameters','InstrName')} - hpD['Diff type'] = { - 'str' : histdata['Sample Parameters']['Type']} - #indexDict[hist]['Diff type'] = { - # 'str' : (hist,'Sample Parameters','Type')} - arr = histdata['Sample Parameters']['Scale'] - hpD['Scale factor'] = { - 'val' : (arr,0), - 'ref' : (arr,1),} - #indexDict[hist]['Scale factor'] = { - # 'val' : (hist,'Sample Parameters','Scale',0), - # 'ref' : (hist,1),} - #breakpoint() - #return + 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 = histdata['Instrument Parameters'][0]['Type'][0] - dataType = histdata['Sample Parameters']['Type'] + 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: @@ -550,26 +646,26 @@ def getSampleVals(G2frame,Histograms): # and loop over them for key,lbl,fmt in parms: - if fmt is None and type(histdata['Sample Parameters'][key]) is list: - arr = histdata['Sample Parameters'][key] - hpD[lbl] = { - 'val' : (arr,0), - 'ref' : (arr,1),} + 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: - hpD[lbl] = { - 'val' : (histdata['Sample Parameters'],key)} + indexDict[hist][lbl] = { + 'val' : ('Sample Parameters',key)} elif type(fmt) is str: - hpD[lbl] = { - 'str' : f"{histdata['Sample Parameters'][key]:{fmt}}"} - + indexDict[hist][lbl] = { + 'str' : ('Sample Parameters',key), + 'fmt' : fmt} + for key in ('FreePrm1','FreePrm2','FreePrm3'): lbl = Controls[key] - hpD[lbl] = { - 'val' : (histdata['Sample Parameters'],key), + indexDict[hist][lbl] = { + 'val' : ('Sample Parameters',key), 'rowlbl' : (Controls,key) } - parmDict[hist] = hpD - return parmDict + return indexDict def getInstVals(G2frame,Histograms): '''Generate the Parameter Data Table (a dict of dicts) with @@ -584,21 +680,20 @@ def getInstVals(G2frame,Histograms): print(f'Unexpected: {groupName} not in groupDict') return # parameters to include in table - parms = [] - parmDict = {} + indexDict = {'_dataSource':Histograms} # loop over histograms in group for hist in groupDict[groupName]: - histdata = Histograms[hist] + indexDict[hist] = {} insVal = Histograms[hist]['Instrument Parameters'][0] - hpD = {} insType = insVal['Type'][1] - try: - hpD['Bank'] = { - 'str' : str(int(insVal['Bank'][1]))} - except: - pass - hpD['Hist type'] = { - 'str' : insType} + 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: @@ -632,17 +727,17 @@ def getInstVals(G2frame,Histograms): else: return {} for key,lbl,fmt in keylist: - arr = insVal[key] if fmt is None: - hpD[lbl] = { - 'init' : (arr,0), - 'val' : (arr,1), - 'ref' : (arr,2),} + indexDict[hist][lbl] = { + 'init' : (insVal[key],0), + 'val' : ('Instrument Parameters',0,key,1), + 'ref' : ('Instrument Parameters',0,key,2),} else: - hpD[lbl] = { - 'str' : f'{arr[1]:{fmt}}'} - parmDict[hist] = hpD - return parmDict + 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 @@ -657,36 +752,24 @@ def getLimitVals(G2frame,Histograms): print(f'Unexpected: {groupName} not in groupDict') return # parameters to include in table - parmDict = {} + indexDict = {'_dataSource':Histograms} # loop over histograms in group for hist in groupDict[groupName]: - histdata = Histograms[hist] - hpD = {} - #breakpoint() - hpD['Tmin'] = { - 'val' : (histdata['Limits'][1],0), - 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]] - } - hpD['Tmax'] = { - 'val' : (histdata['Limits'][1],1), - 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]] - } - for i,item in enumerate(histdata['Limits'][2:]): - hpD[f'excl Low {i+1}'] = { - 'val' : (item,0), - 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]]} - hpD[f'excl High {i+1}'] = { - 'val' : (item,1), - 'range': [histdata['Limits'][0][0],histdata['Limits'][0][1]]} - parmDict[hist] = hpD - # for i in parmDict: - # print(i) - # for j in parmDict[i]: - # print('\t',j) - # for k in parmDict[i][j]: - # print('\t\t',k,parmDict[i][j][k]) - return parmDict - + 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 @@ -702,61 +785,44 @@ def getBkgVals(G2frame,Histograms): print(f'Unexpected: {groupName} not in groupDict') return # parameters to include in table - parms = [] - parmDict = {} + indexDict = {'_dataSource':Histograms} # loop over histograms in group for hist in groupDict[groupName]: - histdata = Histograms[hist] - hpD = {} - hpD['Function'] = { - 'str' : histdata['Background'][0][0]} - hpD['ref flag'] = { - 'ref' : (histdata['Background'][0],1)} - hpD['# Bkg terms'] = { - 'str' : str(int(histdata['Background'][0][2]))} - hpD['# Debye terms'] = { - 'str' : str(int(histdata['Background'][1]['nDebye']))} - for i,term in enumerate(histdata['Background'][1]['debyeTerms']): - hpD[f'A #{i+1}'] = { - 'val' : (histdata['Background'][1]['debyeTerms'][i],0), - 'ref' : (histdata['Background'][1]['debyeTerms'][i],1), - } - hpD[f'R #{i+1}'] = { - 'val' : (histdata['Background'][1]['debyeTerms'][i],2), - 'ref' : (histdata['Background'][1]['debyeTerms'][i],3), - } - hpD[f'U #{i+1}'] = { - 'val' : (histdata['Background'][1]['debyeTerms'][i],4), - 'ref' : (histdata['Background'][1]['debyeTerms'][i],5), - } - hpD['# Bkg Peaks'] = { - 'str' : str(int(histdata['Background'][1]['nPeaks']))} - for i,term in enumerate(histdata['Background'][1]['peaksList']): - hpD[f'pos #{i+1}'] = { - 'val' : (histdata['Background'][1]['peaksList'][i],0), - 'ref' : (histdata['Background'][1]['peaksList'][i],1), - } - hpD[f'int #{i+1}'] = { - 'val' : (histdata['Background'][1]['peaksList'][i],2), - 'ref' : (histdata['Background'][1]['peaksList'][i],3), + 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) } - hpD[f'sig #{i+1}'] = { - 'val' : (histdata['Background'][1]['peaksList'][i],4), - 'ref' : (histdata['Background'][1]['peaksList'][i],5), - } - hpD[f'gam #{i+1}'] = { - 'val' : (histdata['Background'][1]['peaksList'][i],6), - 'ref' : (histdata['Background'][1]['peaksList'][i],7), - } - if histdata['Background'][1]['background PWDR'][0]: + 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' - hpD['Fixed bkg file'] = { - 'str' : val} - parmDict[hist] = hpD - return parmDict - + 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 @@ -786,15 +852,15 @@ def OnScroll(event): page = 0 #print('no page selected',phaseList[page]) # generate a dict with HAP values for each phase (may not be the same) - HAPtable = getHAPvals(G2frame,phaseList[page]) - #debug# for hist in HAPtable: printTable(phaseList[page],hist,HAPtable[hist]) # see the dict + 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 HAPtable: + for hist in HAParray: + if hist == '_dataSource': continue prevkey = None - for key in HAPtable[hist]: + for key in HAParray[hist]: if key not in rowLabels: if prevkey is None: rowLabels.insert(lpos,key) @@ -813,7 +879,6 @@ def OnScroll(event): lblScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) hpad = 3 # space between rows - #lblSizer = wx.FlexGridSizer(0,3,hpad,2) lblSizer = wx.FlexGridSizer(0,2,hpad,2) lblScroll.SetSizer(lblSizer) bigSizer.Add(lblScroll,0,wx.EXPAND) @@ -821,8 +886,7 @@ def OnScroll(event): # 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(HAPtable),hpad,10) - HAPSizer = wx.FlexGridSizer(0,len(HAPtable)+1,hpad,10) + HAPSizer = wx.FlexGridSizer(0,len(HAParray),hpad,10) HAPScroll.SetSizer(HAPSizer) bigSizer.Add(HAPScroll,1,wx.EXPAND) @@ -839,8 +903,7 @@ def OnScroll(event): w0 = wx.StaticText(lblScroll,label=' ') lblSizer.Add(w0) lblSizer.Add(wx.StaticText(lblScroll,label=' Ref ')) - #lblSizer.Add(wx.StaticText(lblScroll,label=' Copy ')) - firstentry,lblDict = displayDataTable(rowLabels,HAPtable,HAPSizer,HAPScroll, + firstentry,lblDict = displayDataArray(rowLabels,HAParray,HAPSizer,HAPScroll, lblRow=True,lblSizer=lblSizer,lblPanel=lblScroll) # get row sizes in data table HAPSizer.Layout() @@ -849,15 +912,6 @@ def OnScroll(event): # (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]) - # w = wx.StaticText(lblScroll,label=row,size=s) - # lblSizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - # lblDict = {} - # for i,row in enumerate(rowLabels): - # w = wx.StaticText(lblScroll,label=row) - # lblDict[row] = w - # lblSizer.Add(w,0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) for i,row in enumerate(rowLabels): s = wx.Size(-1,rowHeights[i+1]) lblDict[row].SetMinSize(s) @@ -887,12 +941,6 @@ def OnScroll(event): mainSizer = wx.BoxSizer(wx.VERTICAL) #botSizer = G2frame.dataWindow.bottomBox #botParent = G2frame.dataWindow.bottomPanel - # label with shared portion of histogram name - # topSizer.Add(wx.StaticText(topParent, - # label=f'HAP parameters for group "{histLabels(G2frame)[0]}"'), - # 0,WACV) - # topSizer.Add((-1,-1),1,wx.EXPAND) - # topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) G2G.HorizontalLine(mainSizer,midPanel) midPanel.SetSizer(mainSizer) @@ -938,285 +986,123 @@ def getHAPvals(G2frame,phase): 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 - # loop over histograms in group - parmDict = {} + indexDict = {'_dataSource':PhaseData['Histograms']} for hist in groupDict[groupName]: - parmDict[hist] = makeHAPtbl(G2frame,phase,PhaseData,hist) - #printTable(phase,hist,parmDict[hist]) - return parmDict - -def makeHAPtbl(G2frame,phase,PhaseData,hist): - '''Construct a Parameter Data Table dict, providing access - to the HAP variables for one phase and histogram. - - :return: the Parameter Data Table dict, as described above. - ''' - SGData = PhaseData['General']['SGData'] - cell = PhaseData['General']['Cell'][1:] - Amat,Bmat = G2lat.cell2AB(cell[:6]) - #G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, hist) - #data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) - HAPdict = PhaseData['Histograms'][hist] - - parmDict = {} - # phase fraction - parmDict['Phase frac'] = { - 'val' : (HAPdict['Scale'],0), - 'ref' : (HAPdict['Scale'],1),} - - parmDict['LeBail extract'] = { - 'str' : "Yes" if HAPdict.get('LeBail') else '(off)' - } - - # size values - arr = HAPdict['Size'] - if arr[0] == 'isotropic': - parmDict['Size'] = { - 'val' : (arr[1],0), - 'ref' : (arr[2],0),} - elif arr[0] == 'uniaxial': - parmDict['Size/Eq'] = { - 'val' : (arr[1],0), - 'ref' : (arr[2],0),} - parmDict['Size/Ax'] = { - 'val' : (arr[1],1), - 'ref' : (arr[2],1),} - parmDict['Size/dir'] = { - 'str' : ','.join([str(i) for i in arr[3]])} - else: - for i,lbl in enumerate(['S11','S22','S33','S12','S13','S23']): - parmDict[f'Size/{lbl}'] = { - 'val' : (arr[4],i), - 'ref' : (arr[5],i),} - parmDict['Size LGmix'] = { - 'val' : (arr[1],2), - 'ref' : (arr[2],2),} - - # microstrain values - arr = HAPdict['Mustrain'] - if arr[0] == 'isotropic': - parmDict['\u00B5Strain'] = { - 'val' : (arr[1],0), - 'ref' : (arr[2],0),} - elif arr[0] == 'uniaxial': - parmDict['\u00B5Strain/Eq'] = { - 'val' : (arr[1],0), - 'ref' : (arr[2],0),} - parmDict['\u00B5Strain/Ax'] = { - 'val' : (arr[1],1), - 'ref' : (arr[2],1),} - parmDict['\u00B5Strain/dir'] = { - 'str' : ','.join([str(i) for i in arr[3]])} - else: - Snames = G2spc.MustrainNames(SGData) - for i,lbl in enumerate(Snames): - if i >= len(arr[4]): break - parmDict[f'\u00B5Strain/{lbl}'] = { - 'val' : (arr[4],i), - 'ref' : (arr[5],i),} - muMean = G2spc.MuShklMean(SGData,Amat,arr[4][:len(Snames)]) - parmDict['\u00B5Strain/mean'] = { - 'str' : f'{muMean:.2f}'} - parmDict['\u00B5Strain LGmix'] = { - 'val' : (arr[1],2), - 'ref' : (arr[2],2),} - - # Hydrostatic terms - Hsnames = G2spc.HStrainNames(SGData) - arr = HAPdict['HStrain'] - for i,lbl in enumerate(Hsnames): - if i >= len(arr[0]): break - parmDict[f'Size/{lbl}'] = { - 'val' : (arr[0],i), - 'ref' : (arr[1],i),} - - # Preferred orientation terms - arr = HAPdict['Pref.Ori.'] - if arr[0] == 'MD': - parmDict['March-Dollase'] = { - 'val' : (arr,1), - 'ref' : (arr,2),} - parmDict['M-D/dir'] = { - 'str' : ','.join([str(i) for i in arr[3]])} - else: - parmDict['Spherical harmonics'] = { - 'ref' : (arr,2),} - parmDict['SH order'] = { - 'str' : str(arr[4])} - for lbl in arr[5]: - parmDict[f'SP {lbl}']= { - 'val' : (arr[5],lbl), - } - parmDict['SH text indx'] = { - 'str' : f'{G2lat.textureIndex(arr[5]):.3f}'} - - # misc: Layer Disp, Extinction - try: - parmDict['Layer displ'] = { - 'val' : (HAPdict['Layer Disp'],0), - 'ref' : (HAPdict['Layer Disp'],1),} - except KeyError: - pass - try: - parmDict['Extinction'] = { - 'val' : (HAPdict['Extinction'],0), - 'ref' : (HAPdict['Extinction'],1),} - except KeyError: - pass - try: - parmDict['Babinet A'] = { - 'val' : (HAPdict['Babinet']['BabA'],0), - 'ref' : (HAPdict['Babinet']['BabA'],1),} - except KeyError: - pass - try: - parmDict['Babinet U'] = { - 'val' : (HAPdict['Babinet']['BabU'],0), - 'ref' : (HAPdict['Babinet']['BabU'],1),} - except KeyError: - pass - return parmDict - -def printTable(phase,hist,parmDict): - # show the variables and values in data table array -- debug use only - print(phase,hist) - for sel in parmDict: - arr = parmDict[sel] - val = 'N/A' - if 'val' in arr: - val = arr['val'][0] [arr['val'][1]] - if 'str' in arr: - v = arr['str'] - val = f'"{v}"' - ref = 'N/A' - if 'ref' in arr: - ref = arr['ref'][0] [arr['ref'][1]] - print(f'{sel!r:20s} {val} {ref}') - print('\n') - -# code that did not work and is being abandoned for now -# -# def HistFrame(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 OnScroll(event): -# 'Synchronize vertical scrolling between the two scrolled windows' -# obj = event.GetEventObject() -# pos = obj.GetViewStart()[1] -# if obj == lblScroll: -# SampleScroll.Scroll(-1, pos) -# else: -# lblScroll.Scroll(-1, pos) -# event.Skip() -# #--------------------------------------------------------------------- -# # generate a dict with Sample values for each histogram -# Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() -# HAPtable = getSampleVals(G2frame,Histograms) -# #debug# for hist in HAPtable: printTable(phaseList[page],hist,HAPtable[hist]) # see the dict -# # construct a list of row labels, attempting to keep the -# # order they appear in the original array -# rowLabels = [] -# lpos = 0 -# for hist in HAPtable: -# prevkey = None -# for key in HAPtable[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 =============================================== -# #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 -# # this causes a crash, but somehow I need to clean up the old contents -# #if midPanel.GetSizer(): -# # for i in midPanel.GetSizer().GetChildren(): -# # i.Destroy() # clear out old widgets -# #botSizer = G2frame.dataWindow.bottomBox -# #botParent = G2frame.dataWindow.bottomPanel -# # label with shared portion of histogram name -# topSizer.Add(wx.StaticText(topParent, -# label=f'Sample parameters for group "{histLabels(G2frame)[0]}"'), -# 0,WACV) -# topSizer.Add((-1,-1),1,wx.EXPAND) -# topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) - -# panel = wx.Panel(midPanel) -# panel.SetSize(midPanel.GetSize()) -# mainSizer = wx.BoxSizer(wx.VERTICAL) -# G2G.HorizontalLine(mainSizer,panel) -# panel.SetSizer(mainSizer) -# Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() - - -# bigSizer = wx.BoxSizer(wx.HORIZONTAL) -# mainSizer.Add(bigSizer,1,wx.EXPAND) -# if True: -# # 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,1,hpad,10) -# lblScroll.SetSizer(lblSizer) -# bigSizer.Add(lblScroll,0,wx.EXPAND) - -# # Create scrolled panel to display HAP data -# SampleScroll = wx.lib.scrolledpanel.ScrolledPanel(panel, -# style=wx.VSCROLL|wx.HSCROLL|wx.ALWAYS_SHOW_SB) -# SampleSizer = wx.FlexGridSizer(0,len(HAPtable),hpad,10) -# SampleScroll.SetSizer(SampleSizer) -# bigSizer.Add(SampleScroll,1,wx.EXPAND) - -# # Bind scroll events to synchronize scrolling -# lblScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) -# SampleScroll.Bind(wx.EVT_SCROLLWIN, OnScroll) -# # label columns with unique part of histogram names -# for hist in histLabels(G2frame)[1]: -# SampleSizer.Add(wx.StaticText(SampleScroll,label=f"\u25A1 = {hist}"), -# 0,wx.ALIGN_CENTER) - -# displayDataTable(rowLabels,HAPtable,SampleSizer,SampleScroll) -# # get row sizes in data table -# SampleSizer.Layout() -# rowHeights = SampleSizer.GetRowHeights() -# # match rose sizes in Labels -# # (must be done after SampleSizer row heights are defined) -# s = wx.Size(-1,rowHeights[0]) -# lblSizer.Add(wx.StaticText(lblScroll,label=' ',size=s)) -# for i,row in enumerate(rowLabels): -# s = wx.Size(-1,rowHeights[i+1]) -# lblSizer.Add(wx.StaticText(lblScroll,label=row,size=s),0, -# wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) -# # Fit the scrolled windows to their content -# lblSizer.Layout() -# xLbl,_ = lblSizer.GetMinSize() -# xTab,yTab = SampleSizer.GetMinSize() -# lblScroll.SetSize((xLbl,yTab)) -# lblScroll.SetMinSize((xLbl+15,yTab)) # add room for scroll bar -# lblScroll.SetVirtualSize(lblSizer.GetMinSize()) -# SampleScroll.SetVirtualSize(SampleSizer.GetMinSize()) -# lblScroll.SetupScrolling(scroll_x=True, scroll_y=True, rate_x=20, rate_y=20) -# SampleScroll.SetupScrolling(scroll_x=True, scroll_y=True, rate_x=20, rate_y=20) -# breakpoint() -# wx.CallLater(100,G2frame.SendSizeEvent) -# G2frame.dataWindow.SetDataSize() + 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 From 633f1bd1a06c461f7e876e1d369a38f283ab12cd Mon Sep 17 00:00:00 2001 From: BHT Date: Fri, 26 Dec 2025 20:16:04 -0600 Subject: [PATCH 24/30] Add group copy/copy selected; load histogram & phase once; make prmArray (from getXXX routines) global; fix some display bugs --- GSASII/GSASIIgroupGUI.py | 145 +++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/GSASII/GSASIIgroupGUI.py b/GSASII/GSASIIgroupGUI.py index f3522fa7..089ee838 100644 --- a/GSASII/GSASIIgroupGUI.py +++ b/GSASII/GSASIIgroupGUI.py @@ -181,18 +181,15 @@ 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 + + def copyPrep(): 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 = [] + selList = [] for g in groupDict: if g == groupName: continue if curLen != len(groupDict[g]): continue @@ -212,38 +209,63 @@ def OnCopyAll(event): 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') + return selList,groupDict,groupName + + def OnCopyAll(event): + res = copyPrep() + if res is None: return + selList,groupDict,groupName = res + dataSource = prmArray['_dataSource'] + for h in selList: # selected groups + for src,dst in zip(groupDict[groupName],groupDict[h]): # histograms in groups (same length enforced) + for i in prmArray[src]: + for j in ('ref','val','str'): + if j in prmArray[src][i]: + if prmArray[src][i][j] is None: continue + try: + arr,indx = indexArrayRef(dataSource,dst,prmArray[src][i][j]) + arr[indx] = indexArrayVal(dataSource,src,prmArray[src][i][j]) + except Exception as msg: # could hit an error if an array element is not defined + pass + #print(msg) + #print('error with',i,dst) def OnCopySel(event): - print('OnCopySel') - G2G.G2MessageBox(G2frame, - f'Sorry, not fully implemented yet', - 'In progress') - return + res = copyPrep() + if res is None: return + selList,groupDict,groupName = res + dataSource = prmArray['_dataSource'] + choices = [] + for src in groupDict[groupName]: + for i in prmArray[src]: + for j in ('ref','val','str'): + if prmArray[src][i].get(j) is None: + continue + if i not in choices: + choices.append(i) + dlg = G2G.G2MultiChoiceDialog(G2frame, 'Copy which items?', 'Copy what?', choices) + itemList = [] + try: + if dlg.ShowModal() == wx.ID_OK: + itemList = [choices[i] for i in dlg.GetSelections()] + finally: + dlg.Destroy() + if len(itemList) == 0: return + for h in selList: # selected groups + for src,dst in zip(groupDict[groupName],groupDict[h]): # histograms in groups (same length enforced) + for i in prmArray[src]: + if i not in itemList: continue + for j in ('ref','val','str'): + if j in prmArray[src][i]: + if prmArray[src][i][j] is None: continue + try: + arr,indx = indexArrayRef(dataSource,dst,prmArray[src][i][j]) + arr[indx] = indexArrayVal(dataSource,src,prmArray[src][i][j]) + except Exception as msg: # could hit an error if an array element is not defined + pass + #print(msg) + #print('error with',i,dst) + Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() if not hasattr(G2frame,'GroupInfo'): G2frame.GroupInfo = {} G2frame.GroupInfo['displayMode'] = G2frame.GroupInfo.get('displayMode','Sample') @@ -271,9 +293,9 @@ def OnCopySel(event): topSizer.Add(G2G.HelpButton(topParent,helpIndex=G2frame.dataWindow.helpKey)) if G2frame.GroupInfo['displayMode'].startswith('Hist'): - HAPframe(G2frame) + HAPframe(G2frame,Histograms,Phases) else: - HistFrame(G2frame) + HistFrame(G2frame,Histograms) if plot: G2pwpl.PlotPatterns(G2frame,plotType='GROUP') G2frame.dataWindow.SetDataSize() #wx.CallLater(100,G2frame.SendSizeEvent) @@ -509,13 +531,13 @@ def displayDataArray(rowLabels,DataArray,Sizer,Panel,lblRow=False,deltaMode=Fals print('Should not happen',DataArray[hist][row],hist,row) return firstentry,lblDict -def HistFrame(G2frame): +def HistFrame(G2frame,Histograms): '''Put everything in a single FlexGridSizer. ''' #--------------------------------------------------------------------- # generate a dict with values for each histogram - Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() CopyCtrl = True + global prmArray if G2frame.GroupInfo['displayMode'].startswith('Sample'): prmArray = getSampleVals(G2frame,Histograms) elif G2frame.GroupInfo['displayMode'].startswith('Instrument'): @@ -527,6 +549,7 @@ def HistFrame(G2frame): prmArray = getBkgVals(G2frame,Histograms) CopyCtrl = False else: + prmArray = None print('Unexpected', G2frame.GroupInfo['displayMode']) return rowLabels = [] @@ -560,7 +583,6 @@ def HistFrame(G2frame): 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) @@ -824,7 +846,7 @@ def getBkgVals(G2frame,Histograms): 'txt' : val} return indexDict -def HAPframe(G2frame): +def HAPframe(G2frame,Histograms,Phases): '''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. @@ -852,15 +874,16 @@ def OnScroll(event): 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]) + global prmArray + prmArray = getHAPvals(G2frame,phaseList[page],Histograms,Phases) # construct a list of row labels, attempting to keep the # order they appear in the original array rowLabels = [] lpos = 0 - for hist in HAParray: + for hist in prmArray: if hist == '_dataSource': continue prevkey = None - for key in HAParray[hist]: + for key in prmArray[hist]: if key not in rowLabels: if prevkey is None: rowLabels.insert(lpos,key) @@ -886,7 +909,7 @@ def OnScroll(event): # 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) + HAPSizer = wx.FlexGridSizer(0,len(prmArray),hpad,10) HAPScroll.SetSizer(HAPSizer) bigSizer.Add(HAPScroll,1,wx.EXPAND) @@ -903,7 +926,7 @@ def OnScroll(event): w0 = wx.StaticText(lblScroll,label=' ') lblSizer.Add(w0) lblSizer.Add(wx.StaticText(lblScroll,label=' Ref ')) - firstentry,lblDict = displayDataArray(rowLabels,HAParray,HAPSizer,HAPScroll, + firstentry,lblDict = displayDataArray(rowLabels,prmArray,HAPSizer,HAPScroll, lblRow=True,lblSizer=lblSizer,lblPanel=lblScroll) # get row sizes in data table HAPSizer.Layout() @@ -944,7 +967,6 @@ def OnScroll(event): 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')) @@ -966,26 +988,13 @@ def OnScroll(event): selectPhase(None) #G2frame.dataWindow.SetDataSize() -def getHAPvals(G2frame,phase): +def getHAPvals(G2frame,phase,Histograms,Phases): '''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 {} - + PhaseData = Phases[phase] SGData = PhaseData['General']['SGData'] cell = PhaseData['General']['Cell'][1:] Amat,Bmat = G2lat.cell2AB(cell[:6]) @@ -1079,15 +1088,15 @@ def getHAPvals(G2frame,phase): indexDict[hist]['Spherical harmonics'] = { 'ref' : ('Pref.Ori.',2),} indexDict[hist]['SH order'] = { - 'str' : str('Pref.Ori.',4)} - for lbl in arr[5]: + 'str' : ('Pref.Ori.',4), + 'fmt' : '.0f'} + for lbl in PhaseData['Histograms'][hist]['Pref.Ori.'][5]: indexDict[hist][f'SP {lbl}']= { 'val' : ('Pref.Ori.',5,lbl), } - indexDict[hist]['SH text indx'] = { + indexDict[hist]['SH txtr indx'] = { 'str' : None, - 'txt' : f'{G2lat.textureIndex('Pref.Ori.'[5]):.3f}'} - + 'txt' : f'{G2lat.textureIndex(PhaseData['Histograms'][hist]['Pref.Ori.'][5]):.3f}'} # misc: Layer Disp, Extinction if 'Layer Disp' in PhaseData['Histograms'][hist]: indexDict[hist]['Layer displ'] = { From 820fe17fe65d90e9e9c842adffcfd08500f39213 Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 28 Dec 2025 17:10:40 -0600 Subject: [PATCH 25/30] Add code based on PR #285 that preserves plot x-range & y-scaling across refinements & repurposes Home button --- GSASII/GSASIIplot.py | 21 ++++ GSASII/GSASIIpwdplot.py | 218 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 219 insertions(+), 20 deletions(-) diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index fa487ffa..7eaed855 100644 --- a/GSASII/GSASIIplot.py +++ b/GSASII/GSASIIplot.py @@ -671,6 +671,27 @@ def _update_view(self): wx.CallAfter(*self.updateActions) Toolbar._update_view(self) + def home(self, *args): + '''Override home button to clear saved GROUP plot limits and trigger replot. + This ensures that pressing home resets to full data range while retaining x-units. + For GROUP plots, we need to replot rather than use matplotlib's home because + matplotlib's home would restore the original shared limits, not per-histogram limits. + (based on MG/Cl Sonnet code) + ''' + G2frame = wx.GetApp().GetMainTopWindow() + # Check if we're in GROUP plot mode - if so, clear saved GROUP + # plot x-limits and trigger a replot + if self.arrows.get('_groupMode'): + # PlotPatterns will use full data range + if hasattr(G2frame, 'groupXlim'): + del G2frame.groupXlim + # Trigger a full replot for GROUP plots + if self.updateActions: + wx.CallAfter(*self.updateActions) + return + # For non-GROUP plots, call the parent's home method + Toolbar.home(self, *args) + def AnyActive(self): for Itool in range(self.GetToolsCount()): if self.GetToolState(self.GetToolByPos(Itool).GetId()): diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index af9777cf..9b76d026 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -229,8 +229,10 @@ def OnPlotKeyPress(event): Page.plotStyle['flTicks'] = (Page.plotStyle.get('flTicks',0)+1)%3 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 + # Clear saved x-limits when toggling sharedX mode (MG/Cl Sonnet) + if hasattr(G2frame, 'groupXlim'): + del G2frame.groupXlim + newPlot = True elif event.key == 'x' and 'PWDR' in plottype: Page.plotStyle['exclude'] = not Page.plotStyle['exclude'] elif event.key == '.': @@ -421,6 +423,13 @@ def OnPlotKeyPress(event): newPlot = True if 'PWDR' in plottype or plottype.startswith('GROUP'): Page.plotStyle['qPlot'] = not Page.plotStyle['qPlot'] + # switching from d to Q + if (Page.plotStyle['qPlot'] and + Page.plotStyle['dPlot'] and + getattr(G2frame, 'groupXlim', None) is not None): + G2frame.groupXlim = ( + 2.0 * np.pi / G2frame.groupXlim[1], # Q_max -> d_min + 2.0 * np.pi / G2frame.groupXlim[0]) # Q_min -> d_max Page.plotStyle['dPlot'] = False Page.plotStyle['chanPlot'] = False elif plottype in ['SASD','REFD']: @@ -436,6 +445,13 @@ def OnPlotKeyPress(event): elif event.key == 't' and ('PWDR' in plottype or plottype.startswith('GROUP')): newPlot = True Page.plotStyle['dPlot'] = not Page.plotStyle['dPlot'] + # switching from Q to d + if (Page.plotStyle['qPlot'] and + Page.plotStyle['dPlot'] and + getattr(G2frame, 'groupXlim', None) is not None): + G2frame.groupXlim = ( + 2.0 * np.pi / G2frame.groupXlim[1], # Q_min <- d_max + 2.0 * np.pi / G2frame.groupXlim[0]) # Q_max <- d_min Page.plotStyle['qPlot'] = False Page.plotStyle['chanPlot'] = False elif event.key == 'm': @@ -1490,7 +1506,10 @@ def refPlotUpdate(Histograms,cycle=None,restore=False): ''' if restore: (G2frame.SinglePlot,G2frame.Contour,G2frame.Weight, - G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot']) = savedSettings + G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'], + Page.plotStyle['qPlot'],Page.plotStyle['dPlot']) = savedSettings + # Also save to G2frame so settings survive Page recreation during ResetPlots (MG/Cl Sonnet) + G2frame.savedPlotStyle = copy.copy(Page.plotStyle) return if plottingItem not in Histograms: @@ -1704,7 +1723,70 @@ def drawTicks(Phases,phaseList,group=False): Plot.axvline(xt,color=plcolor, picker=3., label='_FLT_'+phase,lw=0.5) - + + # Callback used to update y-limits when user zooms interactively (MG/Cl Sonnet) + def onGroupXlimChanged(ax): + '''Callback to update y-limits for all panels when x-range changes. + We calculate the global y-range across all panels for the visible x-range, + then explicitly set y-limits on ALL panels. + ''' + xlim = ax.get_xlim() + # Save x-limits for persistence across refinements + if (plotOpt['sharedX'] and + (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): + G2frame.groupXlim = xlim + + # Calculate global y-range across ALL panels for visible x-range + global_ymin = float('inf') + global_ymax = float('-inf') + global_dzmin = float('inf') + global_dzmax = float('-inf') + max_tick_space = 0 + + for i in range(Page.groupN): + xarr = np.array(gX[i]) + xye = gdat[i] + mask = (xarr >= xlim[0]) & (xarr <= xlim[1]) + if np.any(mask): + # Calculate scaled y-values for visible data + scaleY = lambda Y, idx=i: (Y - gYmin[idx]) / (gYmax[idx] - gYmin[idx]) * 100 + visible_obs = scaleY(xye[1][mask]) + visible_calc = scaleY(xye[3][mask]) + visible_bkg = scaleY(xye[4][mask]) + ymin_visible = min(visible_obs.min(), visible_calc.min(), visible_bkg.min()) + ymax_visible = max(visible_obs.max(), visible_calc.max(), visible_bkg.max()) + global_ymin = min(global_ymin, ymin_visible) + global_ymax = max(global_ymax, ymax_visible) + # Track tick space needed + if not Page.plotStyle.get('flTicks', False): + max_tick_space = max(max_tick_space, len(RefTbl[i]) * 5) + else: + max_tick_space = max(max_tick_space, 1) + # Calculate diff y-limits + DZ_visible = (xye[1][mask] - xye[3][mask]) * np.sqrt(xye[2][mask]) + global_dzmin = min(global_dzmin, DZ_visible.min()) + global_dzmax = max(global_dzmax, DZ_visible.max()) + + # Apply global y-limits to ALL panels explicitly + if global_ymax > global_ymin: + yrange = global_ymax - global_ymin + ypad = max(yrange * 0.05, 1.0) + ylim_upper = (global_ymin - ypad - max_tick_space, global_ymax + ypad) + for i in range(Page.groupN): + up, down = adjustDim(i, Page.groupN) + Plots[up].set_ylim(ylim_upper) + Plots[up].autoscale(enable=False, axis='y') + if global_dzmax > global_dzmin: + dzrange = global_dzmax - global_dzmin + dzpad = max(dzrange * 0.05, 0.5) + ylim_lower = (global_dzmin - dzpad, global_dzmax + dzpad) + for i in range(Page.groupN): + up, down = adjustDim(i, Page.groupN) + Plots[down].set_ylim(ylim_lower) + Plots[down].autoscale(enable=False, axis='y') + # Force canvas redraw to apply new limits + Page.canvas.draw_idle() + #### beginning PlotPatterns execution ##################################### global exclLines,Page global DifLine @@ -1756,6 +1838,13 @@ def drawTicks(Phases,phaseList,group=False): if not new and hasattr(Page,'prevPlotType'): if Page.prevPlotType != plottype: new = True Page.prevPlotType = plottype + + # Restore saved plot style settings (qPlot, dPlot, logPlot) if they were preserved + # across a refinement cycle. These get saved in refPlotUpdate(restore=True) and + # need to be applied here because Page may have been recreated by ResetPlots. (based on MG/Cl Sonnet) + if hasattr(G2frame, 'savedPlotStyle'): + Page.plotStyle.update(G2frame.savedPlotStyle) + del G2frame.savedPlotStyle # Clear after applying if G2frame.ifSetLimitsMode and G2frame.GPXtree.GetItemText(G2frame.GPXtree.GetSelection()) == 'Limits': # note mode @@ -1823,7 +1912,8 @@ def drawTicks(Phases,phaseList,group=False): plottingItem = G2frame.GPXtree.GetItemText(G2frame.PatternId) # save settings to be restored after refinement with repPlotUpdate({},restore=True) savedSettings = (G2frame.SinglePlot,G2frame.Contour,G2frame.Weight, - G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot']) + G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'], + Page.plotStyle['qPlot'],Page.plotStyle['dPlot']) G2frame.SinglePlot = True G2frame.Contour = False G2frame.Weight = True @@ -2143,7 +2233,7 @@ def drawTicks(Phases,phaseList,group=False): Title += ' - background' 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: + elif Page.plotStyle['dPlot'] and ('PWDR' in plottype or plottype == 'GROUP'): xLabel = r'$d, \AA$' elif Page.plotStyle['chanPlot'] and G2frame.Contour: xLabel = 'Channel no.' @@ -2215,8 +2305,6 @@ def drawTicks(Phases,phaseList,group=False): 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']: @@ -2225,6 +2313,9 @@ def drawTicks(Phases,phaseList,group=False): gX[i] = gdat[i][0] gXmin[i] = min(gX[i]) gXmax[i] = max(gX[i]) + # Calculate Y range from full data initially (may be updated later for zoom) + gYmax[i] = max(max(gdat[i][1]),max(gdat[i][3])) + gYmin[i] = min(min(gdat[i][1]),min(gdat[i][3])) # obs-calc/sigma DZ = (gdat[i][1]-gdat[i][3])*np.sqrt(gdat[i][2]) DZmin = min(DZmin,DZ.min()) @@ -2237,22 +2328,23 @@ def drawTicks(Phases,phaseList,group=False): 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, + #Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True, + # gridspec_kw=GS_kw) + # Don't use sharey='row' when sharedX - we'll manage y-limits manually + # This avoids conflicts between sharey and our dynamic y-limit updates + Plots = Page.figure.subplots(2,Page.groupN,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) + + # Connect callback for all panels when sharedX is enabled + if (plotOpt['sharedX'] and + (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): + up, down = adjustDim(0, Page.groupN) + Plots[up].callbacks.connect('xlim_changed', onGroupXlimChanged) # pretty up the tick labels up,down = adjustDim(0,Page.groupN) @@ -2268,7 +2360,7 @@ def drawTicks(Phases,phaseList,group=False): 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.text(0.001,0.03,commonltrs,fontsize=13,color='g') Page.figure.supxlabel(xLabel) for i,h in enumerate(groupPlotList): up,down = adjustDim(i,Page.groupN) @@ -2284,7 +2376,7 @@ def drawTicks(Phases,phaseList,group=False): transform=Plot.transAxes, verticalalignment='top', horizontalalignment=ha, - fontsize=14) + fontsize=14,color='g',fontweight='bold') 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) @@ -2296,6 +2388,86 @@ def drawTicks(Phases,phaseList,group=False): 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) + + # Set axis limits AFTER plotting data to prevent autoscaling from overriding them (MG/Cl Sonnet) + # When sharedX is enabled, calculate common x-range encompassing all histograms + if (plotOpt['sharedX'] and + (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): + if getattr(G2frame, 'groupXlim', None) is not None: + commonXlim = getattr(G2frame, 'groupXlim', None) + else: + # Calculate union of all histogram x-ranges + commonXmin = min(gXmin.values()) + commonXmax = max(gXmax.values()) + commonXlim = (commonXmin, commonXmax) + + # First pass: set x-limits and calculate global y-range for visible data + # Since sharey='row', all upper panels share y-limits, all lower panels share y-limits + global_ymin = float('inf') + global_ymax = float('-inf') + global_dzmin = float('inf') + global_dzmax = float('-inf') + max_tick_space = 0 + + for i in range(Page.groupN): + up, down = adjustDim(i, Page.groupN) + if (plotOpt['sharedX'] and + (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): + xlim = commonXlim + Plots[up].set_xlim(xlim) + Plots[down].set_xlim(xlim) + else: + xlim = (gXmin[i], gXmax[i]) + Plots[up].set_xlim(xlim) + Plots[down].set_xlim(xlim) + + # Calculate y-range for this panel's visible data + xarr = np.array(gX[i]) + xye = gdat[i] + mask = (xarr >= xlim[0]) & (xarr <= xlim[1]) + if np.any(mask): + scaleY = lambda Y, idx=i: (Y - gYmin[idx]) / (gYmax[idx] - gYmin[idx]) * 100 + visible_obs = scaleY(xye[1][mask]) + visible_calc = scaleY(xye[3][mask]) + visible_bkg = scaleY(xye[4][mask]) + ymin_visible = min(visible_obs.min(), visible_calc.min(), visible_bkg.min()) + ymax_visible = max(visible_obs.max(), visible_calc.max(), visible_bkg.max()) + global_ymin = min(global_ymin, ymin_visible) + global_ymax = max(global_ymax, ymax_visible) + # Track tick space needed + if not Page.plotStyle.get('flTicks', False): + max_tick_space = max(max_tick_space, len(RefTbl[i]) * 5) + else: + max_tick_space = max(max_tick_space, 1) + # Calculate diff y-limits + DZ_visible = (xye[1][mask] - xye[3][mask]) * np.sqrt(xye[2][mask]) + global_dzmin = min(global_dzmin, DZ_visible.min()) + global_dzmax = max(global_dzmax, DZ_visible.max()) + + # Apply global y-limits to ALL panels explicitly (sharey may not propagate properly) + if global_ymax > global_ymin: + yrange = global_ymax - global_ymin + ypad = max(yrange * 0.05, 1.0) + ylim_upper = (global_ymin - ypad - max_tick_space, global_ymax + ypad) + else: + # Fallback to full range + if not Page.plotStyle.get('flTicks', False): + ylim_upper = (-max_tick_space, 102) + else: + ylim_upper = (-1, 102) + if global_dzmax > global_dzmin: + dzrange = global_dzmax - global_dzmin + dzpad = max(dzrange * 0.05, 0.5) + ylim_lower = (global_dzmin - dzpad, global_dzmax + dzpad) + else: + ylim_lower = (DZmin, DZmax) + + # Set y-limits on ALL panels + for i in range(Page.groupN): + up, down = adjustDim(i, Page.groupN) + Plots[up].set_ylim(ylim_upper) + Plots[down].set_ylim(ylim_lower) + try: # try used as in PWDR menu not Groups # Not sure if this does anything G2frame.dataWindow.moveTickLoc.Enable(False) @@ -2303,6 +2475,12 @@ def drawTicks(Phases,phaseList,group=False): # G2frame.dataWindow.moveDiffCurve.Enable(True) except: pass + # Save the current x-limits for GROUP plots so they can be restored after refinement (MG/Cl Sonnet) + # When sharedX is enabled in Q/d-space, all panels share the same x-range + if (plotOpt['sharedX'] and + (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): + up,down = adjustDim(0,Page.groupN) + G2frame.groupXlim = Plots[up].get_xlim() Page.canvas.draw() return elif G2frame.Weight and not G2frame.Contour: From 3dd616bf4491b5b2afed84e3545f2d01455c5bbd Mon Sep 17 00:00:00 2001 From: BHT Date: Sun, 28 Dec 2025 20:12:14 -0600 Subject: [PATCH 26/30] closer to PR #285: preserves plot x-range in sharedX mode, but not y-scaling across refinements --- GSASII/GSASIIpwdplot.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 9b76d026..7d8ac20f 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -1503,13 +1503,13 @@ def refPlotUpdate(Histograms,cycle=None,restore=False): updates the curves, not the reflection marks or the legend. It should be called with restore=True to reset plotting parameters after the refinement is done. + + Note that the Page.plotStyle values are stashed in G2frame + to be restored later ''' if restore: (G2frame.SinglePlot,G2frame.Contour,G2frame.Weight, - G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'], - Page.plotStyle['qPlot'],Page.plotStyle['dPlot']) = savedSettings - # Also save to G2frame so settings survive Page recreation during ResetPlots (MG/Cl Sonnet) - G2frame.savedPlotStyle = copy.copy(Page.plotStyle) + G2frame.plusPlot,G2frame.SubBack,G2frame.savedPlotStyle) = savedSettings return if plottingItem not in Histograms: @@ -1832,19 +1832,15 @@ def onGroupXlimChanged(ax): if G2frame.Contour: publish = None new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab('Powder Patterns','mpl') + if hasattr(G2frame, 'savedPlotStyle'): + Page.plotStyle.update(G2frame.savedPlotStyle) + del G2frame.savedPlotStyle # do this only once 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 - - # Restore saved plot style settings (qPlot, dPlot, logPlot) if they were preserved - # across a refinement cycle. These get saved in refPlotUpdate(restore=True) and - # need to be applied here because Page may have been recreated by ResetPlots. (based on MG/Cl Sonnet) - if hasattr(G2frame, 'savedPlotStyle'): - Page.plotStyle.update(G2frame.savedPlotStyle) - del G2frame.savedPlotStyle # Clear after applying if G2frame.ifSetLimitsMode and G2frame.GPXtree.GetItemText(G2frame.GPXtree.GetSelection()) == 'Limits': # note mode @@ -1912,8 +1908,8 @@ def onGroupXlimChanged(ax): plottingItem = G2frame.GPXtree.GetItemText(G2frame.PatternId) # save settings to be restored after refinement with repPlotUpdate({},restore=True) savedSettings = (G2frame.SinglePlot,G2frame.Contour,G2frame.Weight, - G2frame.plusPlot,G2frame.SubBack,Page.plotStyle['logPlot'], - Page.plotStyle['qPlot'],Page.plotStyle['dPlot']) + G2frame.plusPlot,G2frame.SubBack, + copy.deepcopy(Page.plotStyle)) G2frame.SinglePlot = True G2frame.Contour = False G2frame.Weight = True @@ -2321,13 +2317,11 @@ def onGroupXlimChanged(ax): 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') + GS_kw = {'height_ratios':[4, 1]} #Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True, # gridspec_kw=GS_kw) # Don't use sharey='row' when sharedX - we'll manage y-limits manually @@ -2335,6 +2329,9 @@ def onGroupXlimChanged(ax): Plots = Page.figure.subplots(2,Page.groupN,sharex=True, gridspec_kw=GS_kw) else: + # apportion axes lengths making some plots bigger so that initially units are equal + xfrac = [(gXmax[i]-gXmin[i])/totalrange for i in range(Page.groupN)] + GS_kw = {'height_ratios':[4, 1], 'width_ratios':xfrac,} 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., From 6c64f45f95f61a7a64766f55461e67d96540c850 Mon Sep 17 00:00:00 2001 From: BHT Date: Thu, 1 Jan 2026 20:15:51 -0600 Subject: [PATCH 27/30] Add routines to save/restore limits on all plot axes; restore limits after refinement; fix reset of limits on w key; cleanup plot margins --- GSASII/GSASIIdataGUI.py | 4 ++- GSASII/GSASIIplot.py | 51 ++++++++++++++++++++++++++++-- GSASII/GSASIIpwdplot.py | 70 ++++++++++++++++++++++++----------------- 3 files changed, 92 insertions(+), 33 deletions(-) diff --git a/GSASII/GSASIIdataGUI.py b/GSASII/GSASIIdataGUI.py index c6facee6..0a4707a0 100644 --- a/GSASII/GSASIIdataGUI.py +++ b/GSASII/GSASIIdataGUI.py @@ -5538,6 +5538,8 @@ def OnRefine(self,event): self.SaveTreeSetting() # save the current tree selection self.GPXtree.SaveExposedItems() # save the exposed/hidden tree items if self.PatternId and self.GPXtree.GetItemText(self.PatternId).startswith('PWDR '): + # true when a pattern is selected for plotting, which includes + # when a group is selected. refPlotUpdate = G2pwpl.PlotPatterns(self,refineMode=True) # prepare for plot updating else: refPlotUpdate = None @@ -9182,7 +9184,7 @@ def OnShowShift(event): # if GSASIIpath.GetConfigValue('debug'): # print('Debug: reloading',G2gr) # from importlib import reload - # reload(G2G) + # reload(G2pwpl) # reload(G2gr) G2gr.UpdateGroup(G2frame,item) elif GSASIIpath.GetConfigValue('debug'): diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index 7eaed855..381495be 100644 --- a/GSASII/GSASIIplot.py +++ b/GSASII/GSASIIplot.py @@ -325,6 +325,7 @@ def __init__(self,parent,id=-1,G2frame=None): self.allowZoomReset = True # this indicates plot should be updated not initialized # (BHT: should this be in tabbed panel rather than here?) self.lastRaisedPlotTab = None + self.savedPlotLims = None def OnNotebookKey(self,event): '''Called when a keystroke event gets picked up by the notebook window @@ -395,7 +396,7 @@ def GetTabIndex(self,label): # if plotNum is not None: # wx.CallAfter(self.SetSelectionNoRefresh,plotNum) - def FindPlotTab(self,label,Type,newImage=True): + def FindPlotTab(self,label,Type,newImage=True,saveLimits=False): '''Open a plot tab for initial plotting, or raise the tab if it already exists Set a flag (Page.plotInvalid) that it has been redrawn Record the name of the this plot in self.lastRaisedPlotTab @@ -409,6 +410,8 @@ def FindPlotTab(self,label,Type,newImage=True): :param bool newImage: forces creation of a new graph for matplotlib plots only (defaults as True) + :param bool saveLimits: When True, limits for all MPL axes (plots) + are saved in self.savedPlotLims. :returns: new,plotNum,Page,Plot,limits where * new: will be True if the tab was just created @@ -417,8 +420,9 @@ def FindPlotTab(self,label,Type,newImage=True): the plot appears * Plot: the mpl.Axes object for the graphic (mpl) or the figure for openGL. - * limits: for mpl plots, when a plot already exists, this will be a tuple - with plot scaling. None otherwise. + * limits: for mpl plots, when a plot already exists, this + will be a tuple with plot scaling. None otherwise. Only appropriate + for plots with one set of axes. ''' limits = None Plot = None @@ -426,6 +430,7 @@ def FindPlotTab(self,label,Type,newImage=True): new = False plotNum,Page = self.GetTabIndex(label) if Type == 'mpl' or Type == '3d': + if saveLimits: self.savePlotLims(Page) Axes = Page.figure.get_axes() Plot = Page.figure.gca() #get previous plot limits = [Plot.get_xlim(),Plot.get_ylim()] # save previous limits @@ -469,6 +474,46 @@ def FindPlotTab(self,label,Type,newImage=True): Page.toolbar.enableArrows() # Disable Arrow keys if present return new,plotNum,Page,Plot,limits + def savePlotLims(self,Page): + '''Make a copy of all the current axes in the notebook object + ''' + self.savedPlotLims = [ + [i.get_xlim() for i in Page.figure.get_axes()], + [i.get_ylim() for i in Page.figure.get_axes()]] + #print(f'saved {len(self.savedPlotLims[1])} axes limits') + + def restoreSavedPlotLims(self,Page): + '''Restore the plot limits, when previously saved, and when + ``G2frame.restorePlotLimits`` is set to True, which + is done when ``GSASIIpwdplot.refPlotUpdate`` is called with + ``restore=True``, which indicates that "live plotting" is done. + The restore operation can only be done once, as the limits + are deleted after use in this method. + ''' + if self.savedPlotLims is None: + #print('---- nothing to restore') + return + if not hasattr(self.G2frame,'restorePlotLimits'): + #print('---- restorePlotLimits not set') + return + if not self.G2frame.restorePlotLimits: + #print('---- restorePlotLimits: not yet') + return + savedPlotLims = self.savedPlotLims + axesList = Page.figure.get_axes() + if len(axesList) != len(savedPlotLims[0]): + #print('saved lengths differ',len(axesList),len(savedPlotLims[0])) + return + for i,ax in enumerate(axesList): + ax.set_xlim(savedPlotLims[0][i]) + ax.set_ylim(savedPlotLims[1][i]) + #print(i, + # savedPlotLims[0][i][0],savedPlotLims[0][i][1], + # savedPlotLims[1][i][0],savedPlotLims[1][i][1]) + self.savedPlotLims = None + self.G2frame.restorePlotLimits = False + Page.canvas.draw() + def _addPage(self,name,page): '''Add the newly created page to the notebook and associated lists. diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 7d8ac20f..9186f7eb 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -121,20 +121,6 @@ def ReplotPattern(G2frame,newPlot,plotType,PatternName=None,PickName=None): G2frame.Extinct = [] # array of extinct reflections PlotPatterns(G2frame,plotType=plotType) -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)) - elif Page.plotStyle['dPlot']: - Lines.append(Plot.axvline(G2lat.Pos2dsp(Parms,pos),color=color, - picker=pickrad,linestyle=style)) - else: - Lines.append(Plot.axvline(pos,color=color, - picker=pickrad,linestyle=style)) - def PlotPatterns(G2frame,newPlot=False,plotType='PWDR',data=None, extraKeys=[],refineMode=False,indexFrom='',fromTree=False): '''Powder pattern plotting package - displays single or multiple powder @@ -209,7 +195,7 @@ def OnPlotKeyPress(event): G2frame.SinglePlot = True elif 'PWDR' in plottype: # Turning on Weight plot clears previous limits G2frame.FixedLimits['dylims'] = ['',''] - newPlot = True + #newPlot = True # this resets the x & y limits, not wanted! elif event.key in ['shift+1','!']: # save current plot settings as defaults # shift+1 assumes US keyboard print('saving plotting defaults for',G2frame.GPXtree.GetItemText(G2frame.PatternId)) @@ -1501,15 +1487,19 @@ def onPlotFormat(event): def refPlotUpdate(Histograms,cycle=None,restore=False): '''called to update an existing plot during a Rietveld fit; it only updates the curves, not the reflection marks or the legend. - It should be called with restore=True to reset plotting - parameters after the refinement is done. + After the refinement is complete it is called with restore=True to + reset plotting parameters. - Note that the Page.plotStyle values are stashed in G2frame - to be restored later + Note that the ``Page.plotStyle`` values are stashed in + ``G2frame.savedPlotStyle`` to be restored after FindPlotTab is + called and ``G2frame.restorePlotLimits`` is set so that + saved plot limits will be applied when + ``G2frame.G2plotNB.restoreSavedPlotLims`` is called. ''' if restore: (G2frame.SinglePlot,G2frame.Contour,G2frame.Weight, G2frame.plusPlot,G2frame.SubBack,G2frame.savedPlotStyle) = savedSettings + G2frame.restorePlotLimits = True return if plottingItem not in Histograms: @@ -1724,11 +1714,15 @@ def drawTicks(Phases,phaseList,group=False): picker=3., label='_FLT_'+phase,lw=0.5) - # Callback used to update y-limits when user zooms interactively (MG/Cl Sonnet) + # Callback used to update y-limits when user zooms interactively (from MG/Cl Sonnet) def onGroupXlimChanged(ax): - '''Callback to update y-limits for all panels when x-range changes. + '''Callback to update y-limits for all panels in group plot when x-range changes. We calculate the global y-range across all panels for the visible x-range, then explicitly set y-limits on ALL panels. + + This currently allows the y-limit to be set manually for one plot. If + a second one is changed, the previous manual change is lost. + I wonder if we can identify which plots have manual changes. ''' xlim = ax.get_xlim() # Save x-limits for persistence across refinements @@ -1831,10 +1825,11 @@ def onGroupXlimChanged(ax): publish = None if G2frame.Contour: publish = None - new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab('Powder Patterns','mpl') + new,plotNum,Page,Plot,limits = G2frame.G2plotNB.FindPlotTab( + 'Powder Patterns','mpl',saveLimits=refineMode) if hasattr(G2frame, 'savedPlotStyle'): Page.plotStyle.update(G2frame.savedPlotStyle) - del G2frame.savedPlotStyle # do this only once + del G2frame.savedPlotStyle # do this only once & after Page is defined Page.toolbar.setPublish(publish) Page.toolbar.arrows['_groupMode'] = None # if we are changing histogram types (including group to individual, reset plot) @@ -2338,13 +2333,12 @@ def onGroupXlimChanged(ax): right=.99,top=1.-3/200.,hspace=0,wspace=0) # Connect callback for all panels when sharedX is enabled + up,down = adjustDim(0,Page.groupN) if (plotOpt['sharedX'] and (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): - up, down = adjustDim(0, Page.groupN) Plots[up].callbacks.connect('xlim_changed', onGroupXlimChanged) # 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: @@ -2479,7 +2473,8 @@ def onGroupXlimChanged(ax): up,down = adjustDim(0,Page.groupN) G2frame.groupXlim = Plots[up].get_xlim() Page.canvas.draw() - return + wx.CallLater(100,G2frame.G2plotNB.restoreSavedPlotLims,Page) # restore limits to previous, if saved + return # end of group plot 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],} @@ -2489,10 +2484,10 @@ def onGroupXlimChanged(ax): # Plot,Plot1 = MPLsubplots(Page.figure, 2, 1, sharex=True, gridspec_kw=GS_kw) Plot1.set_ylabel(r'$\mathsf{\Delta(I)/\sigma(I)}$',fontsize=16) Plot1.set_xlabel(xLabel,fontsize=16) - Page.figure.subplots_adjust(left=16/100.,bottom=16/150., - right=.98,top=1.-16/200.,hspace=0) else: Plot.set_xlabel(xLabel,fontsize=16) + Page.figure.subplots_adjust(left=8/100.,bottom=16/150., + right=.98,top=1.-8/100.,hspace=0,wspace=0) if not G2frame.Contour: Page.toolbar.enableArrows('',(PlotPatterns,G2frame)) if G2frame.Weight and G2frame.Contour: @@ -3293,7 +3288,10 @@ def onGroupXlimChanged(ax): G2frame.dataWindow.moveTickSpc.Enable(True) if DifLine[0]: G2frame.dataWindow.moveDiffCurve.Enable(True) - if refineMode: return refPlotUpdate + if refineMode: + return refPlotUpdate + else: + wx.CallLater(100,G2frame.G2plotNB.restoreSavedPlotLims,Page) # restore limits to previous, if saved def PublishRietveldPlot(G2frame,Pattern,Plot,Page,reuse=None): '''Creates a window to show a customizable "Rietveld" plot. Exports that @@ -4810,3 +4808,17 @@ def StyleChange(*args): mainSizer.Fit(dlg) dlg.ShowModal() StyleChange() + +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)) + elif Page.plotStyle['dPlot']: + Lines.append(Plot.axvline(G2lat.Pos2dsp(Parms,pos),color=color, + picker=pickrad,linestyle=style)) + else: + Lines.append(Plot.axvline(pos,color=color, + picker=pickrad,linestyle=style)) From 20f40b79b4338047caaba5ba17a6a93948b7fd11 Mon Sep 17 00:00:00 2001 From: BHT Date: Fri, 2 Jan 2026 08:58:45 -0600 Subject: [PATCH 28/30] fix problem where use of thin-long tickmarks rescaled x-axis --- GSASII/GSASIIpwdplot.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index 9186f7eb..f29388e9 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -1705,6 +1705,9 @@ def drawTicks(Phases,phaseList,group=False): # 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 + # axvline changes plot limits, triggering onGroupXlimChanged + # so turn that off for now. + G2frame.stop_onGroupXlimChanged = True 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, @@ -1713,6 +1716,7 @@ def drawTicks(Phases,phaseList,group=False): Plot.axvline(xt,color=plcolor, picker=3., label='_FLT_'+phase,lw=0.5) + del G2frame.stop_onGroupXlimChanged # Callback used to update y-limits when user zooms interactively (from MG/Cl Sonnet) def onGroupXlimChanged(ax): @@ -1720,10 +1724,18 @@ def onGroupXlimChanged(ax): We calculate the global y-range across all panels for the visible x-range, then explicitly set y-limits on ALL panels. - This currently allows the y-limit to be set manually for one plot. If - a second one is changed, the previous manual change is lost. - I wonder if we can identify which plots have manual changes. + This code behaves a bit funny w/r to zoom or pan of one plot in a group + in that the plot that is modified can have its y-axis range changed, + but if the another plot is changed, then the previous plot is given the + same y-range as all the others, so only one y-range can be changed. + Perhaps this is because the zoom/pan is applied after the changes + here are applied. + + To do better, we probably need a mode that unlocks the coupling of + the y-axes ranges. ''' + if getattr(G2frame,'stop_onGroupXlimChanged',False): + return # disable this routine when needed xlim = ax.get_xlim() # Save x-limits for persistence across refinements if (plotOpt['sharedX'] and From c8e308ba96dde8e4e2b98143573439f21bb73bcb Mon Sep 17 00:00:00 2001 From: BHT Date: Fri, 2 Jan 2026 20:43:33 -0600 Subject: [PATCH 29/30] update pixi to match main --- pixi/pixi.lock | 635 +++++++++++++++++++++++++++++++++++++++++++++++++ pixi/pixi.toml | 4 + 2 files changed, 639 insertions(+) diff --git a/pixi/pixi.lock b/pixi/pixi.lock index 277672e8..8584f3a0 100644 --- a/pixi/pixi.lock +++ b/pixi/pixi.lock @@ -3,6 +3,8 @@ environments: default: channels: - url: https://conda.anaconda.org/conda-forge/ + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -231,15 +233,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.5-hf13058a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h3b512aa_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h8f0d4bb_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py313h8715ba9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_hc369343_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-hc73cdc9_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h7e5c614_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx-19.1.7-default_h1c12a56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_impl_osx-64-19.1.7-hb295874_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-19.1.7-h7e5c614_28.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compilers-1.11.0-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py313hc551f4f_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/crc32c-2.7.1-py313h6865ccc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cython-3.1.4-py313ha8e042b_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda @@ -248,9 +265,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.60.1-py313h0f4d31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.11.0-h9ab62e8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.1-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-14.3.0-hcc3c99d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-14.3.0-h94fe04d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-14.3.0-h3223c34_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.12-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.45-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/h5py-3.14.0-nompi_py313h5e6d4bd_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda @@ -263,10 +285,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.37.0-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.9-py313hb91e98b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.17-h72f5680_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_h466f870_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hcca01a6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.4-ha6bc127_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-37_he492b99_openblas.conda @@ -274,8 +299,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-37_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_hc369343_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.14.1-h5dec5d8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-19.1.7-h707e725_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.24-hcc1b750_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda @@ -284,16 +312,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.1-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.1-h6912278_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h306097a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-14.3.0-h660b60f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-h336fb69_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-37_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm20-20.1.8-h56e7563_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h83c2472_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.50-h84aeda2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-haa3b502_0.conda @@ -303,11 +334,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.0-h23bb396_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.2-h472b3d1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.6-py313h4ad75b8_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/mesalib-25.0.5-hcf854c5_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.9.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.1.2-py313h5eff275_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -350,13 +385,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.16.2-py313h61f8160_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-14.5-hbf94ba6_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/seekpath-2.1.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/smmap-5.0.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spglib-2.6.0-py313h2088a77_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spirv-tools-2025.4-hcb651aa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h8d8e812_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda @@ -380,6 +418,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zarr-3.1.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda osx-arm64: @@ -742,6 +781,8 @@ environments: py310: channels: - url: https://conda.anaconda.org/conda-forge/ + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -969,14 +1010,29 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py310h79c4529_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.5-hf13058a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h3b512aa_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h8f0d4bb_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py310h8970a1e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_hc369343_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-hc73cdc9_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h7e5c614_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx-19.1.7-default_h1c12a56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_impl_osx-64-19.1.7-hb295874_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-19.1.7-h7e5c614_28.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compilers-1.11.0-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.2-py310hf166250_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cython-3.1.4-py310h229d7d5_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda @@ -984,9 +1040,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fasteners-0.19-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.60.1-py310hd951482_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.11.0-h9ab62e8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.1-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-14.3.0-hcc3c99d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-14.3.0-h94fe04d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-14.3.0-h3223c34_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.12-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.45-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/h5py-3.14.0-nompi_py310h4c08e8d_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda @@ -999,10 +1060,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.37.0-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.9-py310hfcdb090_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.17-h72f5680_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_h466f870_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hcca01a6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.4-ha6bc127_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-37_he492b99_openblas.conda @@ -1010,8 +1074,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-37_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_hc369343_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.14.1-h5dec5d8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-19.1.7-h707e725_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.24-hcc1b750_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda @@ -1020,15 +1087,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.1-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.1-h6912278_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h306097a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-14.3.0-h660b60f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-h336fb69_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-37_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm20-20.1.8-h56e7563_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h83c2472_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.50-h84aeda2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-haa3b502_0.conda @@ -1038,11 +1108,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.0-h23bb396_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.2-h472b3d1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.6-py310h2102b89_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/mesalib-25.0.5-hcf854c5_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.9.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.1.2-py310h8cf47bc_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -1084,13 +1158,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.15.2-py310hef62574_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-14.5-hbf94ba6_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/seekpath-2.1.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/smmap-5.0.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spglib-2.6.0-py310h547be3f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spirv-tools-2025.4-hcb651aa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h8d8e812_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda @@ -1113,6 +1190,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxshmfence-1.3.3-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zarr-2.18.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py310h3aa7efa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda osx-arm64: @@ -1467,6 +1545,8 @@ environments: py311: channels: - url: https://conda.anaconda.org/conda-forge/ + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -1697,15 +1777,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311h7b20566_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.5-hf13058a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h3b512aa_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h8f0d4bb_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py311h8ebb5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_hc369343_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-hc73cdc9_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h7e5c614_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx-19.1.7-default_h1c12a56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_impl_osx-64-19.1.7-hb295874_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-19.1.7-h7e5c614_28.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compilers-1.11.0-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py311hd4d69bb_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/crc32c-2.7.1-py311h179db11_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cython-3.1.4-py311h8726017_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda @@ -1714,9 +1809,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.60.1-py311he13f9b5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.11.0-h9ab62e8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.1-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-14.3.0-hcc3c99d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-14.3.0-h94fe04d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-14.3.0-h3223c34_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.12-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.45-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/h5py-3.14.0-nompi_py311h9f650d9_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda @@ -1729,10 +1829,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.37.0-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.9-py311ha94bed4_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.17-h72f5680_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_h466f870_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hcca01a6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.4-ha6bc127_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-37_he492b99_openblas.conda @@ -1740,8 +1843,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-37_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_hc369343_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.14.1-h5dec5d8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-19.1.7-h707e725_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.24-hcc1b750_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda @@ -1750,15 +1856,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.1-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.1-h6912278_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h306097a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-14.3.0-h660b60f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-h336fb69_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-37_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm20-20.1.8-h56e7563_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h83c2472_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.50-h84aeda2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-haa3b502_0.conda @@ -1768,11 +1877,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.0-h23bb396_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.2-h472b3d1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.6-py311h48d7e91_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/mesalib-25.0.5-hcf854c5_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.9.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.1.2-py311haec20ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -1815,13 +1928,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.16.2-py311h32c7e5c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-14.5-hbf94ba6_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/seekpath-2.1.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/smmap-5.0.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spglib-2.6.0-py311h9dffb50_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spirv-tools-2025.4-hcb651aa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h8d8e812_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda @@ -1846,6 +1962,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zarr-3.1.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py311h62e9434_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda osx-arm64: @@ -2208,6 +2325,8 @@ environments: py312: channels: - url: https://conda.anaconda.org/conda-forge/ + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -2438,15 +2557,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py312h462f358_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.5-hf13058a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h3b512aa_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h8f0d4bb_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py312hf9bc6d9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_hc369343_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-hc73cdc9_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h7e5c614_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx-19.1.7-default_h1c12a56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_impl_osx-64-19.1.7-hb295874_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-19.1.7-h7e5c614_28.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compilers-1.11.0-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py312hedd4973_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/crc32c-2.7.1-py312h6efa6bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cython-3.1.4-py312hfbda96f_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda @@ -2455,9 +2589,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.60.1-py312hacf3034_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.11.0-h9ab62e8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.1-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-14.3.0-hcc3c99d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-14.3.0-h94fe04d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-14.3.0-h3223c34_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.12-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.45-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/h5py-3.14.0-nompi_py312ha1048bf_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda @@ -2470,10 +2609,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.37.0-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.9-py312hef387a8_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.17-h72f5680_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_h466f870_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hcca01a6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.4-ha6bc127_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-37_he492b99_openblas.conda @@ -2481,8 +2623,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-37_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_hc369343_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.14.1-h5dec5d8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-19.1.7-h707e725_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.24-hcc1b750_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda @@ -2491,15 +2636,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.1-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.1-h6912278_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h306097a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-14.3.0-h660b60f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-h336fb69_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-37_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm20-20.1.8-h56e7563_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h83c2472_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.50-h84aeda2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-haa3b502_0.conda @@ -2509,11 +2657,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.0-h23bb396_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.2-h472b3d1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.6-py312h7894933_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/mesalib-25.0.5-hcf854c5_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.9.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.1.2-py312hd099df3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -2556,13 +2708,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.16.2-py312he2acf2f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-14.5-hbf94ba6_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/seekpath-2.1.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/smmap-5.0.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spglib-2.6.0-py312hbb39f8f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spirv-tools-2025.4-hcb651aa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h8d8e812_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda @@ -2587,6 +2742,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zarr-3.1.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py312h01f6755_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda osx-arm64: @@ -2949,6 +3105,8 @@ environments: py313: channels: - url: https://conda.anaconda.org/conda-forge/ + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -3177,15 +3335,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.34.5-hf13058a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h3b512aa_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h8f0d4bb_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-2.0.0-py313h8715ba9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_hc369343_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-hc73cdc9_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h7e5c614_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx-19.1.7-default_h1c12a56_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_impl_osx-64-19.1.7-hb295874_28.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-19.1.7-h7e5c614_28.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compilers-1.11.0-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.3.3-py313hc551f4f_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/crc32c-2.7.1-py313h6865ccc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/cython-3.1.4-py313ha8e042b_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda @@ -3194,9 +3367,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.60.1-py313h0f4d31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.11.0-h9ab62e8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.14.1-h694c41f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-14.3.0-hcc3c99d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-14.3.0-h94fe04d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-14.3.0-h3223c34_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.12-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.45-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/h5py-3.14.0-nompi_py313h5e6d4bd_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/hdf5-1.14.6-nompi_hc8237f9_103.conda @@ -3209,10 +3387,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.37.0-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.9-py313hb91e98b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.17-h72f5680_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_h466f870_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hcca01a6_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libaec-1.1.4-ha6bc127_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-37_he492b99_openblas.conda @@ -3220,8 +3401,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.1.0-h1c43f85_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-37_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_hc369343_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.14.1-h5dec5d8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-19.1.7-h707e725_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.24-hcc1b750_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-h10d778d_2.conda @@ -3230,16 +3414,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype-2.14.1-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libfreetype6-2.14.1-h6912278_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h306097a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-14.3.0-h660b60f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-h336fb69_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.1.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-37_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm20-20.1.8-h56e7563_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.67.0-h3338091_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.30-openmp_h83c2472_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.50-h84aeda2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.11.1-hed3591d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.7.1-haa3b502_0.conda @@ -3249,11 +3436,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.0-h23bb396_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-21.1.2-h472b3d1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.10.6-py313h4ad75b8_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/mesalib-25.0.5-hcf854c5_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.9.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.1.2-py313h5eff275_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -3296,13 +3487,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.16.2-py313h61f8160_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-14.5-hbf94ba6_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/seekpath-2.1.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/smmap-5.0.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spglib-2.6.0-py313h2088a77_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/spirv-tools-2025.4-hcb651aa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h8d8e812_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda @@ -3326,6 +3520,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zarr-3.1.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda osx-arm64: @@ -4211,6 +4406,18 @@ packages: license_family: MIT size: 179696 timestamp: 1744128058734 +- conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda + sha256: 2bd1cf3d26789b7e1d04e914ccd169bd618fceed68abf7b6a305266b88dcf861 + md5: 2b23ec416cef348192a5a17737ddee60 + depends: + - cctools >=949.0.1 + - clang_osx-64 19.* + - ld64 >=530 + - llvm-openmp + license: BSD-3-Clause + license_family: BSD + size: 6695 + timestamp: 1753098825695 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-compiler-1.11.0-h61f9b84_0.conda sha256: b51bd1551cfdf41500f732b4bd1e4e70fb1e74557165804a648f32fa9c671eec md5: 148516e0c9edf4e9331a4d53ae806a9b @@ -4291,6 +4498,17 @@ packages: license: LGPL-2.1-only or MPL-1.1 size: 978114 timestamp: 1741554591855 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_3.conda + sha256: b07dfc5023c9bfa0abb6e77e1bed86a7c6bde6b737addedb85129e808173ff29 + md5: acdbe6dbefafb1eba531160c68328456 + depends: + - cctools_impl_osx-64 1030.6.3 llvm19_1_h3b512aa_3 + - ld64 956.6 llvm19_1_hc3792c1_3 + - libllvm19 >=19.1.7,<19.2.0a0 + license: APSL-2.0 + license_family: Other + size: 24286 + timestamp: 1767114529119 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1024.3-hd01ab73_1.conda sha256: 5f155c8ef4e2b1a3a9a0cf9544defb439f94e032147e515464732cc027f842ba md5: 50f17681b331bc28cf1d55df2b2414dd @@ -4302,6 +4520,37 @@ packages: license_family: Other size: 20699 timestamp: 1756645577110 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h3b512aa_3.conda + sha256: 60dfb2c7b685aeb867c7b359ae0c7e2147b90cc15b0397040f256d17e62c4d5b + md5: a52bbde5581ef42bdcbf050ca8c83646 + depends: + - __osx >=10.13 + - ld64_osx-64 >=956.6,<956.7.0a0 + - libcxx + - libllvm19 >=19.1.7,<19.2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-tools 19.1.* + - sigtool-codesign + constrains: + - ld64 956.6.* + - cctools 1030.6.3.* + - clang 19.1.* + license: APSL-2.0 + license_family: Other + size: 742929 + timestamp: 1767114490312 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h8f0d4bb_3.conda + sha256: 060b807b306f0e3476db009a2b218f419977740d8708975fabceb138d135aec3 + md5: 09ff64ce958395c9dd58c847d709d28d + depends: + - cctools_impl_osx-64 1030.6.3 llvm19_1_h3b512aa_3 + - ld64_osx-64 956.6 llvm19_1_h466f870_3 + constrains: + - cctools 1030.6.3.* + license: APSL-2.0 + license_family: Other + size: 23229 + timestamp: 1767114532748 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_osx-arm64-1024.3-haeb51d2_1.conda sha256: ea77d8790feb469a1440a330bca1641194b5de21ba8ccdf9c7a05e355e1f153e md5: bdecbcc574c1ae36f164346368404f63 @@ -4554,6 +4803,15 @@ packages: license_family: MIT size: 51033 timestamp: 1754767444665 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_5.conda + sha256: 5bcabcc3a5689bc47dbd6a58a3a785f8fe3f4e91410a299392d9cdf7ae7c16d6 + md5: 5bd21a5ea37ab0fbe1d9cbba4e0e7c80 + depends: + - clang-19 19.1.7 default_hc369343_5 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 24455 + timestamp: 1759436889569 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-19.1.7-default_h474c9e2_3.conda sha256: e2061f7a16ae5a381d7f66b5ccd91a92b7ad6ac356f1d2ee2934015581eb3bf7 md5: b5a92027d9f6136108beeda7b6edfec9 @@ -4563,6 +4821,18 @@ packages: license_family: Apache size: 24360 timestamp: 1747709802932 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_hc369343_5.conda + sha256: 2631c79a027ee8b9c2d4d0a458f0588e8fe03fe1dfb3e3bcd47e7b0f4d0d2175 + md5: b37d33a750251c79214c812eca726241 + depends: + - __osx >=10.13 + - libclang-cpp19.1 19.1.7 default_hc369343_5 + - libcxx >=19.1.7 + - libllvm19 >=19.1.7,<19.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 765727 + timestamp: 1759436729883 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-19-19.1.7-default_hf90f093_3.conda sha256: c7f21028560ee5cc72d882d930b56c8521126987308819c37b97e1760d3e39bc md5: 8ea1b606f2c5cb255b53c868d1eb8dbc @@ -4575,6 +4845,19 @@ packages: license_family: Apache size: 760894 timestamp: 1747709675681 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-hc73cdc9_28.conda + sha256: a393747ffc868fe4e51b4b20570bab597024e49798568647e66340bc19d1986f + md5: 11b55abbdae1166253b57800d267e748 + depends: + - cctools_impl_osx-64 + - clang 19.1.7.* + - compiler-rt 19.1.7.* + - ld64_osx-64 + - llvm-tools 19.1.7.* + license: BSD-3-Clause + license_family: BSD + size: 17860 + timestamp: 1765841971797 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_impl_osx-arm64-19.1.7-h76e6a08_25.conda sha256: ee3c0976bde0ac19f84d29213ea3d9c3fd9c56d56c33ee471a8680cf69307ce1 md5: a4e2f211f7c3cf582a6cb447bee2cad9 @@ -4588,6 +4871,17 @@ packages: license_family: BSD size: 18159 timestamp: 1748575942374 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h7e5c614_28.conda + sha256: defe8d27247c063b7273b11e6dccbd9b55fb59686dea17e2bd54f138db4b7a32 + md5: 9e78ad15b683ff11b0e18a971ab78a54 + depends: + - cctools_osx-64 + - clang_impl_osx-64 19.1.7 hc73cdc9_28 + - sdkroot_env_osx-64 + license: BSD-3-Clause + license_family: BSD + size: 20764 + timestamp: 1765841975402 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_osx-arm64-19.1.7-h07b0088_25.conda sha256: 92a45a972af5eba3b7fca66880c3d5bdf73d0c69a3e21d1b3999fb9b5be1b323 md5: 1b53cb5305ae53b5aeba20e58c625d96 @@ -4597,6 +4891,16 @@ packages: license_family: BSD size: 21337 timestamp: 1748575949016 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx-19.1.7-default_h1c12a56_5.conda + sha256: 6553c7b6a898bd00c218656d3438dc3a70f2bb79f795ce461792c55304558af2 + md5: 6b6f3133d60b448c0f12885f53d5ed09 + depends: + - clang 19.1.7 default_h1323312_5 + - libcxx-devel 19.1.* + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 24505 + timestamp: 1759436910517 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx-19.1.7-default_h1ffe849_3.conda sha256: c6094b6c846248930ab2f559b04e14f9d6463e1c88b9d33b479bf27df916d6d7 md5: 8b6dff933df21ccf744b5ecbc9dfd3ab @@ -4607,6 +4911,18 @@ packages: license_family: Apache size: 24486 timestamp: 1747709816351 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_impl_osx-64-19.1.7-hb295874_28.conda + sha256: 9b734bc9a1d3a53b7c7bc82d2989d959dfc4212b76516692b4996038be00f850 + md5: 53ea0e8ae9ed508da86574d64ec2be15 + depends: + - clang_osx-64 19.1.7 h7e5c614_28 + - clangxx 19.1.7.* + - libcxx >=19 + - libllvm19 >=19.1.7,<19.2.0a0 + license: BSD-3-Clause + license_family: BSD + size: 17992 + timestamp: 1765842018470 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_impl_osx-arm64-19.1.7-h276745f_25.conda sha256: b997d325da6ca60e71937b9e28f114893401ca7cf94c4007d744e402a25c2c52 md5: 5eeaa7b2dd32f62eb3beb0d6ba1e664f @@ -4619,6 +4935,18 @@ packages: license_family: BSD size: 18297 timestamp: 1748576000726 +- conda: https://conda.anaconda.org/conda-forge/osx-64/clangxx_osx-64-19.1.7-h7e5c614_28.conda + sha256: 78aba4e421a40442d157b9141d4bf38513d1e2288300efaa8d7e16558d9b6b3f + md5: 302456b38cee4b4b5c8ccd8498605cb6 + depends: + - cctools_osx-64 + - clang_osx-64 19.1.7 h7e5c614_28 + - clangxx_impl_osx-64 19.1.7 hb295874_28 + - sdkroot_env_osx-64 + license: BSD-3-Clause + license_family: BSD + size: 19573 + timestamp: 1765842022184 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_osx-arm64-19.1.7-h07b0088_25.conda sha256: 93801e6e821ae703d03629b1b993dbae1920b80012818edd5fcd18a9055897ce md5: 4e09188aa8def7d8b3ae149aa856c0e5 @@ -4638,6 +4966,17 @@ packages: license_family: BSD size: 27011 timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + sha256: 28e5f0a6293acba68ebc54694a2fc40b1897202735e8e8cbaaa0e975ba7b235b + md5: e6b9e71e5cb08f9ed0185d31d33a074b + depends: + - __osx >=10.13 + - clang 19.1.7.* + - compiler-rt_osx-64 19.1.7.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + size: 96722 + timestamp: 1757412473400 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-19.1.7-hd2aecb6_0.conda sha256: db920f02717984329375c0c188335c23104895b0e5a2da295e475dabe4192c69 md5: 28f46d13b77fcc390c84ca49b68b9ecb @@ -4649,6 +4988,18 @@ packages: license_family: APACHE size: 96534 timestamp: 1736976644597 +- conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + sha256: e6effe89523fc6143819f7a68372b28bf0c176af5b050fe6cf75b62e9f6c6157 + md5: 32deecb68e11352deaa3235b709ddab2 + depends: + - clang 19.1.7.* + constrains: + - compiler-rt 19.1.7 + - clangxx 19.1.7 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + size: 10425780 + timestamp: 1757412396490 - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-19.1.7-h7969c41_0.conda sha256: 6d9701824622609a47aae525d0a527e46dd7bbdc3f5648a3035df177f93d858e md5: bb78d3cc0758bb3fc3cb0fab51ec4424 @@ -4661,6 +5012,17 @@ packages: license_family: APACHE size: 10796006 timestamp: 1736976593839 +- conda: https://conda.anaconda.org/conda-forge/osx-64/compilers-1.11.0-h694c41f_0.conda + sha256: d95722dfe9a45b22fbb4e8d4f9531ac5ef7d829f3bfd2ed399d45d7590681bd0 + md5: 308ed38aeff454285547012272cb59f5 + depends: + - c-compiler 1.11.0 h7a00415_0 + - cxx-compiler 1.11.0 h307afc9_0 + - fortran-compiler 1.11.0 h9ab62e8_0 + license: BSD-3-Clause + license_family: BSD + size: 7509 + timestamp: 1753098827841 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compilers-1.11.0-hce30654_0.conda sha256: 01f02e9baa51ef2debfdc55df57ea6a7fce9b5fb9356c3267afb517e1dc4363a md5: aac0d423ecfd95bde39582d0de9ca657 @@ -5033,6 +5395,16 @@ packages: license: LGPL-2.1-or-later size: 53045 timestamp: 1756602227299 +- conda: https://conda.anaconda.org/conda-forge/osx-64/cxx-compiler-1.11.0-h307afc9_0.conda + sha256: d6976f8d8b51486072abfe1e76a733688380dcbd1a8e993a43d59b80f7288478 + md5: 463bb03bb27f9edc167fb3be224efe96 + depends: + - c-compiler 1.11.0 h7a00415_0 + - clangxx_osx-64 19.* + license: BSD-3-Clause + license_family: BSD + size: 6732 + timestamp: 1753098827160 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cxx-compiler-1.11.0-h88570a1_0.conda sha256: 99800d97a3a2ee9920dfc697b6d4c64e46dc7296c78b1b6c746ff1c24dea5e6c md5: 043afed05ca5a0f2c18252ae4378bdee @@ -5635,6 +6007,19 @@ packages: license_family: MIT size: 2490458 timestamp: 1756328983115 +- conda: https://conda.anaconda.org/conda-forge/osx-64/fortran-compiler-1.11.0-h9ab62e8_0.conda + sha256: 21e2ec84b7b152daa22fa8cc98be5d4ba9ff93110bbc99e05dfd45eb6f7e8b77 + md5: ee1a3ecd568a695ea16747198df983eb + depends: + - cctools >=949.0.1 + - gfortran + - gfortran_osx-64 14.* + - ld64 >=530 + - llvm-openmp + license: BSD-3-Clause + license_family: BSD + size: 6749 + timestamp: 1753098826431 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fortran-compiler-1.11.0-h81a4f41_0.conda sha256: 1a030edc0e79e4e7a4ed9736ec6925303940148d00f20faf3a7abf0565de181e md5: d221c62af175b83186f96d8b0880bff6 @@ -5730,6 +6115,17 @@ packages: license_family: LGPL size: 579311 timestamp: 1754960116630 +- conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran-14.3.0-hcc3c99d_0.conda + sha256: e99605f629a4baceba28bfda6305f6898a42a1a05a5833a92808b737457a0711 + md5: 6077316830986f224d771f9e6ba5c516 + depends: + - cctools + - gfortran_osx-64 14.3.0 + - ld64 + license: GPL-3.0-or-later WITH GCC-exception-3.1 + license_family: GPL + size: 32631 + timestamp: 1751220511321 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gfortran-14.3.0-h3ef1dbf_0.conda sha256: cfdf9b4dd37ddfc15ea1c415619d4137004fa135d7192e5cdcea8e3944dc9df7 md5: e148e0bc9bbc90b6325a479a5501786d @@ -5752,6 +6148,27 @@ packages: license_family: BSD size: 756507 timestamp: 1740241113441 +- conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-14.3.0-h94fe04d_1.conda + sha256: 7a2a952ffee0349147768c1d6482cb0933349017056210118ebd5f0fb688f5d5 + md5: 1a81d1a0cb7f241144d9f10e55a66379 + depends: + - __osx >=10.13 + - cctools_osx-64 + - clang + - gmp >=6.3.0,<7.0a0 + - isl 0.26.* + - libcxx >=17 + - libgfortran-devel_osx-64 14.3.0.* + - libgfortran5 >=14.3.0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - mpc >=1.3.1,<2.0a0 + - mpfr >=4.2.1,<5.0a0 + - zlib + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 23083852 + timestamp: 1759709470800 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gfortran_impl_osx-arm64-14.3.0-h969232b_1.conda sha256: 75b2c2d7718ac3084eea3b9b7a49203437bfa04806229a567d781ab827f199d9 md5: 2eb14ea8bce422d2f481cea83ec71f8d @@ -5786,6 +6203,22 @@ packages: license_family: GPL size: 14926629 timestamp: 1740241036024 +- conda: https://conda.anaconda.org/conda-forge/osx-64/gfortran_osx-64-14.3.0-h3223c34_0.conda + sha256: 14014ad4d46e894645979cbad42dd509482172095c756bdb5474918e0638bd57 + md5: 979b3c36c57d31e1112fa1b1aec28e02 + depends: + - cctools_osx-64 + - clang + - clang_osx-64 + - gfortran_impl_osx-64 14.3.0 + - ld64_osx-64 + - libgfortran + - libgfortran-devel_osx-64 14.3.0 + - libgfortran5 >=14.3.0 + license: GPL-3.0-or-later WITH GCC-exception-3.1 + license_family: GPL + size: 35767 + timestamp: 1751220493617 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gfortran_osx-arm64-14.3.0-h3c33bd0_0.conda sha256: 2644e5f4b4eed171b12afb299e2413be5877db92f30ec03690621d1ae648502c md5: 8db8c0061c0f3701444b7b9cc9966511 @@ -5845,6 +6278,15 @@ packages: license: LGPL-2.1-or-later size: 116716 timestamp: 1754315054614 +- conda: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda + sha256: 75aa5e7a875afdcf4903b7dc98577672a3dc17b528ac217b915f9528f93c85fc + md5: 427101d13f19c4974552a4e5b072eef1 + depends: + - __osx >=10.13 + - libcxx >=16 + license: GPL-2.0-or-later OR LGPL-3.0-or-later + size: 428919 + timestamp: 1718981041839 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda sha256: 76e222e072d61c840f64a44e0580c2503562b009090f55aa45053bf1ccb385dd md5: eed7278dfbab727b56f2c0b64330814b @@ -6428,6 +6870,17 @@ packages: license_family: BSD size: 638940 timestamp: 1748711254071 +- conda: https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda + sha256: d39bf147cb9958f197dafa0b8ad8c039b7374778edac05b5c78b712786e305c7 + md5: d06222822a9144918333346f145b68c6 + depends: + - libcxx >=14.0.6 + track_features: + - isl_imath-32 + license: MIT + license_family: MIT + size: 894410 + timestamp: 1680649639107 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/isl-0.26-imath32_h347afa1_101.conda sha256: fc9272371750c56908b8e535755b1e23cf7803a2cc4a7d9ae539347baa14f740 md5: e80e44a3f4862b1da870dc0557f8cf3b @@ -6772,6 +7225,19 @@ packages: license_family: MIT size: 510641 timestamp: 1739161381270 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_3.conda + sha256: fc720deee55e62a21c7b3a86041acce201472731f9db826099f4060e8a92ad78 + md5: 2a1c17d828bd3916f871d9a49432e0a1 + depends: + - ld64_osx-64 956.6 llvm19_1_h466f870_3 + - libllvm19 >=19.1.7,<19.2.0a0 + constrains: + - cctools 1030.6.3.* + - cctools_osx-64 1030.6.3.* + license: APSL-2.0 + license_family: Other + size: 21562 + timestamp: 1767114511590 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-955.13-he86490a_1.conda sha256: 0a86572557ba022dae78eb57ce8d2efd2a00fec595bf56abb733870dda611acf md5: b350b94c4bcf9e420affa09e0ce2ec8a @@ -6785,6 +7251,24 @@ packages: license_family: Other size: 18091 timestamp: 1756645549597 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_h466f870_3.conda + sha256: f3d3eb60f756f9eda6ef9ca89bca372e2785762b31b42f2967253a6a17b1eac8 + md5: 5998e06528c03076d645c5d5c3479b81 + depends: + - __osx >=10.13 + - libcxx + - libllvm19 >=19.1.7,<19.2.0a0 + - sigtool-codesign + - tapi >=1600.0.11.8,<1601.0a0 + constrains: + - ld64 956.6.* + - cctools_impl_osx-64 1030.6.3.* + - cctools 1030.6.3.* + - clang 19.1.* + license: APSL-2.0 + license_family: Other + size: 1115701 + timestamp: 1767114433927 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-955.13-hc42d924_1.conda sha256: 112f4d57c4d451010f94d10c926a2a54f0f1d62e382ac3a0e13c4f611aa3b12d md5: 3cb7f3c67053be4f5b34156562f4f9c6 @@ -7152,6 +7636,17 @@ packages: license_family: BSD size: 70700 timestamp: 1754682490395 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_hc369343_5.conda + sha256: 16ff6eea7319f5e7a8091028e6ed66a33b0ea5a859075354b93674e6f0a1087a + md5: 51c684dbc10be31478e7fc0e85d27bfe + depends: + - __osx >=10.13 + - libcxx >=19.1.7 + - libllvm19 >=19.1.7,<19.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 14856234 + timestamp: 1759436552121 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp19.1-19.1.7-default_hf90f093_3.conda sha256: 581014d18bb6a9c2c7b46ca932b338b54b351bd8e9ccfd5ad665fd2d9810b8d0 md5: 560546d163a6622b494ce92204e67540 @@ -7254,6 +7749,16 @@ packages: license_family: Apache size: 568692 timestamp: 1756698505599 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-devel-19.1.7-h7c275be_2.conda + sha256: 760af3509e723d8ee5a9baa7f923a213a758b3a09e41ffdaf10f3a474898ab3f + md5: 52031c3ab8857ea8bcc96fe6f1b6d778 + depends: + - libcxx >=19.1.7 + - libcxx-headers >=19.1.7,<19.1.8.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 23069 + timestamp: 1764648572536 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-devel-19.1.7-h6dc3340_1.conda sha256: 6dd08a65b8ef162b058dc931aba3bdb6274ba5f05b6c86fbd0c23f2eafc7cc47 md5: 1399af81db60d441e7c6577307d5cf82 @@ -7263,6 +7768,17 @@ packages: license_family: Apache size: 825628 timestamp: 1742451285589 +- conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-19.1.7-h707e725_2.conda + sha256: 36485e6807e03a4f15a8018ec982457a9de0a1318b4b49a44c5da75849dbe24f + md5: de91b5ce46dc7968b6e311f9add055a2 + depends: + - __unix + constrains: + - libcxx-devel 19.1.7 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 830747 + timestamp: 1764647922410 - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda sha256: 8420748ea1cc5f18ecc5068b4f24c7a023cc9b20971c99c824ba10641fb95ddf md5: 64f0c503da58ec25ebd359e4d990afa8 @@ -7622,6 +8138,13 @@ packages: license_family: GPL size: 134383 timestamp: 1756239485494 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-14.3.0-h660b60f_1.conda + sha256: b60e918409b71302ee61b61080b1b254a902c03fbcbb415c81925dc016c5990e + md5: 731190552d91ade042ddf897cfb361aa + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 551342 + timestamp: 1756238221735 - conda: https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-arm64-14.3.0-hc965647_1.conda sha256: f6ecc12e02a30ab7ee7a8b7285e4ffe3c2452e43885ce324b85827b97659a8c8 md5: c1b69e537b3031d0f5af780b432ce511 @@ -7944,6 +8467,20 @@ packages: license_family: BSD size: 82224 timestamp: 1754682540087 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda + sha256: 375a634873b7441d5101e6e2a9d3a42fec51be392306a03a2fa12ae8edecec1a + md5: 05a54b479099676e75f80ad0ddd38eff + depends: + - __osx >=10.13 + - libcxx >=19 + - libxml2 + - libxml2-16 >=2.14.5 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 28801374 + timestamp: 1757354631264 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm19-19.1.7-hc4b4ae8_1.conda sha256: 5a1d3e7505e8ce6055c3aa361ae660916122089a80abfb009d8d4c49238a7ea4 md5: 020aeb16fc952ac441852d8eba2cf2fd @@ -8287,6 +8824,16 @@ packages: license: LGPL-2.1-or-later size: 6543651 timestamp: 1743368725313 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda + sha256: f87b743d5ab11c1a8ddd800dd9357fc0fabe47686068232ddc1d1eed0d7321ec + md5: 3576aba85ce5e9ab15aa0ea376ab864b + depends: + - __osx >=10.13 + - openssl >=3.5.4,<4.0a0 + license: MIT + license_family: MIT + size: 38085 + timestamp: 1767044977731 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da md5: 0b367fad34931cb79e0d6b7e5c06bb1c @@ -8776,6 +9323,22 @@ packages: license_family: APACHE size: 344490 timestamp: 1756145011384 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda + sha256: 8d042ee522bc9eb12c061f5f7e53052aeb4f13e576e624c8bebaf493725b95a0 + md5: 0f79b23c03d80f22ce4fe0022d12f6d2 + depends: + - __osx >=10.13 + - libllvm19 19.1.7 h56e7563_2 + - llvm-tools-19 19.1.7 h879f4bc_2 + constrains: + - llvmdev 19.1.7 + - llvm 19.1.7 + - clang 19.1.7 + - clang-tools 19.1.7 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 87962 + timestamp: 1757355027273 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-19.1.7-hd2aecb6_1.conda sha256: 0537eb46cd766bdae85cbdfc4dfb3a4d70a85c6c088a33722104bbed78256eca md5: b79a1a40211c67a3ae5dbd0cb36604d2 @@ -8792,6 +9355,19 @@ packages: license_family: Apache size: 87945 timestamp: 1737781780073 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + sha256: fd281acb243323087ce672139f03a1b35ceb0e864a3b4e8113b9c23ca1f83bf0 + md5: bf644c6f69854656aa02d1520175840e + depends: + - __osx >=10.13 + - libcxx >=19 + - libllvm19 19.1.7 h56e7563_2 + - libzlib >=1.3.1,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 17198870 + timestamp: 1757354915882 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-19-19.1.7-h87a4c7e_1.conda sha256: 74588508746622baae1bb9c6a69ef571af68dfc7af2bd09546aff26ab3d31764 md5: ebaf5f56104cdb0481fda2a6069f85bf @@ -9402,6 +9978,17 @@ packages: license_family: Proprietary size: 103088799 timestamp: 1753975600547 +- conda: https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda + sha256: dcf91571da6c2f0db96d43a1b639047def05a0e1b6436d42c9129ab14af47b10 + md5: 0520855aaae268ea413d6bc913f1384c + depends: + - __osx >=10.13 + - gmp >=6.3.0,<7.0a0 + - mpfr >=4.2.1,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + size: 107774 + timestamp: 1725629348601 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.3.1-h8f1351a_1.conda sha256: 2700899ad03302a1751dbf2bca135407e470dd83ac897ab91dd8675d4300f158 md5: a5635df796b71f6ca400fc7026f50701 @@ -9413,6 +10000,16 @@ packages: license_family: LGPL size: 104766 timestamp: 1725629165420 +- conda: https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-haed47dc_3.conda + sha256: dddb6721dff05b8dfb654c532725330231fcb81ff1e27d885ee0cdcc9fccf1c4 + md5: d511e58aaaabfc23136880d9956fa7a6 + depends: + - __osx >=10.13 + - gmp >=6.3.0,<7.0a0 + license: LGPL-3.0-only + license_family: LGPL + size: 373396 + timestamp: 1725746891597 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda sha256: 4463e4e2aba7668e37a1b8532859191b4477a6f3602a5d6b4d64ad4c4baaeac5 md5: 4e4ea852d54cc2b869842de5044662fb @@ -12442,6 +13039,13 @@ packages: license_family: BSD size: 15282796 timestamp: 1756530913317 +- conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-14.5-hbf94ba6_4.conda + sha256: e0ec582a2ef7eca39fb40b17753e0a4006c02e794e3fc85ab598931d16ba28d5 + md5: bfc192e9093bd93e38185351be812157 + license: BSD-3-Clause + license_family: BSD + size: 8900 + timestamp: 1764616252089 - conda: https://conda.anaconda.org/conda-forge/noarch/seekpath-2.1.0-pyhd8ed1ab_2.conda sha256: 32a512965d887b2214ba4cb4bb92401a00539695fd7ce56b20786cf487b91052 md5: abe4bef6497cfea3f58566c43868cbe0 @@ -12471,6 +13075,17 @@ packages: license_family: MIT size: 210264 timestamp: 1643442231687 +- conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda + sha256: b89d89d0b62e0a84093205607d071932cca228d4d6982a5b073eec7e765b146d + md5: 1261fc730f1d8af7eeea8a0024b23493 + depends: + - __osx >=10.13 + - libsigtool 0.1.3 hc0f2934_0 + - openssl >=3.5.4,<4.0a0 + license: MIT + license_family: MIT + size: 123083 + timestamp: 1767045007433 - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d md5: 3339e3b65d58accf4ca4fb8748ab16b3 @@ -12823,6 +13438,16 @@ packages: license_family: MIT size: 26988 timestamp: 1733569565672 +- conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h8d8e812_0.conda + sha256: 2602632f7923fd59042a897bfb22f050d78f2b5960d53565eae5fa6a79308caa + md5: aae272355bc3f038e403130a5f6f5495 + depends: + - libcxx >=19.0.0.a0 + - __osx >=10.13 + - ncurses >=6.5,<7.0a0 + license: NCSA + size: 213480 + timestamp: 1762535196805 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1300.6.5-h03f4b80_0.conda sha256: 37cd4f62ec023df8a6c6f9f6ffddde3d6620a83cbcab170a8fff31ef944402e5 md5: b703bc3e6cba5943acf0e5f987b5d0e2 @@ -14252,6 +14877,16 @@ packages: license_family: MIT size: 22963 timestamp: 1749421737203 +- conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda + sha256: 219edbdfe7f073564375819732cbf7cc0d7c7c18d3f546a09c2dfaf26e4d69f3 + md5: c989e0295dcbdc08106fe5d9e935f0b9 + depends: + - __osx >=10.13 + - libzlib 1.3.1 hd23fc13_2 + license: Zlib + license_family: Other + size: 88544 + timestamp: 1727963189976 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda sha256: 58f8860756680a4831c1bf4f294e2354d187f2e999791d53b1941834c4b37430 md5: e3170d898ca6cb48f1bb567afb92f775 diff --git a/pixi/pixi.toml b/pixi/pixi.toml index 7c78c095..3efd41d6 100644 --- a/pixi/pixi.toml +++ b/pixi/pixi.toml @@ -67,6 +67,10 @@ gcc = ">=14.2.0,<14.3" compilers = ">=1.11.0,<2" clang = ">=19.1.7,<22" +[target.osx-64.dependencies] +compilers = ">=1.11.0,<2" +clang = ">=19.1.7,<22" + # for now assume gcc & gfortran already installed in Linux # The list above contains a number of items that are needed only for From 681763bb7ec98c021ac303fb6e9a4f0ef3744fcd Mon Sep 17 00:00:00 2001 From: BHT Date: Sat, 3 Jan 2026 14:42:45 -0600 Subject: [PATCH 30/30] more work on preventing limit resets; this should replace everything in PR #285 --- GSASII/GSASIIplot.py | 16 ++++++++-------- GSASII/GSASIIpwdplot.py | 14 +++++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/GSASII/GSASIIplot.py b/GSASII/GSASIIplot.py index 381495be..df93b1c3 100644 --- a/GSASII/GSASIIplot.py +++ b/GSASII/GSASIIplot.py @@ -474,31 +474,31 @@ def FindPlotTab(self,label,Type,newImage=True,saveLimits=False): Page.toolbar.enableArrows() # Disable Arrow keys if present return new,plotNum,Page,Plot,limits - def savePlotLims(self,Page): + def savePlotLims(self,Page,debug=False): '''Make a copy of all the current axes in the notebook object ''' self.savedPlotLims = [ [i.get_xlim() for i in Page.figure.get_axes()], [i.get_ylim() for i in Page.figure.get_axes()]] - #print(f'saved {len(self.savedPlotLims[1])} axes limits') - + if debug: + print(f'saved {len(self.savedPlotLims[1])} axes limits') + #print( self.savedPlotLims) def restoreSavedPlotLims(self,Page): '''Restore the plot limits, when previously saved, and when ``G2frame.restorePlotLimits`` is set to True, which is done when ``GSASIIpwdplot.refPlotUpdate`` is called with - ``restore=True``, which indicates that "live plotting" is done. + ``restore=True``, which indicates that "live plotting" is + finished. This is also set for certain plot key-press + combinations. The restore operation can only be done once, as the limits are deleted after use in this method. ''' if self.savedPlotLims is None: #print('---- nothing to restore') return - if not hasattr(self.G2frame,'restorePlotLimits'): + if not getattr(self.G2frame,'restorePlotLimits',False): #print('---- restorePlotLimits not set') return - if not self.G2frame.restorePlotLimits: - #print('---- restorePlotLimits: not yet') - return savedPlotLims = self.savedPlotLims axesList = Page.figure.get_axes() if len(axesList) != len(savedPlotLims[0]): diff --git a/GSASII/GSASIIpwdplot.py b/GSASII/GSASIIpwdplot.py index f29388e9..6d874d29 100644 --- a/GSASII/GSASIIpwdplot.py +++ b/GSASII/GSASIIpwdplot.py @@ -188,15 +188,17 @@ def OnPlotKeyPress(event): except TypeError: G2frame.G2plotNB.status.SetStatusText(f'Select {plottype} pattern first',1) return + if 'GROUP' in plottype: # save plot limits in case we want to restore them + G2frame.G2plotNB.savePlotLims(Page) newPlot = False - if event.key == 'w': + if event.key == 'w' and not 'GROUP' in plottype: G2frame.Weight = not G2frame.Weight if not G2frame.Weight and not G2frame.Contour and 'PWDR' in plottype: G2frame.SinglePlot = True elif 'PWDR' in plottype: # Turning on Weight plot clears previous limits G2frame.FixedLimits['dylims'] = ['',''] #newPlot = True # this resets the x & y limits, not wanted! - elif event.key in ['shift+1','!']: # save current plot settings as defaults + elif event.key in ['shift+1','!'] and not 'GROUP' in plottype: # save current plot settings as defaults # shift+1 assumes US keyboard print('saving plotting defaults for',G2frame.GPXtree.GetItemText(G2frame.PatternId)) data = G2frame.GPXtree.GetItemPyData(G2frame.PatternId) @@ -213,6 +215,7 @@ def OnPlotKeyPress(event): 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 + if 'GROUP' in plottype: G2frame.restorePlotLimits = True elif event.key == 'x' and groupName is not None: # share X axis scale for Pattern Groups plotOpt['sharedX'] = not plotOpt['sharedX'] # Clear saved x-limits when toggling sharedX mode (MG/Cl Sonnet) @@ -304,6 +307,7 @@ def OnPlotKeyPress(event): elif Page.plotStyle['Offset'][0] > -100.: Page.plotStyle['Offset'][0] -= 10. elif event.key == 'g': + if 'GROUP' in plottype: G2frame.restorePlotLimits = True mpl.rcParams['axes.grid'] = not mpl.rcParams['axes.grid'] elif event.key == 'l' and not G2frame.SinglePlot: Page.plotStyle['Offset'][1] -= 1. @@ -2257,7 +2261,7 @@ def onGroupXlimChanged(ax): Page.Choice = [' key press', 'f: toggle full-length ticks', 'g: toggle grid', - 's: toggle sqrt plot', + #'s: toggle sqrt plot', # TODO: implement this 'q: toggle Q plot', 't: toggle d-spacing plot', 'x: share x-axes (Q/d only)'] @@ -2326,7 +2330,7 @@ def onGroupXlimChanged(ax): totalrange += gXmax[i]-gXmin[i] if plotOpt['sharedX'] and ( Page.plotStyle['qPlot'] or Page.plotStyle['dPlot']): - Page.figure.text(0.001,0.94,'X shared',fontsize=11, + Page.figure.text(0.94,0.03,'X shared',fontsize=10, color='g') GS_kw = {'height_ratios':[4, 1]} #Plots = Page.figure.subplots(2,Page.groupN,sharey='row',sharex=True, @@ -2484,8 +2488,8 @@ def onGroupXlimChanged(ax): (Page.plotStyle['qPlot'] or Page.plotStyle['dPlot'])): up,down = adjustDim(0,Page.groupN) G2frame.groupXlim = Plots[up].get_xlim() + wx.CallAfter(G2frame.G2plotNB.restoreSavedPlotLims,Page) # restore limits to previous, if saved & requested Page.canvas.draw() - wx.CallLater(100,G2frame.G2plotNB.restoreSavedPlotLims,Page) # restore limits to previous, if saved return # end of group plot elif G2frame.Weight and not G2frame.Contour: Plot.set_visible(False) #hide old plot frame, will get replaced below