Manipulating and combining existing graphs programatically
Hi,
I often have a few data folders (DFs) where I'd like to view the contents -- as graphs. So, I'm currently trying to produce a Panel where I can click through different views in order to visualize the DFs.
I'm using a code package which already works fine to produce various graphs. So, I'm just looking to make a "wrapper" around pre-existing graph-producing functions.
My strategy has been to:
- Execute() the functions and macros which make graphs
- Use WinRecreation() to capture the recreation macros for the resulting graphs, and reproduce them inside a panel
- Kill the original graphs
This leads to some issues and confusion with wave paths in the WinRecreation result (which are sometimes relative and sometimes absolute) and also with ControlBar (I think I will need to remove all ControlBar commands for this to really work... or somehow programatically shift down all controls that would have been inside the ControlBar).
I'm starting to think I should take an alternative approach:
- Use NewPanel /EXT to produce an attached panel, so on any given graph I can jump to another graph.
- Just produce a lot of graphs and use the WindowBrowser to only show graphs for a given DF. Unfortunately, the WindowBrowser doesn't allow filtering by DF, only by wave. But I could use a keyword prefix.
- Just produce one reliable "mega graph" and then use ReplaceWave allincdf
With this post, I'm hoping for any insights from those who have more experience programming UIs in Igor. Thanks in advance, j
I struggled with something akin to this in Image Tools. I need to store images for contact prints. I scroll through the images in a single window, so they do not remain "resident" for some later recreation.
I ended up storing the WinReaction strings for the image graph in a global folder (root:Packages:Image Tools: ...). Each WinReaction string has a name that is characteristic of the image that was stored in the graph at the time. It helps to check back and show the user when that image has already been set as a "contact print image".
I know that I wonder about a "more elegant" way to handle my needs as well. But, necessity was a push, and my approach seems to work now.
Also, as far as I recall, I had some way that I used to remove the control bar portion of the WinReaction. I can dig back for the specifics as needed. In the meantime, you are welcomed to look at the code in the Image Tools Print.ipf procedure file for further insights. Ask back here for specifics as needed.
April 29, 2020 at 02:27 pm - Permalink
Thanks for pointing me to that. I did not really notice any use of ControlBar except for a few buttons.
I think the only way to guarantee elegance in both cases is for the package to only manage graphs it produces.
The fundamental problem for me is that Igor contains certain "hacks" like the ControlBar, which let users blur the line between Graphs and Panels. The ways I see to handle ControlBars are:
April 30, 2020 at 08:44 am - Permalink
Odd. Somehow between the previous version and the current version I've misplaced what happens to the control bar.
You are right about this. A Control Bar will get in the way to create a clean graph copy. I also cannot see a clean way to program around the bells and whistles that come with a control bar, e.g. via regex eliminations from the WinReaction.
I will also say that, when I run an Execute/Q/Z using the WinReaction string that contains a ControlBar commands, I do not get the ControlBar. I am not sure why (I have to dig deeper on what my as-yet-kludged-together code is really doing at that point).
April 30, 2020 at 09:36 am - Permalink
I'm not sure exactly what you want....
But what I often do is browse through XPS spectra, for instance. I do that by creating a graph of an empty wave that I would call MyDisplayedWave, for instance. I also create two popup menus: one listing all datafolders and the other listing all the waves of the desired type and dimensionality in the in folder selected in the first pop up menu. Selecting a wave in the 2nd popup menu now duplicates the selected wave into MyDisplayedWave, which will display the data. I found that much easier and less prone to bugs than adding and removing waves from the graph.
Is it something like that you want?
If you want to remove buttons and controlbars from a windows recreation string have a look at Grep. Below is an example I use to create clean copies of graphs
// GrepList(x, y, 1, "\r") means include all items in x where y is NOT true.
// ^\t(x|y|y) means starting with (^) a tabulator (\t) followed by either x, y or z.
WinRecreationString=GrepList(WinRecreationString, "^\t(DefineGuide|ControlBar|SetWindow|Cursor|ListBox|CheckBox|PopupMenu|ValDisplay|SetVariable|Button|ModifyGraph margin|NewPanel|ModifyPanel|RenameWindow|SetActiveSubwindow|ShowInfo)", 1, "\r")
April 30, 2020 at 10:33 am - Permalink
@olelytken Thanks, I think you are right that this is the most painless strategy.
May 11, 2020 at 11:00 pm - Permalink
I tried to reduce the functions I use to a minimal working example. Run CreateWindow to see how it works
// Creates a graph
String ActiveWindow="MyWindow"
// Creates some dummy waves to display
Make/O/N=20 root:Peter/WAVE=Peter
Make/O/N=100 root:Paul/WAVE=Paul
Make/O/N=500 root:Mary/WAVE=Mary
NewDataFolder/O root:Grimm
DFREF GrimmFolder=root:Grimm
Make/O/N=200 GrimmFolder:Hansel/WAVE=Hansel
Make/O/N=10 GrimmFolder:Gretel/WAVE=Gretel
NewDataFolder/O root:EmptyFolder
Peter=p+10
Paul=exp(p/100)-p
Mary=2*p^4+3*p^3-p+5
Hansel=sin(p/20)-0.01*p
Gretel=cos(p)
SetScale /P x, 1, 2, "", Peter
SetScale /P x, 1, -2, "", Paul
SetScale /P x, 100, 0.1, "", Mary
SetScale /P x, 0, 0.1, "", Hansel
SetScale /P x, 0, -0.1, "", Gretel
// Creates the folder to hold all the permanent waves, used for different bureaucratic purposes, such as listboxes, displaying waves, etc...
NewDataFolder/O root:Programming
DFREF ProgramFolder=root:Programming
// Kills MyWindow if it exists
DoWindow /K $ActiveWindow
// Creates an empty wave to display
Make/O/N=0 ProgramFolder:DataWave/WAVE=DataWave
// Creates the graph
Display /W=(250, 150, 800, 500) /K=1 /N=$ActiveWindow DataWave
ModifyGraph /W=$ActiveWindow margin(top)=80
// Selects the active data folder
PopupMenu DataFolderPopUp bodyWidth=250, mode=1, pos={235, 5}, proc=PopUpFolderProc, value=PopUpFolderList(), win=$ActiveWindow
// Popup menu to select the spectrum to display
PopupMenu DisplayedWavePopUp bodyWidth=250, mode=1, pos={235,30}, proc=PopUpWaveProc, value=PopUpWaveList(), win=$ActiveWindow
// Runs the update function
UpdateMyWindow()
end
Function/S PopUpFolderList()
// Returns a semicolon separated string list of the full paths to all datafolders
String FolderName=""
DFREF ParentFolder=root:
DFREF ChildFolder=root:
DFREF ExcludeFolder=root:Programming
// Creates a wave to hold the names of all existing folders. If more than 4000 folders exist, something will happen....
Make/FREE/O/DF/N=4000 ListOfFolders
ListOfFolders[0]=root:
// Returns a list of the data folders in root:
String ListString=GetDataFolder(1, root:)+";"
// Counts through the list of folders
Variable n=0, i=0, a=0, b=1, c=0
for (a=0; a<b; a+=1)
// Finds the number of additional folders in the active folder
ParentFolder=ListOfFolders[a]
n=CountObjectsDFR(ParentFolder, 4)
// Adds the additional folders to the list of folders, excluding ExcludeFolder
for (i=0; i<n; i+=1)
FolderName=GetIndexedObjNameDFR(ParentFolder, 4, i)
ChildFolder=ParentFolder:$FolderName
if (DataFolderRefsEqual(ChildFolder, ExcludeFolder))
c+=1
else
ListOfFolders[b+i-c]=ChildFolder
ListString+=GetDataFolder(1, ChildFolder)+";"
endif
endfor
// Increases the number of folders by the number of additonal folders
b+=n-c
// Resets the exclude counter
c=0
endfor
// Returns the case-insensitive alphanumerically sorted list of data folders
Return SortList(ListString, ";", 16)
end
Function/S PopUpWaveList()
// Returns a list of all one-dimensional waves in the selected data folder
String ListOfWaves=""
// Finds the selected folder
ControlInfo /W=MyWindow DataFolderPopUp
String FolderName=S_Value
DFREF Folder=$FolderName
if (DataFolderRefStatus(Folder)==0)
// Returns data folder does not exist, if the data folder does not exist
ListOfWaves="Data folder does not exist;"
else
// Saves the active data folder
DFREF CurrentFolder=GetDataFolderDFR()
// Changes the active folder to Folder
SetDataFolder Folder
// Returns a semi-colon seperated list of one-dimensional numeric waves in the active folder
ListOfWaves=WaveList("*", ";", "DIMS:1,DF:0,CMPLX:0,TEXT:0,WAVE:0")
// Returns the active folder to the original folder
SetDataFolder CurrentFolder
// Sorts the list alphabetically, using a case-insensitive alphanumeric sort that sorts wave0 and wave9 before wave10
ListOfWaves=SortList(ListOfWaves, ";", 16)
// If the list is empty "No waves in folder" is added
if (StrLen(ListOfWaves)==0)
ListOfWaves="No waves in folder;"
endif
endif
// Returns the list of waves
Return ListOfWaves
end
Function PopUpFolderProc(PU_Struct) : PopupMenuControl
// Runs the update function if the data folder selection is changed
STRUCT WMPopupAction &PU_Struct
// If the selection was changed
if (PU_Struct.eventCode==2)
// Ignores further calls to the popup menu procedure until this one has finished
PU_Struct.blockReentry=1
// Finds the selected folder
String FolderName=PU_Struct.popStr
DFREF Folder=$FolderName
// Changes the displayed wave selection to the first item in the list and updates the list
PopupMenu DisplayedWavePopUp mode=1, win=$PU_Struct.win
// Runs the update function
UpdateMyWindow()
// If the user scrolls through the items in the list too fast, the selection will change before the update has finished, and the selection will not match the displayed data. This will prevent that
PopupMenu $PU_Struct.ctrlName popmatch=PU_Struct.popStr, win=$PU_Struct.win
endif
end
Function PopUpWaveProc(PU_Struct) : PopupMenuControl
// Runs the update function if the wave selection is changed
STRUCT WMPopupAction &PU_Struct
// If the selection was changed
if (PU_Struct.eventCode==2)
// Ignores further calls to the popup menu procedure until this one has finished
PU_Struct.blockReentry=1
// Runs the update function
UpdateMyWindow()
// If the user scrolls through the items in the list too fast, the selection will change before the update has finished, and the selection will not match the displayed data. This will prevent that
PopupMenu $PU_Struct.ctrlName popmatch=PU_Struct.popStr, win=$PU_Struct.win
endif
end
Function UpdateMyWindow()
// Displays the selected wave in MyWindow
DFREF ProgramFolder=root:Programming
// Finds the selected folder
ControlInfo /W=MyWindow DataFolderPopUp
String FolderName=S_Value
DFREF Folder=$FolderName
if (DataFolderRefStatus(Folder)==0)
// Creates an empty wave to display if the datafolder selection is invalid
Make/O/N=0 ProgramFolder:DataWave
else
// Finds the selected wave
ControlInfo /W=MyWindow DisplayedWavePopUp
String DisplayedWaveName=S_Value
Wave/Z DisplayedWave=Folder:$DisplayedWaveName
if (WaveExists(DisplayedWave))
// Displays the selected wave
Duplicate/O DisplayedWave, ProgramFolder:DataWave
else
// Creates an empty wave to display if the wave selection is invalid
Make/O/N=0 ProgramFolder:DataWave
endif
endif
end
May 12, 2020 at 04:47 am - Permalink