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:

  1. Use NewPanel /EXT to produce an attached panel, so on any given graph I can jump to another graph. 
  2. 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.
  3. 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.

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:

  1. Just refuse to handle them unless the user reprograms the graph as a panel + subgraph
  2. Try to shift all controls in the bar out of "my" control region
  3. Use an external window for controls

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).

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

//   Removes unwanted controls from the recreation string.
//   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")

 

@olelytken Thanks, I think you are right that this is the most painless strategy.

I tried to reduce the functions I use to a minimal working example. Run CreateWindow to see how it works

Function CreateWindow()
//  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